【企业级项目实操指南2】结合已有代码和MPJ实现统一的数据统计接口(2)
【企业级项目实操指南1】结合已有代码和MPJ实现统一的数据统计接口(1)
https://www.cnblogs.com/zwj/p/18829115/bnp-doit-001
在(1)的基础上做一些优化和补充,一方面是满足日期范围搜索条件,一方面是对命名的优化。
后端 - MPJAggregateUtil
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.jeecg.common.system.query.MPJQueryGenerator;
import org.jeecg.common.system.query.ReferenceColumn;
import java.lang.reflect.Field;
import java.util.List;
public class MPJAggregateUtil {
/**
* 构建聚合查询条件
*
* @param mpjLW 查询包装器
* @param entityClass 实体类
* @param columns 统计参数列表
* @param <T> 泛型实体类型
*/
public static <T> void buildAggregation(MPJLambdaWrapper<T> mpjLW, Class<?> entityClass, List<AggregationColumn> columns) {
// 遍历统计列配置
columns.forEach(column -> {
if (column == null) {
throw new IllegalArgumentException("请输入要统计的列");
}
String columnName = column.getColumnName();
AggregationFunction aggregationFunction = column.getFunc();
if (columnName == null || aggregationFunction == null) {
throw new IllegalArgumentException("请输入要统计的方式");
}
Field field = getFieldFromHierarchy(entityClass, columnName);
String sql = "";
if (field != null && field.isAnnotationPresent(ReferenceColumn.class)) {
ReferenceColumn annotation = field.getAnnotation(ReferenceColumn.class);
String tColumn = MPJQueryGenerator.buildReferenceColumn(annotation, mpjLW);
sql = buildSql(aggregationFunction, tColumn, field.getName());
} else {
String snakeCaseColumnName = camelToSnakeCase(columnName);
sql = buildSql(aggregationFunction, "t." + snakeCaseColumnName, snakeCaseColumnName);
}
mpjLW.select(sql);
});
}
/**
* 构建 SQL 聚合表达式
*
* @param aggregationFunction 聚合函数
* @param columnExpression 列表达式
* @param aliasPrefix 别名前缀
* @return 完整的 SQL 表达式
*/
private static String buildSql(AggregationFunction aggregationFunction, String columnExpression, String aliasPrefix) {
String aliasSuffix = aggregationFunction.name().toLowerCase();
switch (aggregationFunction) {
case COUNT_DISTINCT:
return String.format("COUNT(DISTINCT %s) AS %s__count", columnExpression, aliasPrefix);
case COUNT:
case SUM:
case AVG:
case MIN:
case MAX:
return String.format("%s(%s) AS %s__%s", aggregationFunction.getFunctionName(), columnExpression, aliasPrefix, aliasSuffix);
default:
throw new IllegalArgumentException("Unsupported aggregation function: " + aggregationFunction);
}
}
/**
* 小驼峰转蛇形命名(snake_case)
*
* @param str 输入的小驼峰格式字符串
* @return 转换后的蛇形命名字符串(snake_case)
*/
public static String camelToSnakeCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}
StringBuilder result = new StringBuilder();
for (char c : str.toCharArray()) {
if (Character.isUpperCase(c)) {
result.append("_").append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 从类及其父类中递归查找字段
*
* @param clazz 类
* @param fieldName 字段名
* @return 找到的字段,如果未找到则返回 null
*/
private static Field getFieldFromHierarchy(Class<?> clazz, String fieldName) {
while (clazz != null && !clazz.equals(Object.class)) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass(); // 递归查找父类
}
}
return null; // 如果未找到字段,返回 null
}
}
后端 - DTO
@Data
public class AggregationColumn {
private String columnName;
private AggregationFunction func;
}
@Data
public class AggregationData<T> {
private T entity;
private List<AggregationColumn> params;
}
后端 - 枚举
package com.bytz.modules.erp.enums;
public enum AggregationFunction {
COUNT("COUNT"),
COUNT_DISTINCT("COUNT_DISTINCT"),
SUM("SUM"),
AVG("AVG"),
MIN("MIN"),
MAX("MAX");
private final String functionName;
AggregationFunction(String functionName) {
this.functionName = functionName;
}
public String getFunctionName() {
return functionName;
}
@Override
public String toString() {
return functionName;
}
}
后端 - Controller
@ApiOperation("数据统计")
@RequestMapping(value = "/aggregate", method = RequestMethod.POST)
public Result<List<Map<String, Object>>> statTimeCard(
@RequestBody AggregationData<TimeCardRes> aggregationData,
HttpServletRequest req) {
return Result.ok(tsmService.statWithJoin(aggregationData.getEntity(), req.getParameterMap(), aggregationData.getParams()));
}
}
后端 - Service
public List<Map<String, Object>> statWithJoin(TimeCardRes conditionEntity, Map<String, String[]> parameters,List<AggregationColumn> columns) {
MPJLambdaWrapper<TimeCard> mpjLW = new MPJLambdaWrapper<>(TimeCard.class);
// 联表
mpjLW.leftJoin(ActivityInfo.class, ActivityInfo::getId, TimeCard::getActivityId);
mpjLW.leftJoin(SysUser.class, SysUser::getId, TimeCard::getUserId);
// 条件
MPJQueryGenerator.installMPJ(mpjLW, conditionEntity, parameters);
MPJAggregateUtil.buildAggregation(mpjLW, TimeCardRes.class ,columns);
return this.selectJoinMaps(mpjLW);
}
前端 - 请求接口
const aggregateData = (url, queryParam, aggregationParams) => postAction(url, {
entity: queryParam,
params: aggregationParams
});
前端 - 请求方法
aggregate() {
// 说明有需要统计的内容
aggregateData(this.aggregationUrl + "/aggregate" + this.urlParam, this.getQueryParams(), this.aggregationParams)
.then(res => {
if (res.code === 200) {
// 如果返回的结果不为空,则更新 aggregationResult
if (res.result[0] !== null) {
this.aggregationResult = Object.fromEntries(
Object.entries(res.result[0]).map(([key, value]) => [key, value ?? 0])
);
} else {
// 如果返回的结果为空,则将所有字段设置为 0
this.aggregationResult = Object.fromEntries(
Object.keys(this.aggregationResult).map(key => [key, 0])
);
}
}
});
关于URLParam的问题,这个变量包含的参数会被后端的req接收到
思考一个问题,加载List的时候,用的是get请求,因此 HttpServletRequest req能正确收到数据
但Post请求的时候,请求体都在Body,就会出现req根本收不到任何数据
可是我们必须把某些数据放在req里面(主要是为了和body的参数分离),以便复用原有的解析方法
解决办法也很简单,就是使用URL拼接的方式
定义一个 urlParam: "?",然后把要放在URL里面的参数拼接上去
比如
getQueryParams(startIndex = 1) {
this.urlParam = "?"; // 重置请求路径上的参数
const queryParam = { ...this.queryParam, ...this.queryParamOther };
// 增加对 startPage的支持 2025-1-16
queryParam.startPage = this.ipagination.current - startIndex;
Object.keys(queryParam).forEach(key => {
// 起始时间
if (key.endsWith("_flag") && queryParam[key].length > 0) {
queryParam[key.slice(0, -5) + "_begin"] = moment(queryParam[key][0]).startOf("day").format("YYYY-MM-DD HH:mm:ss");
queryParam[key.slice(0, -5) + "_end"] = moment(queryParam[key][1]).endOf("day").format("YYYY-MM-DD HH:mm:ss");
delete queryParam[key];
this.urlParam += key.slice(0, -5) + "_begin=" + queryParam[key.slice(0, -5) + "_begin"] + "&" + key.slice(0, -5) + "_end=" + queryParam[key.slice(0, -5) + "_end"];
}
……其他代码
当然,你后端要有对应的处理逻辑哈,没这个需求可以不要的
前端 - 参数
aggregationUrl: "/erp/issuedInvoice",
aggregationParams: [
{
"columnName": "invoiceAmount",
"func": "SUM"
}
],
aggregationResult: {
invoice_amount__sum: 0
}
前端 - 显示
<div class="statistics-info">
<span> 开票总金额:¥ {{ aggregationResult.invoice_amount__sum | ThousandSeparate(2) }} </span>
<a-button type="link" @click="aggregate">
刷新统计
</a-button>
</div>
前端 - 样式
.list-action {
display: flex;
justify-content: space-between;
align-items: center;
}
.statistics-info {
margin-left: 20px;
padding: 4px 12px;
box-sizing: border-box;
span {
margin-right: 10px;
&:not(:first-child) {
border-left: 1px solid #ccc;
padding-left: 10px;
}
}
}
版 权 声 明

浙公网安备 33010602011771号