1.46 增加通用统计查询方法,子类直接实现即可。

This commit is contained in:
dengqichen 2025-12-29 14:53:46 +08:00
parent 23f4daf3e9
commit 16a7b9513d
10 changed files with 51 additions and 187 deletions

View File

@ -7,7 +7,6 @@ import com.qqchen.deploy.backend.deploy.query.ApplicationQuery;
import com.qqchen.deploy.backend.deploy.service.IApplicationService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.framework.dto.PageResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@ -54,11 +53,6 @@ public class ApplicationApiController extends BaseController<Application, Applic
return super.findAll();
}
@GetMapping("/pageWithStats")
public Response<PageResult<ApplicationDTO>> pageWithStats(ApplicationQuery query) {
return Response.success(applicationService.pageWithStats(query));
}
@Override
public Response<List<ApplicationDTO>> findAll(ApplicationQuery query) {
return super.findAll(query);

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.deploy.query;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
@ -28,8 +29,8 @@ public class ApplicationQuery extends BaseQuery {
@QueryField(field = "buildType")
private String buildType;
@QueryField(field = "languageType")
private String languageType;
@QueryField(field = "language")
private DevelopmentLanguageTypeEnum language;
@QueryField(field = "applicationCategoryId")
private Long applicationCategoryId;

View File

@ -1,13 +1,9 @@
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.Application;
import com.qqchen.deploy.backend.deploy.repository.projection.ApplicationEnabledStatistics;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface IApplicationRepository extends IBaseRepository<Application, Long> {
@ -16,15 +12,4 @@ public interface IApplicationRepository extends IBaseRepository<Application, Lon
*/
Long countByApplicationCategoryIdAndDeletedFalse(Long applicationCategoryId);
/**
* 按启用状态分组统计应用数量一次查询获取已启用和已禁用的数量
*
* @return 统计结果列表包含enabled状态和对应的count
*/
@Query("SELECT a.enabled as enabled, COUNT(a) as count " +
"FROM Application a " +
"WHERE a.deleted = false " +
"GROUP BY a.enabled")
List<ApplicationEnabledStatistics> countByEnabledGroupBy();
}

View File

@ -1,20 +0,0 @@
package com.qqchen.deploy.backend.deploy.repository.projection;
/**
* 应用启用状态统计投影接口
* 用于Spring Data JPA的查询结果映射
*/
public interface ApplicationEnabledStatistics {
/**
* 获取启用状态
* @return true=已启用false=已禁用
*/
Boolean getEnabled();
/**
* 获取该状态下的应用数量
* @return 应用数量
*/
Long getCount();
}

View File

@ -4,21 +4,12 @@ import com.qqchen.deploy.backend.deploy.dto.ApplicationDTO;
import com.qqchen.deploy.backend.deploy.dto.DevelopmentLanguageTypeDTO;
import com.qqchen.deploy.backend.deploy.entity.Application;
import com.qqchen.deploy.backend.deploy.query.ApplicationQuery;
import com.qqchen.deploy.backend.framework.dto.PageResult;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List;
public interface IApplicationService extends IBaseService<Application, ApplicationDTO, ApplicationQuery, Long> {
/**
* 分页查询应用带统计信息
*
* @param query 查询条件
* @return 分页结果包含已启用和已禁用的总数统计
*/
PageResult<ApplicationDTO> pageWithStats(ApplicationQuery query);
List<DevelopmentLanguageTypeDTO> getAllDevelopmentLanguageTypes();
}

View File

@ -11,6 +11,7 @@ import com.qqchen.deploy.backend.system.model.ExternalSystemDTO;
import com.qqchen.deploy.backend.deploy.entity.Application;
import com.qqchen.deploy.backend.deploy.entity.ApplicationCategory;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.QApplication;
import com.qqchen.deploy.backend.deploy.entity.RepositoryProject;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.deploy.query.ApplicationQuery;
@ -19,23 +20,21 @@ import com.qqchen.deploy.backend.deploy.repository.IApplicationRepository;
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
import com.qqchen.deploy.backend.deploy.repository.IRepositoryProjectRepository;
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
import com.qqchen.deploy.backend.deploy.repository.projection.ApplicationEnabledStatistics;
import com.qqchen.deploy.backend.deploy.service.IApplicationService;
import com.qqchen.deploy.backend.framework.dto.PageResult;
import com.qqchen.deploy.backend.framework.dto.PageStats;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import org.springframework.data.domain.Page;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.querydsl.core.BooleanBuilder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.toList;
@ -111,36 +110,27 @@ public class ApplicationServiceImpl extends BaseServiceImpl<Application, Applica
});
}
/**
* 添加启用/禁用统计信息
*/
@Override
public PageResult<ApplicationDTO> pageWithStats(ApplicationQuery query) {
Page<ApplicationDTO> page = super.page(query);
fillApplicationRelations(page.getContent());
protected void addStats(PageStats<ApplicationDTO> result, ApplicationQuery query) {
// 复用框架的查询条件构建
BooleanBuilder predicate = createQueryPredicate(query);
// 一次查询获取启用和禁用状态的应用总数按enabled分组统计
List<ApplicationEnabledStatistics> statistics = applicationRepository.countByEnabledGroupBy();
// 统计启用和禁用数量带筛选条件
Long enabledCount = applicationRepository.count(predicate.and(QApplication.application.enabled.eq(true)));
Long disabledCount = applicationRepository.count(predicate.and(QApplication.application.enabled.eq(false)));
// 解析统计结果
Long enabledCount = 0L;
Long disabledCount = 0L;
for (ApplicationEnabledStatistics stat : statistics) {
if (Boolean.TRUE.equals(stat.getEnabled())) {
enabledCount = stat.getCount();
} else if (Boolean.FALSE.equals(stat.getEnabled())) {
disabledCount = stat.getCount();
}
}
// 构建PageResult并添加统计字段
return new PageResult<>(page)
.addExtraField("enabledCount", enabledCount)
.addExtraField("disabledCount", disabledCount);
result.addStat("enabledCount", enabledCount);
result.addStat("disabledCount", disabledCount);
}
@Override
public Page<ApplicationDTO> page(ApplicationQuery query) {
Page<ApplicationDTO> page = super.page(query);
fillApplicationRelations(page.getContent());
return page;
Page<ApplicationDTO> result = super.page(query);
fillApplicationRelations(result.getContent());
return result;
}
@Override

View File

@ -2,12 +2,12 @@ package com.qqchen.deploy.backend.framework.controller;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import org.springframework.data.domain.Page;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

View File

@ -15,225 +15,136 @@ import java.util.Map;
import java.util.function.Function;
/**
* 通用分页结果包装类
* 带统计信息的分页结果包装类
* 实现Page接口保留Spring Data Page的所有字段并支持添加额外的统计字段
* 额外字段会通过@JsonAnyGetter展开到与totalElements同一层级
*
* @param <T> 分页数据类型
*/
public class PageResult<T> implements Page<T>, Serializable {
public class PageStats<T> implements Page<T>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 内部Page对象用于委托所有Page接口方法
*/
@JsonIgnore
private final Page<T> page;
/**
* 额外的统计字段会被展开到JSON的顶层
*/
@JsonIgnore
private final Map<String, Object> extraFields = new HashMap<>();
private final Map<String, Object> stats = new HashMap<>();
/**
* 构造函数
*
* @param page Spring Data Page对象
*/
public PageResult(Page<T> page) {
public PageStats(Page<T> page) {
this.page = page;
}
/**
* 添加额外的统计字段
*
* @param key 字段名
* @param value 字段值
* @return 当前对象支持链式调用
* 添加统计字段
*/
public PageResult<T> addExtraField(String key, Object value) {
this.extraFields.put(key, value);
public PageStats<T> addStat(String key, Object value) {
this.stats.put(key, value);
return this;
}
/**
* Jackson注解将extraFields的内容展开到JSON的顶层
* 使得额外字段与totalElements等在同一层级
*/
@JsonAnyGetter
public Map<String, Object> getExtraFields() {
return extraFields;
public Map<String, Object> getStats() {
return stats;
}
/**
* Jackson注解支持反序列化时设置额外字段
*/
@JsonAnySetter
public void setExtraField(String key, Object value) {
extraFields.put(key, value);
public void setStat(String key, Object value) {
stats.put(key, value);
}
// ==================== 实现Page接口的所有方法 ====================
// ==================== 实现Page接口 ====================
/**
* 获取当前页的数据列表
*/
@Override
public List<T> getContent() {
return page.getContent();
}
/**
* 获取总记录数
*/
@Override
public long getTotalElements() {
return page.getTotalElements();
}
/**
* 获取总页数
*/
@Override
public int getTotalPages() {
return page.getTotalPages();
}
/**
* 获取当前页码从0开始
*/
@Override
public int getNumber() {
return page.getNumber();
}
/**
* 获取每页大小
*/
@Override
public int getSize() {
return page.getSize();
}
/**
* 获取当前页的实际记录数
*/
@Override
public int getNumberOfElements() {
return page.getNumberOfElements();
}
/**
* 是否有内容
*/
@Override
public boolean hasContent() {
return page.hasContent();
}
/**
* 是否为空
*/
@Override
public boolean isEmpty() {
return page.isEmpty();
}
/**
* 是否为第一页
*/
@Override
public boolean isFirst() {
return page.isFirst();
}
/**
* 是否为最后一页
*/
@Override
public boolean isLast() {
return page.isLast();
}
/**
* 是否有下一页
*/
@Override
public boolean hasNext() {
return page.hasNext();
}
/**
* 是否有上一页
*/
@Override
public boolean hasPrevious() {
return page.hasPrevious();
}
/**
* 获取排序信息
*/
@Override
public Sort getSort() {
return page.getSort();
}
/**
* 获取分页信息
*/
@Override
public Pageable getPageable() {
return page.getPageable();
}
/**
* 获取下一页的分页信息
*/
@Override
public Pageable nextPageable() {
return page.nextPageable();
}
/**
* 获取上一页的分页信息
*/
@Override
public Pageable previousPageable() {
return page.previousPageable();
}
/**
* 转换当前页的数据类型
*
* @param converter 转换函数
* @param <U> 目标类型
* @return 新的PageResult对象
*/
@Override
public <U> PageResult<U> map(Function<? super T, ? extends U> converter) {
public <U> PageStats<U> map(Function<? super T, ? extends U> converter) {
Page<U> mappedPage = page.map(converter);
PageResult<U> result = new PageResult<>(mappedPage);
// 复制额外字段
result.extraFields.putAll(this.extraFields);
PageStats<U> result = new PageStats<>(mappedPage);
result.stats.putAll(this.stats);
return result;
}
/**
* 获取迭代器
*/
@Override
public Iterator<T> iterator() {
return page.iterator();
}
/**
* 获取内部Page对象用于需要直接访问Page的场景
*/
@JsonIgnore
public Page<T> getPage() {
return page;
}
}

View File

@ -2,8 +2,8 @@ package com.qqchen.deploy.backend.framework.service;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import org.springframework.data.domain.Page;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import java.io.Serializable;
import java.util.List;

View File

@ -11,6 +11,7 @@ import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.framework.dto.PageStats;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.framework.query.DateRange;
@ -131,7 +132,18 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, Q
public Page<D> page(Q query) {
validateDatabaseOperation("page");
Page<T> page = repository.findAll(createQueryPredicate(query), createPageRequest(query));
return converter.toDtoPage(page);
Page<D> dtoPage = converter.toDtoPage(page);
PageStats<D> result = new PageStats<>(dtoPage);
addStats(result, query);
return result;
}
/**
* 添加统计信息钩子方法
* 子类可重写此方法添加自定义统计字段
*/
protected void addStats(PageStats<D> result, Q query) {
// 默认空实现子类可重写
}
protected BooleanBuilder createQueryPredicate(BaseQuery query) {