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

View File

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

View File

@ -1,13 +1,9 @@
package com.qqchen.deploy.backend.deploy.repository; package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.Application; 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 com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
@Repository @Repository
public interface IApplicationRepository extends IBaseRepository<Application, Long> { public interface IApplicationRepository extends IBaseRepository<Application, Long> {
@ -16,15 +12,4 @@ public interface IApplicationRepository extends IBaseRepository<Application, Lon
*/ */
Long countByApplicationCategoryIdAndDeletedFalse(Long applicationCategoryId); 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.dto.DevelopmentLanguageTypeDTO;
import com.qqchen.deploy.backend.deploy.entity.Application; import com.qqchen.deploy.backend.deploy.entity.Application;
import com.qqchen.deploy.backend.deploy.query.ApplicationQuery; 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 com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List; import java.util.List;
public interface IApplicationService extends IBaseService<Application, ApplicationDTO, ApplicationQuery, Long> { public interface IApplicationService extends IBaseService<Application, ApplicationDTO, ApplicationQuery, Long> {
/**
* 分页查询应用带统计信息
*
* @param query 查询条件
* @return 分页结果包含已启用和已禁用的总数统计
*/
PageResult<ApplicationDTO> pageWithStats(ApplicationQuery query);
List<DevelopmentLanguageTypeDTO> getAllDevelopmentLanguageTypes(); 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.Application;
import com.qqchen.deploy.backend.deploy.entity.ApplicationCategory; import com.qqchen.deploy.backend.deploy.entity.ApplicationCategory;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; 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.entity.RepositoryProject;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum; import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.deploy.query.ApplicationQuery; 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.IExternalSystemRepository;
import com.qqchen.deploy.backend.deploy.repository.IRepositoryProjectRepository; import com.qqchen.deploy.backend.deploy.repository.IRepositoryProjectRepository;
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository; 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.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 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.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.querydsl.core.BooleanBuilder;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -111,36 +110,27 @@ public class ApplicationServiceImpl extends BaseServiceImpl<Application, Applica
}); });
} }
/**
* 添加启用/禁用统计信息
*/
@Override @Override
public PageResult<ApplicationDTO> pageWithStats(ApplicationQuery query) { protected void addStats(PageStats<ApplicationDTO> result, ApplicationQuery query) {
Page<ApplicationDTO> page = super.page(query); // 复用框架的查询条件构建
fillApplicationRelations(page.getContent()); 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)));
// 解析统计结果 result.addStat("enabledCount", enabledCount);
Long enabledCount = 0L; result.addStat("disabledCount", disabledCount);
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);
} }
@Override @Override
public Page<ApplicationDTO> page(ApplicationQuery query) { public Page<ApplicationDTO> page(ApplicationQuery query) {
Page<ApplicationDTO> page = super.page(query); Page<ApplicationDTO> result = super.page(query);
fillApplicationRelations(page.getContent()); fillApplicationRelations(result.getContent());
return page; return result;
} }
@Override @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.domain.Entity;
import com.qqchen.deploy.backend.framework.dto.BaseDTO; 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.query.BaseQuery;
import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.framework.service.IBaseService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

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