Generate admin chart aggregation endpoints
This commit is contained in:
@@ -34,4 +34,43 @@ public class ChartQueryParameter
|
|||||||
{
|
{
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJavaLiteral()
|
||||||
|
{
|
||||||
|
return toJavaLiteral(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJavaLiteral(Object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
if (value instanceof Number || value instanceof Boolean)
|
||||||
|
{
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
return "\"" + escapeJava(String.valueOf(value)) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeJava(String value)
|
||||||
|
{
|
||||||
|
StringBuilder escaped = new StringBuilder();
|
||||||
|
for (int index = 0; index < value.length(); index++)
|
||||||
|
{
|
||||||
|
char ch = value.charAt(index);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\\': escaped.append("\\\\"); break;
|
||||||
|
case '"': escaped.append("\\\""); break;
|
||||||
|
case '\n': escaped.append("\\n"); break;
|
||||||
|
case '\r': escaped.append("\\r"); break;
|
||||||
|
case '\t': escaped.append("\\t"); break;
|
||||||
|
case '\b': escaped.append("\\b"); break;
|
||||||
|
case '\f': escaped.append("\\f"); break;
|
||||||
|
default: escaped.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return escaped.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import com.ruoyi.generator.domain.front.dto.block.BusinessBlockConfigField;
|
|||||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
|
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
|
||||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
|
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
|
||||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockTemplateFile;
|
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockTemplateFile;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.block.ChartMetricConfig;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.block.ChartQueryParameter;
|
||||||
import com.ruoyi.generator.domain.front.dto.block.ChartQueryRenderModel;
|
import com.ruoyi.generator.domain.front.dto.block.ChartQueryRenderModel;
|
||||||
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;
|
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;
|
||||||
import com.ruoyi.generator.service.front.BusinessBlockRegistryService;
|
import com.ruoyi.generator.service.front.BusinessBlockRegistryService;
|
||||||
@@ -473,6 +475,11 @@ public class BusinessBlockGenerationService
|
|||||||
context.put("chartType", file.getDefinition().getChartType());
|
context.put("chartType", file.getDefinition().getChartType());
|
||||||
context.put("chartDataset", instance.getDataset());
|
context.put("chartDataset", instance.getDataset());
|
||||||
context.put("chartDisplay", instance.getDisplay());
|
context.put("chartDisplay", instance.getDisplay());
|
||||||
|
context.put("chartMetrics", chartMetricRenderModels(chartQuery.getMetrics()));
|
||||||
|
context.put("chartDateStartLiteral", ChartQueryParameter.toJavaLiteral(
|
||||||
|
chartQuery.getDateRange() == null ? null : chartQuery.getDateRange().getStart()));
|
||||||
|
context.put("chartDateEndLiteral", ChartQueryParameter.toJavaLiteral(
|
||||||
|
chartQuery.getDateRange() == null ? null : chartQuery.getDateRange().getEnd()));
|
||||||
context.put("apiBasePath", "/admin/business-blocks/" + file.getInstanceCode());
|
context.put("apiBasePath", "/admin/business-blocks/" + file.getInstanceCode());
|
||||||
}
|
}
|
||||||
context.put("cartPkColumn", pkColumnName(project, renderConfig.get("cartTable")));
|
context.put("cartPkColumn", pkColumnName(project, renderConfig.get("cartTable")));
|
||||||
@@ -530,6 +537,22 @@ public class BusinessBlockGenerationService
|
|||||||
return renderConfig;
|
return renderConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Map<String, String>> chartMetricRenderModels(List<ChartMetricConfig> metrics)
|
||||||
|
{
|
||||||
|
List<Map<String, String>> result = new ArrayList<Map<String, String>>();
|
||||||
|
for (ChartMetricConfig metric : metrics == null
|
||||||
|
? Collections.<ChartMetricConfig>emptyList() : metrics)
|
||||||
|
{
|
||||||
|
Map<String, String> item = new LinkedHashMap<String, String>();
|
||||||
|
item.put("alias", metric.getAlias());
|
||||||
|
item.put("aliasLiteral", ChartQueryParameter.toJavaLiteral(metric.getAlias()));
|
||||||
|
item.put("labelLiteral", ChartQueryParameter.toJavaLiteral(
|
||||||
|
StringUtils.defaultIfEmpty(metric.getLabel(), metric.getAlias())));
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public void putEmbeddedBlockVelocityContext(VelocityContext context, GenProject project,
|
public void putEmbeddedBlockVelocityContext(VelocityContext context, GenProject project,
|
||||||
FrontProjectPageDesign pageDesign)
|
FrontProjectPageDesign pageDesign)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ${packageName}.controller.block;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import ${packageName}.common.Result;
|
||||||
|
import ${packageName}.service.block.I${InstanceClassName}Service;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("${apiBasePath}")
|
||||||
|
public class ${InstanceClassName}Controller
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private I${InstanceClassName}Service ${instanceClassName}Service;
|
||||||
|
|
||||||
|
@GetMapping("/data")
|
||||||
|
public Result<Map<String, Object>> data()
|
||||||
|
{
|
||||||
|
return Result.success(${instanceClassName}Service.loadData());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ${packageName}.mapper.block;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ${InstanceClassName}Mapper
|
||||||
|
{
|
||||||
|
List<Map<String, Object>> selectData(Map<String, Object> params);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="${packageName}.mapper.block.${InstanceClassName}Mapper">
|
||||||
|
|
||||||
|
<select id="selectData" parameterType="java.util.Map" resultType="java.util.LinkedHashMap">
|
||||||
|
select
|
||||||
|
#if(!$chartQuery.metricOnly)
|
||||||
|
${chartQuery.dimensionSql} as dimensionKey,
|
||||||
|
#end
|
||||||
|
${chartQuery.metricSelectSql}
|
||||||
|
from ${chartQuery.tableName} t
|
||||||
|
#if($chartQuery.whereSql)
|
||||||
|
where ${chartQuery.whereSql}
|
||||||
|
#end
|
||||||
|
#if($chartQuery.groupBySql)
|
||||||
|
group by ${chartQuery.groupBySql}
|
||||||
|
#end
|
||||||
|
#if($chartQuery.orderBySql)
|
||||||
|
order by ${chartQuery.orderBySql}
|
||||||
|
#end
|
||||||
|
limit ${chartQuery.limit}
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ${packageName}.service.block;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface I${InstanceClassName}Service
|
||||||
|
{
|
||||||
|
Map<String, Object> loadData();
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package ${packageName}.service.block.impl;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import ${packageName}.mapper.block.${InstanceClassName}Mapper;
|
||||||
|
import ${packageName}.service.block.I${InstanceClassName}Service;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ${InstanceClassName}ServiceImpl implements I${InstanceClassName}Service
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private ${InstanceClassName}Mapper ${instanceClassName}Mapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> loadData()
|
||||||
|
{
|
||||||
|
Map<String, Object> params = new LinkedHashMap<String, Object>();
|
||||||
|
#foreach($parameter in $chartQuery.parameters)
|
||||||
|
params.put("${parameter.name}", ${parameter.javaLiteral});
|
||||||
|
#end
|
||||||
|
putDateRange(params);
|
||||||
|
|
||||||
|
List<Map<String, Object>> rows = ${instanceClassName}Mapper.selectData(params);
|
||||||
|
List<Map<String, Object>> dimensions = new ArrayList<Map<String, Object>>();
|
||||||
|
List<Map<String, Object>> series = new ArrayList<Map<String, Object>>();
|
||||||
|
Map<String, Object> summary = new LinkedHashMap<String, Object>();
|
||||||
|
|
||||||
|
#if($chartQuery.metricOnly)
|
||||||
|
Object value = rows.isEmpty() ? null : rows.get(0).get(${chartMetrics.get(0).aliasLiteral});
|
||||||
|
summary.put("value", value);
|
||||||
|
summary.put("count", Integer.valueOf(rows.size()));
|
||||||
|
#else
|
||||||
|
#foreach($metric in $chartMetrics)
|
||||||
|
series.add(seriesItem(${metric.labelLiteral}, ${metric.aliasLiteral}));
|
||||||
|
#end
|
||||||
|
for (Map<String, Object> row : rows)
|
||||||
|
{
|
||||||
|
Map<String, Object> dimension = new LinkedHashMap<String, Object>();
|
||||||
|
dimension.put("label", row.get("dimensionKey"));
|
||||||
|
dimension.put("value", row.get("dimensionKey"));
|
||||||
|
dimensions.add(dimension);
|
||||||
|
#foreach($metric in $chartMetrics)
|
||||||
|
appendSeriesValue(series, ${foreach.index}, row.get(${metric.aliasLiteral}));
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
summary.put("count", Integer.valueOf(rows.size()));
|
||||||
|
#end
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||||
|
result.put("dimensions", dimensions);
|
||||||
|
result.put("series", series);
|
||||||
|
result.put("summary", summary);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> seriesItem(String name, String alias)
|
||||||
|
{
|
||||||
|
Map<String, Object> item = new LinkedHashMap<String, Object>();
|
||||||
|
item.put("name", name);
|
||||||
|
item.put("alias", alias);
|
||||||
|
item.put("values", new ArrayList<Object>());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void appendSeriesValue(List<Map<String, Object>> series, int index, Object value)
|
||||||
|
{
|
||||||
|
((List<Object>) series.get(index).get("values")).add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putDateRange(Map<String, Object> params)
|
||||||
|
{
|
||||||
|
#if($chartQuery.dateRange)
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
#if($chartQuery.dateRange.preset == "today")
|
||||||
|
params.put("dateStart", today.atStartOfDay());
|
||||||
|
params.put("dateEnd", today.plusDays(1).atStartOfDay());
|
||||||
|
#elseif($chartQuery.dateRange.preset == "last_7_days")
|
||||||
|
params.put("dateStart", today.minusDays(6).atStartOfDay());
|
||||||
|
params.put("dateEnd", today.plusDays(1).atStartOfDay());
|
||||||
|
#elseif($chartQuery.dateRange.preset == "last_30_days")
|
||||||
|
params.put("dateStart", today.minusDays(29).atStartOfDay());
|
||||||
|
params.put("dateEnd", today.plusDays(1).atStartOfDay());
|
||||||
|
#elseif($chartQuery.dateRange.preset == "this_month")
|
||||||
|
params.put("dateStart", today.withDayOfMonth(1).atStartOfDay());
|
||||||
|
params.put("dateEnd", today.withDayOfMonth(1).plusMonths(1).atStartOfDay());
|
||||||
|
#elseif($chartQuery.dateRange.preset == "this_year")
|
||||||
|
params.put("dateStart", today.withDayOfYear(1).atStartOfDay());
|
||||||
|
params.put("dateEnd", today.withDayOfYear(1).plusYears(1).atStartOfDay());
|
||||||
|
#else
|
||||||
|
params.put("dateStart", LocalDate.parse(${chartDateStartLiteral}).atStartOfDay());
|
||||||
|
params.put("dateEnd", LocalDate.parse(${chartDateEndLiteral}).plusDays(1).atStartOfDay());
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -344,6 +344,29 @@ public class BusinessBlockGenerationServiceTest
|
|||||||
assertEquals(Integer.valueOf(4), blocks.get(1).get("span"));
|
assertEquals(Integer.valueOf(4), blocks.get(1).get("span"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generatedChartBackendUsesBoundParametersAndUnifiedResponse()
|
||||||
|
{
|
||||||
|
Map<String, String> rendered = renderChartFiles(chartProject());
|
||||||
|
|
||||||
|
assertTrue(rendered.get("mapper").contains(
|
||||||
|
"List<Map<String, Object>> selectData(Map<String, Object> params)"));
|
||||||
|
assertTrue(rendered.get("mapperXml").contains("t.status = #{filter0}"));
|
||||||
|
assertTrue(rendered.get("mapperXml").contains("t.create_time >= #{dateStart}"));
|
||||||
|
assertTrue(rendered.get("mapperXml").contains("t.create_time < #{dateEnd}"));
|
||||||
|
assertFalse(rendered.get("mapperXml").contains("PAID"));
|
||||||
|
assertTrue(rendered.get("mapperXml").contains("limit 100"));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("params.put(\"filter0\", \"PAID\")"));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("today.minusDays(29).atStartOfDay()"));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("seriesItem(\"Sales\", \"salesAmount\")"));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("\"dimensions\""));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("\"series\""));
|
||||||
|
assertTrue(rendered.get("serviceImpl").contains("\"summary\""));
|
||||||
|
assertTrue(rendered.get("controller").contains(
|
||||||
|
"@RequestMapping(\"/admin/business-blocks/admin_line_chart_001\")"));
|
||||||
|
assertTrue(rendered.get("controller").contains("@GetMapping(\"/data\")"));
|
||||||
|
}
|
||||||
|
|
||||||
private void setField(String name, Object value) throws Exception
|
private void setField(String name, Object value) throws Exception
|
||||||
{
|
{
|
||||||
Field field = BusinessBlockGenerationService.class.getDeclaredField(name);
|
Field field = BusinessBlockGenerationService.class.getDeclaredField(name);
|
||||||
@@ -406,6 +429,19 @@ public class BusinessBlockGenerationServiceTest
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, String> renderChartFiles(GenProject project)
|
||||||
|
{
|
||||||
|
Map<String, String> rendered = new LinkedHashMap<String, String>();
|
||||||
|
for (GeneratedBusinessBlockFile file : service.listFiles(project, "backend"))
|
||||||
|
{
|
||||||
|
if ("admin_line_chart_001".equals(file.getInstanceCode()))
|
||||||
|
{
|
||||||
|
rendered.put(file.getTemplateKey(), service.renderFile(project, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
private void assertRenderedFiles(GenProject project, List<GeneratedBusinessBlockFile> files)
|
private void assertRenderedFiles(GenProject project, List<GeneratedBusinessBlockFile> files)
|
||||||
{
|
{
|
||||||
for (GeneratedBusinessBlockFile file : files)
|
for (GeneratedBusinessBlockFile file : files)
|
||||||
@@ -550,6 +586,7 @@ public class BusinessBlockGenerationServiceTest
|
|||||||
+ "\"dimension\":{\"field\":\"createTime\",\"timeUnit\":\"day\"},"
|
+ "\"dimension\":{\"field\":\"createTime\",\"timeUnit\":\"day\"},"
|
||||||
+ "\"metrics\":[{\"field\":\"totalAmount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\",\"label\":\"Sales\"}],"
|
+ "\"metrics\":[{\"field\":\"totalAmount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\",\"label\":\"Sales\"}],"
|
||||||
+ "\"filters\":[{\"field\":\"status\",\"operator\":\"eq\",\"value\":\"PAID\"}],"
|
+ "\"filters\":[{\"field\":\"status\",\"operator\":\"eq\",\"value\":\"PAID\"}],"
|
||||||
|
+ "\"dateRange\":{\"field\":\"createTime\",\"preset\":\"last_30_days\"},"
|
||||||
+ "\"sort\":{\"by\":\"dimension\",\"order\":\"asc\"},\"limit\":100},"
|
+ "\"sort\":{\"by\":\"dimension\",\"order\":\"asc\"},\"limit\":100},"
|
||||||
+ "\"display\":{\"title\":\"Sales Trend\",\"smooth\":true}},"
|
+ "\"display\":{\"title\":\"Sales Trend\",\"smooth\":true}},"
|
||||||
+ "{\"id\":\"admin_metric_chart_001\",\"blockCode\":\"admin_metric_chart\",\"name\":\"Order Count\",\"span\":4,"
|
+ "{\"id\":\"admin_metric_chart_001\",\"blockCode\":\"admin_metric_chart\",\"name\":\"Order Count\",\"span\":4,"
|
||||||
|
|||||||
@@ -53,6 +53,17 @@ public class ChartBlockRenderSupportTest
|
|||||||
assertFalse(model.getWhereSql().contains("OR 1=1"));
|
assertFalse(model.getWhereSql().contains("OR 1=1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rendersFilterValuesAsSafeJavaLiteralsForGeneratedServices()
|
||||||
|
{
|
||||||
|
BusinessBlockInstance instance = lineInstance();
|
||||||
|
instance.getDataset().getFilters().get(0).setValue("PAID\" + run();\n//");
|
||||||
|
|
||||||
|
ChartQueryRenderModel model = support.build(orderTable(), definition("line"), instance);
|
||||||
|
|
||||||
|
assertEquals("\"PAID\\\" + run();\\n//\"", model.getParameters().get(0).getJavaLiteral());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void formatsDayWeekAndMonthDimensionsFromResolvedColumns()
|
public void formatsDayWeekAndMonthDimensionsFromResolvedColumns()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user