|
|
|
|
|
|
|
|
|
|
|
package com.ruoyi.tjfx.service.impl; |
|
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
|
|
import com.ruoyi.tjfx.entity.TjfxAnalysisDataDTO; |
|
|
|
|
|
import com.ruoyi.tjfx.mapper.TjfxAnalysisDataMapper; |
|
|
|
|
|
import com.ruoyi.tjfx.service.TjfxAnalysisDataService; |
|
|
|
|
|
import org.slf4j.Logger; |
|
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Service |
|
|
|
|
|
public class TjfxAnalysisDataServiceImpl implements TjfxAnalysisDataService { |
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(TjfxAnalysisDataServiceImpl.class); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
private TjfxAnalysisDataMapper tjfxAnalysisDataMapper; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 根据产品代码查询数据并进行聚合处理 |
|
|
|
|
|
* |
|
|
|
|
|
* @param tjfxAnalysisDataDTO 查询条件数据传输对象,包含查询所需的参数 |
|
|
|
|
|
* @return 返回聚合处理后的结果列表,每个元素是一个Map,键为String类型,值为Object类型 |
|
|
|
|
|
* @throws Exception 可能抛出的异常,如数据库查询异常等 |
|
|
|
|
|
*/ |
|
|
|
|
|
public List<Map<String, Object>> selectByProductCode(TjfxAnalysisDataDTO tjfxAnalysisDataDTO) throws Exception { |
|
|
|
|
|
// 调用数据访问层方法获取原始数据 |
|
|
|
|
|
List<Map<String, Object>> rows = tjfxAnalysisDataMapper.select(tjfxAnalysisDataDTO); |
|
|
|
|
|
// 对获取的数据进行聚合处理后返回 |
|
|
|
|
|
return aggregate(rows); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 根据条件查询分析数据并返回聚合后的结果 |
|
|
|
|
|
* |
|
|
|
|
|
* @param tjfxAnalysisDataDTO 查询条件的数据传输对象 |
|
|
|
|
|
* @return 返回一个Map,键为String类型,值为List<String>类型,表示查询结果 |
|
|
|
|
|
* @throws Exception 如果查询过程中发生异常,则抛出Exception |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public Map<String, List<String>> select(TjfxAnalysisDataDTO tjfxAnalysisDataDTO) throws Exception { |
|
|
|
|
|
// 调用数据访问层的select方法查询数据,返回结果为List<Map<String, Object>>类型 |
|
|
|
|
|
List<Map<String, Object>> rows = tjfxAnalysisDataMapper.select(tjfxAnalysisDataDTO); |
|
|
|
|
|
// 将查询结果聚合为Map<String, List<String>>类型并返回 |
|
|
|
|
|
return aggregateToMap(rows); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 查询月度数据 |
|
|
|
|
|
* |
|
|
|
|
|
* @param tjfxAnalysisDataDTO 查询条件参数对象 |
|
|
|
|
|
* @return 返回月度数据列表 |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public List<Map<String, Object>> selectMonth(TjfxAnalysisDataDTO tjfxAnalysisDataDTO) throws Exception { |
|
|
|
|
|
String productCode = tjfxAnalysisDataDTO.getProductCode(); |
|
|
|
|
|
List<String> productCodeList; |
|
|
|
|
|
if (productCode != null && !productCode.isEmpty()) { |
|
|
|
|
|
productCodeList = Arrays.stream(productCode.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
productCodeList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String brand = tjfxAnalysisDataDTO.getBrand(); |
|
|
|
|
|
List<String> brandList; |
|
|
|
|
|
if (brand != null && !brand.isEmpty()) { |
|
|
|
|
|
brandList = Arrays.stream(brand.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
brandList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String shopName = tjfxAnalysisDataDTO.getShopName(); |
|
|
|
|
|
List<String> shopNameList; |
|
|
|
|
|
if (shopName != null && !shopName.isEmpty()) { |
|
|
|
|
|
shopNameList = Arrays.stream(shopName.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
shopNameList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String customerName = tjfxAnalysisDataDTO.getCustomerName(); |
|
|
|
|
|
List<String> customerNameList; |
|
|
|
|
|
if (customerName != null && !customerName.isEmpty()) { |
|
|
|
|
|
customerNameList = Arrays.stream(customerName.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
customerNameList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String categorySpecs = tjfxAnalysisDataDTO.getCategorySpecs(); |
|
|
|
|
|
String sql = "1=1"; |
|
|
|
|
|
if (categorySpecs != null && !categorySpecs.trim().isEmpty()) { |
|
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper(); |
|
|
|
|
|
Map<String, List<String>> res = objectMapper.readValue(categorySpecs, new com.fasterxml.jackson.core.type.TypeReference<Map<String, List<String>>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
sql = res.entrySet().stream() |
|
|
|
|
|
.map(entry -> { |
|
|
|
|
|
String key = entry.getKey(); |
|
|
|
|
|
List<String> values = entry.getValue(); |
|
|
|
|
|
|
|
|
|
|
|
// 明确指定JSON路径格式。 |
|
|
|
|
|
|
|
|
|
|
|
String jsonPath = String.format("$.\"%s\"", key); |
|
|
|
|
|
|
|
|
|
|
|
// 生成条件时确保整体格式正确 |
|
|
|
|
|
String orConditions = values.stream() |
|
|
|
|
|
.map(value -> { |
|
|
|
|
|
String escapedValue = value.replace("'", "''"); |
|
|
|
|
|
// 注意这里JSON路径用单引号包裹,内部键名用双引号 |
|
|
|
|
|
return String.format("JSON_UNQUOTE(JSON_EXTRACT(category_specs, '%s')) = '%s'", jsonPath, escapedValue); |
|
|
|
|
|
}) |
|
|
|
|
|
.collect(Collectors.joining(" OR ")).trim(); |
|
|
|
|
|
|
|
|
|
|
|
return "(" + orConditions + ")"; |
|
|
|
|
|
}) |
|
|
|
|
|
.collect(Collectors.joining(" AND ")).trim(); |
|
|
|
|
|
} |
|
|
|
|
|
String startDate = tjfxAnalysisDataDTO.getStartDate(); |
|
|
|
|
|
String endDate = tjfxAnalysisDataDTO.getEndDate(); |
|
|
|
|
|
String category = tjfxAnalysisDataDTO.getCategory(); |
|
|
|
|
|
|
|
|
|
|
|
// 执行查询 |
|
|
|
|
|
List<Map<String, Object>> result = tjfxAnalysisDataMapper.selectMonth(sql, productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category); |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 根据商品、店铺和规格条件查询分析数据 |
|
|
|
|
|
* 返回包含客户数据、商品数据和规格数据的综合结果 |
|
|
|
|
|
* |
|
|
|
|
|
* @param tjfxAnalysisDataDTO 查询条件参数,包含商品、店铺和规格相关信息 |
|
|
|
|
|
* @return 返回包含客户数据、商品数据和规格数据的列表 |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public Map<String, Object> selectByProductAndShopAndSpecification(TjfxAnalysisDataDTO tjfxAnalysisDataDTO) throws Exception { |
|
|
|
|
|
// 获取商品编码并处理为列表形式 |
|
|
|
|
|
String productCode = tjfxAnalysisDataDTO.getProductCode(); |
|
|
|
|
|
List<String> productCodeList; |
|
|
|
|
|
if (productCode != null && !productCode.isEmpty()) { |
|
|
|
|
|
// 将逗号分隔的商品编码字符串转换为列表,并过滤空字符串 |
|
|
|
|
|
productCodeList = Arrays.stream(productCode.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
productCodeList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取品牌信息并处理为列表形式 |
|
|
|
|
|
String brand = tjfxAnalysisDataDTO.getBrand(); |
|
|
|
|
|
List<String> brandList; |
|
|
|
|
|
if (brand != null && !brand.isEmpty()) { |
|
|
|
|
|
// 将逗号分隔的品牌字符串转换为列表,并过滤空字符串 |
|
|
|
|
|
brandList = Arrays.stream(brand.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
brandList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取店铺名称并处理为列表形式 |
|
|
|
|
|
String shopName = tjfxAnalysisDataDTO.getShopName(); |
|
|
|
|
|
List<String> shopNameList; |
|
|
|
|
|
if (shopName != null && !shopName.isEmpty()) { |
|
|
|
|
|
// 将逗号分隔的店铺名称字符串转换为列表,并过滤空字符串 |
|
|
|
|
|
shopNameList = Arrays.stream(shopName.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
shopNameList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取客户名称并处理为列表形式 |
|
|
|
|
|
String customerName = tjfxAnalysisDataDTO.getCustomerName(); |
|
|
|
|
|
List<String> customerNameList; |
|
|
|
|
|
if (customerName != null && !customerName.isEmpty()) { |
|
|
|
|
|
// 将逗号分隔的客户名称字符串转换为列表,并过滤空字符串 |
|
|
|
|
|
customerNameList = Arrays.stream(customerName.split(",")) |
|
|
|
|
|
.filter(code -> !code.isEmpty()) // 过滤空字符串 |
|
|
|
|
|
.collect(Collectors.toList()); // 转换为可修改的ArrayList |
|
|
|
|
|
} else { |
|
|
|
|
|
customerNameList = new ArrayList<>(); // 空列表 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 处理规格条件,构建SQL查询条件 |
|
|
|
|
|
String categorySpecs = tjfxAnalysisDataDTO.getCategorySpecs(); |
|
|
|
|
|
String sql = "1=1"; // 默认条件,表示无限制 |
|
|
|
|
|
if (categorySpecs != null && !categorySpecs.trim().isEmpty()) { |
|
|
|
|
|
// 使用Jackson的ObjectMapper解析JSON格式的规格条件 |
|
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper(); |
|
|
|
|
|
Map<String, List<String>> res = objectMapper.readValue(categorySpecs, new com.fasterxml.jackson.core.type.TypeReference<Map<String, List<String>>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
// 将规格条件转换为SQL的JSON_EXTRACT查询条件 |
|
|
|
|
|
sql = res.entrySet().stream() |
|
|
|
|
|
.map(entry -> { |
|
|
|
|
|
String key = entry.getKey(); |
|
|
|
|
|
List<String> values = entry.getValue(); |
|
|
|
|
|
|
|
|
|
|
|
// 明确指定JSON路径格式,使用方括号语法可能兼容性更好 |
|
|
|
|
|
String jsonPath = String.format("$.\"%s\"", key); |
|
|
|
|
|
|
|
|
|
|
|
// 生成条件时确保整体格式正确 |
|
|
|
|
|
String orConditions = values.stream() |
|
|
|
|
|
.map(value -> { |
|
|
|
|
|
String escapedValue = value.replace("'", "''"); |
|
|
|
|
|
// 注意这里JSON路径用单引号包裹,内部键名用双引号 |
|
|
|
|
|
return String.format("JSON_UNQUOTE(JSON_EXTRACT(category_specs, '%s')) = '%s'", jsonPath, escapedValue); |
|
|
|
|
|
}) |
|
|
|
|
|
.collect(Collectors.joining(" OR ")).trim(); |
|
|
|
|
|
|
|
|
|
|
|
return "(" + orConditions + ")"; |
|
|
|
|
|
}) |
|
|
|
|
|
.collect(Collectors.joining(" AND ")).trim(); |
|
|
|
|
|
} |
|
|
|
|
|
// 获取查询的时间范围和分类信息 |
|
|
|
|
|
String startDate = tjfxAnalysisDataDTO.getStartDate(); |
|
|
|
|
|
String endDate = tjfxAnalysisDataDTO.getEndDate(); |
|
|
|
|
|
String category = tjfxAnalysisDataDTO.getCategory(); |
|
|
|
|
|
// 创建结果列表 |
|
|
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
|
|
// 查询按客户名称分组的数据 |
|
|
|
|
|
List<Map<String, Object>> customerData = tjfxAnalysisDataMapper.selectByCustomer(productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category, sql); |
|
|
|
|
|
// 查询按商品分组的数据 |
|
|
|
|
|
List<Map<String, Object>> productData = tjfxAnalysisDataMapper.selectByProduct(productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category, sql); |
|
|
|
|
|
// 查询按规格分组的数据 |
|
|
|
|
|
List<Map<String, Object>> rows = tjfxAnalysisDataMapper.selectBySpecification(productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category, sql); |
|
|
|
|
|
//查询店铺 |
|
|
|
|
|
List<Map<String, Object>> shop = tjfxAnalysisDataMapper.selectByShopName(productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category, sql); |
|
|
|
|
|
//查询按品牌分组的数据 |
|
|
|
|
|
List<Map<String, Object>> brands = tjfxAnalysisDataMapper.selectByBrand(productCodeList, brandList, shopNameList, customerNameList, startDate, endDate, category, sql); |
|
|
|
|
|
// 使用新的规格数据聚合方法处理数据 |
|
|
|
|
|
Map<String, Map<String, Map<String, Object>>> specificationData; |
|
|
|
|
|
try { |
|
|
|
|
|
specificationData = aggregateSpecificationData(rows); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("处理规格数据时发生错误", e); |
|
|
|
|
|
specificationData = new LinkedHashMap<>(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result.put("customer_name", customerData); |
|
|
|
|
|
result.put("product_code", productData); |
|
|
|
|
|
result.put("brand", brands); |
|
|
|
|
|
result.put("shop_name", shop); |
|
|
|
|
|
result.put("category_specs", specificationData); |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 将输入的Map列表聚合为包含所有唯一值的Map,每个键对应一个排序后的列表 |
|
|
|
|
|
* 主要用于获取所有可能的选项值,用于前端下拉框等场景 |
|
|
|
|
|
* |
|
|
|
|
|
* @param input 包含多个Map的列表,每个Map代表一条记录 |
|
|
|
|
|
* @return 包含所有唯一值的Map,键为字段名,值为排序后的唯一值列表 |
|
|
|
|
|
* @throws Exception 可能抛出异常 |
|
|
|
|
|
*/ |
|
|
|
|
|
public static Map<String, List<String>> aggregateToMap(List<Map<String, Object>> input) throws Exception { |
|
|
|
|
|
// 结果Map,包含所有需要的字段,使用LinkedHashMap保持插入顺序 |
|
|
|
|
|
Map<String, List<String>> result = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
// 初始化基础字段为空列表,确保这些字段始终存在于结果中 |
|
|
|
|
|
result.put("product_code", new ArrayList<>()); |
|
|
|
|
|
result.put("brand", new ArrayList<>()); |
|
|
|
|
|
result.put("customer_name", new ArrayList<>()); |
|
|
|
|
|
result.put("shop_name", new ArrayList<>()); |
|
|
|
|
|
|
|
|
|
|
|
// 处理空输入情况,直接返回初始化的结果Map |
|
|
|
|
|
if (input == null || input.isEmpty()) { |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 按product_code分组(保持插入顺序) |
|
|
|
|
|
Map<String, List<Map<String, Object>>> grouped = input.stream() |
|
|
|
|
|
.collect(Collectors.groupingBy(m -> nz((String) m.get("product_code")), LinkedHashMap::new, Collectors.toList())); |
|
|
|
|
|
|
|
|
|
|
|
// 用于去重的集合 |
|
|
|
|
|
LinkedHashSet<String> productCodes = new LinkedHashSet<>(); |
|
|
|
|
|
LinkedHashSet<String> brands = new LinkedHashSet<>(); |
|
|
|
|
|
LinkedHashSet<String> customerNames = new LinkedHashSet<>(); |
|
|
|
|
|
LinkedHashSet<String> shopNames = new LinkedHashSet<>(); |
|
|
|
|
|
|
|
|
|
|
|
// 分类规格键 -> 有序去重值集合(动态添加键) |
|
|
|
|
|
Map<String, LinkedHashSet<String>> categoryValues = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有分组数据 |
|
|
|
|
|
for (List<Map<String, Object>> records : grouped.values()) { |
|
|
|
|
|
for (Map<String, Object> record : records) { |
|
|
|
|
|
// 收集基础字段 |
|
|
|
|
|
productCodes.add(nz((String) record.get("product_code"))); |
|
|
|
|
|
brands.add(nz((String) record.get("brand"))); |
|
|
|
|
|
customerNames.add(nz((String) record.get("customer_name"))); |
|
|
|
|
|
shopNames.add(nz((String) record.get("shop_name"))); |
|
|
|
|
|
|
|
|
|
|
|
// 处理category_specs字段(动态处理所有键) |
|
|
|
|
|
String specs = (String) record.get("category_specs"); |
|
|
|
|
|
if (specs != null && !specs.trim().isEmpty()) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 使用与aggregate方法相同的方式解析JSON |
|
|
|
|
|
ObjectMapper mapper = new ObjectMapper(); |
|
|
|
|
|
Map<String, String> specsMap = mapper.readValue(specs, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
for (Map.Entry<String, String> entry : specsMap.entrySet()) { |
|
|
|
|
|
String key = entry.getKey(); |
|
|
|
|
|
String value = entry.getValue(); |
|
|
|
|
|
if (value != null && !value.trim().isEmpty()) { |
|
|
|
|
|
// 动态获取或创建键对应的集合 |
|
|
|
|
|
categoryValues.computeIfAbsent(key, k -> new LinkedHashSet<>()) |
|
|
|
|
|
.add(value.trim()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception ignore) { |
|
|
|
|
|
// 解析失败时忽略 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 排序并将去重后的集合转换为列表 |
|
|
|
|
|
List<String> sortedProductCodes = new ArrayList<>(productCodes); |
|
|
|
|
|
Collections.sort(sortedProductCodes); |
|
|
|
|
|
result.put("product_code", sortedProductCodes); |
|
|
|
|
|
|
|
|
|
|
|
List<String> sortedBrands = new ArrayList<>(brands); |
|
|
|
|
|
Collections.sort(sortedBrands); |
|
|
|
|
|
result.put("brand", sortedBrands); |
|
|
|
|
|
|
|
|
|
|
|
List<String> sortedCustomerNames = new ArrayList<>(customerNames); |
|
|
|
|
|
Collections.sort(sortedCustomerNames); |
|
|
|
|
|
result.put("customer_name", sortedCustomerNames); |
|
|
|
|
|
|
|
|
|
|
|
List<String> sortedShopNames = new ArrayList<>(shopNames); |
|
|
|
|
|
Collections.sort(sortedShopNames); |
|
|
|
|
|
result.put("shop_name", sortedShopNames); |
|
|
|
|
|
|
|
|
|
|
|
// 处理所有动态识别的分类规格字段 |
|
|
|
|
|
for (Map.Entry<String, LinkedHashSet<String>> entry : categoryValues.entrySet()) { |
|
|
|
|
|
List<String> sortedValues = new ArrayList<>(entry.getValue()); |
|
|
|
|
|
Collections.sort(sortedValues); |
|
|
|
|
|
result.put(entry.getKey(), sortedValues); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 处理null字符串,将null转为空字符串并trim |
|
|
|
|
|
* |
|
|
|
|
|
* @param s 待处理的字符串 |
|
|
|
|
|
* @return 处理后的字符串,null转为空字符串 |
|
|
|
|
|
*/ |
|
|
|
|
|
public static String nz(String s) { |
|
|
|
|
|
return s == null ? "" : s.trim(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 解析categorySpecs字符串,支持单个JSON对象或多个JSON对象拼接的情况 |
|
|
|
|
|
* |
|
|
|
|
|
* @param categorySpecs 规格字符串,可能是单个JSON或多个JSON拼接 |
|
|
|
|
|
* @param objectMapper Jackson ObjectMapper实例 |
|
|
|
|
|
* @return 合并后的规格Map |
|
|
|
|
|
* @throws Exception 解析异常 |
|
|
|
|
|
*/ |
|
|
|
|
|
private static Map<String, List<String>> parseCategorySpecs(String categorySpecs, ObjectMapper objectMapper) throws Exception { |
|
|
|
|
|
Map<String, List<String>> result = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
if (categorySpecs == null || categorySpecs.trim().isEmpty()) { |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String trimmed = categorySpecs.trim(); |
|
|
|
|
|
|
|
|
|
|
|
// 尝试直接解析为单个JSON对象 |
|
|
|
|
|
try { |
|
|
|
|
|
Map<String, List<String>> singleResult = objectMapper.readValue(trimmed, |
|
|
|
|
|
new com.fasterxml.jackson.core.type.TypeReference<Map<String, List<String>>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
return singleResult; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// 如果单个JSON解析失败,尝试解析多个JSON对象拼接的情况 |
|
|
|
|
|
log.debug("单个JSON解析失败,尝试解析多个JSON对象拼接: {}", e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 处理多个JSON对象拼接的情况 |
|
|
|
|
|
// 使用正则表达式分割JSON对象 |
|
|
|
|
|
// 匹配 { ... } 模式,但需要处理嵌套的大括号 |
|
|
|
|
|
List<String> jsonObjects = splitJsonObjects(trimmed); |
|
|
|
|
|
|
|
|
|
|
|
for (String jsonStr : jsonObjects) { |
|
|
|
|
|
if (jsonStr.trim().isEmpty()) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
Map<String, List<String>> jsonMap = objectMapper.readValue(jsonStr.trim(), |
|
|
|
|
|
new com.fasterxml.jackson.core.type.TypeReference<Map<String, List<String>>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 合并到结果中 |
|
|
|
|
|
for (Map.Entry<String, List<String>> entry : jsonMap.entrySet()) { |
|
|
|
|
|
String key = entry.getKey(); |
|
|
|
|
|
List<String> values = entry.getValue(); |
|
|
|
|
|
|
|
|
|
|
|
if (result.containsKey(key)) { |
|
|
|
|
|
// 如果键已存在,合并值列表并去重 |
|
|
|
|
|
Set<String> uniqueValues = new LinkedHashSet<>(result.get(key)); |
|
|
|
|
|
uniqueValues.addAll(values); |
|
|
|
|
|
result.put(key, new ArrayList<>(uniqueValues)); |
|
|
|
|
|
} else { |
|
|
|
|
|
result.put(key, new ArrayList<>(values)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.warn("解析JSON对象失败: {}", jsonStr, e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 分割多个JSON对象拼接的字符串 |
|
|
|
|
|
* |
|
|
|
|
|
* @param jsonString 包含多个JSON对象的字符串 |
|
|
|
|
|
* @return JSON对象列表 |
|
|
|
|
|
*/ |
|
|
|
|
|
private static List<String> splitJsonObjects(String jsonString) { |
|
|
|
|
|
List<String> result = new ArrayList<>(); |
|
|
|
|
|
if (jsonString == null || jsonString.trim().isEmpty()) { |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int braceCount = 0; |
|
|
|
|
|
int start = -1; |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < jsonString.length(); i++) { |
|
|
|
|
|
char c = jsonString.charAt(i); |
|
|
|
|
|
|
|
|
|
|
|
if (c == '{') { |
|
|
|
|
|
if (braceCount == 0) { |
|
|
|
|
|
start = i; // 记录JSON对象开始位置 |
|
|
|
|
|
} |
|
|
|
|
|
braceCount++; |
|
|
|
|
|
} else if (c == '}') { |
|
|
|
|
|
braceCount--; |
|
|
|
|
|
if (braceCount == 0 && start != -1) { |
|
|
|
|
|
// 找到一个完整的JSON对象 |
|
|
|
|
|
String jsonObj = jsonString.substring(start, i + 1); |
|
|
|
|
|
result.add(jsonObj); |
|
|
|
|
|
start = -1; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 按规格分组处理数据,返回按category_specs字段中所有key分组的Map结构 |
|
|
|
|
|
* 将相同规格的数据进行聚合,累加数量和金额,并按所有key进行分组展示 |
|
|
|
|
|
* |
|
|
|
|
|
* @param input 包含规格数据的列表,每个Map包含category_specs、totalQuantity、totalAmount |
|
|
|
|
|
* @return 返回按category_specs中所有key分组的Map,外层键为key名,内层键为key值,值为包含数量和金额的Map |
|
|
|
|
|
* @throws Exception 可能抛出异常 |
|
|
|
|
|
*/ |
|
|
|
|
|
public static Map<String, Map<String, Map<String, Object>>> aggregateSpecificationData(List<Map<String, Object>> input) throws Exception { |
|
|
|
|
|
// 外层Map:key名称 -> 内层Map:key值 -> {数量, 金额} |
|
|
|
|
|
Map<String, Map<String, Map<String, Object>>> result = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
// 处理空输入情况 |
|
|
|
|
|
if (input == null || input.isEmpty()) { |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (Map<String, Object> record : input) { |
|
|
|
|
|
String specs = (String) record.get("category_specs"); |
|
|
|
|
|
Object totalQuantity = record.get("totalQuantity"); |
|
|
|
|
|
Object totalAmount = record.get("totalAmount"); |
|
|
|
|
|
|
|
|
|
|
|
if (specs != null && !specs.trim().isEmpty()) { |
|
|
|
|
|
try { |
|
|
|
|
|
ObjectMapper mapper = new ObjectMapper(); |
|
|
|
|
|
Map<String, String> specsMap = mapper.readValue(specs, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 获取数量和金额值 |
|
|
|
|
|
Long quantity = totalQuantity != null ? ((Number) totalQuantity).longValue() : 0L; |
|
|
|
|
|
Double amount = totalAmount != null ? ((Number) totalAmount).doubleValue() : 0.0; |
|
|
|
|
|
|
|
|
|
|
|
// 遍历规格Map中的所有key进行分组 |
|
|
|
|
|
for (Map.Entry<String, String> entry : specsMap.entrySet()) { |
|
|
|
|
|
String keyName = entry.getKey(); |
|
|
|
|
|
String keyValue = entry.getValue() != null ? entry.getValue().trim() : ""; |
|
|
|
|
|
|
|
|
|
|
|
if (!keyValue.isEmpty()) { |
|
|
|
|
|
// 确保外层Map包含该keyName |
|
|
|
|
|
if (!result.containsKey(keyName)) { |
|
|
|
|
|
result.put(keyName, new LinkedHashMap<>()); |
|
|
|
|
|
} |
|
|
|
|
|
Map<String, Map<String, Object>> keyGroups = result.get(keyName); |
|
|
|
|
|
|
|
|
|
|
|
// 确保内层Map包含该keyValue |
|
|
|
|
|
if (!keyGroups.containsKey(keyValue)) { |
|
|
|
|
|
Map<String, Object> dataMap = new LinkedHashMap<>(); |
|
|
|
|
|
dataMap.put("totalQuantity", 0L); |
|
|
|
|
|
dataMap.put("totalAmount", 0.0); |
|
|
|
|
|
keyGroups.put(keyValue, dataMap); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 更新数量和金额 |
|
|
|
|
|
Map<String, Object> dataMap = keyGroups.get(keyValue); |
|
|
|
|
|
Long currentQuantity = (Long) dataMap.get("totalQuantity"); |
|
|
|
|
|
Double currentAmount = (Double) dataMap.get("totalAmount"); |
|
|
|
|
|
dataMap.put("totalQuantity", currentQuantity + quantity); |
|
|
|
|
|
dataMap.put("totalAmount", currentAmount + amount); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.warn("解析规格数据失败: " + specs, e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 对每个key下的value进行排序 |
|
|
|
|
|
for (Map.Entry<String, Map<String, Map<String, Object>>> entry : result.entrySet()) { |
|
|
|
|
|
Map<String, Map<String, Object>> sortedKeyValues = new LinkedHashMap<>(); |
|
|
|
|
|
List<String> sortedKeys = new ArrayList<>(entry.getValue().keySet()); |
|
|
|
|
|
Collections.sort(sortedKeys); |
|
|
|
|
|
|
|
|
|
|
|
for (String key : sortedKeys) { |
|
|
|
|
|
sortedKeyValues.put(key, entry.getValue().get(key)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
result.put(entry.getKey(), sortedKeyValues); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 对输入的List<Map<String, Object>>类型数据进行聚合处理 |
|
|
|
|
|
* 按产品代码分组,将相同产品的数据进行聚合,收集品牌、店铺、客户和规格信息 |
|
|
|
|
|
* |
|
|
|
|
|
* @param input 包含Map<String, Object>的列表,作为聚合处理的输入数据 |
|
|
|
|
|
* @return 返回聚合处理后的List<Map < String, Object>>类型数据 |
|
|
|
|
|
* @throws Exception 可能在处理过程中抛出异常 |
|
|
|
|
|
*/ |
|
|
|
|
|
public static List<Map<String, Object>> aggregate(List<Map<String, Object>> input) throws Exception { |
|
|
|
|
|
// 按product_code 分组(保持插入顺序) |
|
|
|
|
|
Map<String, List<Map<String, Object>>> grouped = input.stream() |
|
|
|
|
|
.collect(Collectors.groupingBy(m -> (String) m.get("product_code"), LinkedHashMap::new, Collectors.toList())); |
|
|
|
|
|
|
|
|
|
|
|
List<Map<String, Object>> result = new ArrayList<>(); |
|
|
|
|
|
|
|
|
|
|
|
for (Map.Entry<String, List<Map<String, Object>>> entry : grouped.entrySet()) { |
|
|
|
|
|
String product_code = entry.getKey(); |
|
|
|
|
|
List<Map<String, Object>> records = entry.getValue(); |
|
|
|
|
|
|
|
|
|
|
|
Map<String, Object> aggregated = new LinkedHashMap<>(); |
|
|
|
|
|
aggregated.put("product_code", product_code); |
|
|
|
|
|
|
|
|
|
|
|
// 品牌名称集合 |
|
|
|
|
|
LinkedHashSet<String> brandNames = new LinkedHashSet<>(); |
|
|
|
|
|
// 店铺名称集合 |
|
|
|
|
|
LinkedHashSet<String> shopNames = new LinkedHashSet<>(); |
|
|
|
|
|
// 客户名称集合 |
|
|
|
|
|
LinkedHashSet<String> customerNames = new LinkedHashSet<>(); |
|
|
|
|
|
// 规格信息集合 |
|
|
|
|
|
Map<String, Set<String>> categorySet = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
for (Map<String, Object> record : records) { |
|
|
|
|
|
// 收集基础信息 |
|
|
|
|
|
brandNames.add((String) record.get("brand")); |
|
|
|
|
|
shopNames.add((String) record.get("shop_name")); |
|
|
|
|
|
customerNames.add((String) record.get("customer_name")); |
|
|
|
|
|
|
|
|
|
|
|
// 处理规格信息 |
|
|
|
|
|
String specs = (String) record.get("category_specs"); |
|
|
|
|
|
ObjectMapper mapper = new ObjectMapper(); |
|
|
|
|
|
Map<String, String> specsMap = mapper.readValue(specs, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() { |
|
|
|
|
|
}); |
|
|
|
|
|
for (Map.Entry<String, String> courseEntry : specsMap.entrySet()) { |
|
|
|
|
|
categorySet.computeIfAbsent(courseEntry.getKey(), k -> new HashSet<>()) |
|
|
|
|
|
.add(courseEntry.getValue()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 排序并转回 List<String> |
|
|
|
|
|
Map<String, List<String>> categorySets = new LinkedHashMap<>(); |
|
|
|
|
|
for (Map.Entry<String, Set<String>> courseEntry : categorySet.entrySet()) { |
|
|
|
|
|
List<String> sortedScores = new ArrayList<>(courseEntry.getValue()); |
|
|
|
|
|
Collections.sort(sortedScores); // 升序 |
|
|
|
|
|
List<String> scoresStr = sortedScores.stream() |
|
|
|
|
|
.map(String::valueOf) |
|
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
categorySets.put(courseEntry.getKey(), scoresStr); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 组装聚合结果 |
|
|
|
|
|
aggregated.put("brand", new ArrayList<>(brandNames)); |
|
|
|
|
|
aggregated.put("shop_name", new ArrayList<>(shopNames)); |
|
|
|
|
|
aggregated.put("customer_name", new ArrayList<>(customerNames)); |
|
|
|
|
|
aggregated.putAll(categorySets); |
|
|
|
|
|
|
|
|
|
|
|
result.add(aggregated); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
} |