数据贯通代码提交

This commit is contained in:
llllon 2025-08-23 12:19:37 +08:00
parent 7673f62e0f
commit 010fe19aca
28 changed files with 4540 additions and 0 deletions

View File

@ -0,0 +1,34 @@
plugins {
id 'java'
}
group 'com.actionsoft'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
//,bin/patch目录下的jar
def patchlib = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\patch', includes: ['**\\*.jar'])
//,bin/lib目录
def binLib = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\lib', includes: ['**\\*.jar'])
//bin/jdbc目录inceptor-driver-8.16.0.jarcommon-io jar冲突的类导致编译报错
def jdbc = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\jdbc', includes: ['**\\*.jar']).exclude('inceptor-driver-8.16.0.jar')
def lib2 = fileTree(dir: 'D:\\Actionsoft\\aws7\\apps\\install\\com.awspaas.user.apps.bnbm.datalinkup\\lib', includes: ['**\\*.jar'])
implementation lib2
implementation patchlib
implementation binLib
implementation jdbc
//lib jar包lib(%AWS_HOME%/apps/install/APPID/lib)
def lib = fileTree(dir: project.projectDir.toString() + '\\lib', includes: ['**\\*.jar'])
implementation lib
}
test {
useJUnitPlatform()
}

View File

@ -0,0 +1,384 @@
package com.awspaas.user.apps.bnbm.datalinkup.controller;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.commons.mvc.view.ResponseObject;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.server.bind.annotation.Controller;
import com.actionsoft.bpms.server.bind.annotation.Mapping;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.*;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @ClassName: DataLinkUpController
* @Description:
* @author: 李春洋
* @date: 2025/8/8 10:38
* @Blog: https://
*/
@Controller
public class DataLinkUpController {
private static final Logger LOGGER = LoggerFactory.getLogger(DataLinkUpController.class);
/**
* 配置字段自动配置
* @param sid
* @param toTable
* @param bindid
* @param byTable
* @return
*/
@Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_createTableFiled")
public ResponseObject createTableFiled(String sid, String toTable, String bindid, String byTable) {
ResponseObject ro = ResponseObject.newOkResponse();
LOGGER.info("请求参数sid:{},toTable:{},bindid:{},byTable{}", sid, toTable, bindid,byTable);
// List<RowMap> maps = DBSql.getMaps("SHOW COLUMNS FROM " + toTable + " FROM aws7");
List<RowMap> maps = DBSql.getMaps("SELECT COLUMN_NAME ,COLUMN_COMMENT \n" +
"FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'aws7' \n" +
"AND TABLE_NAME = '"+byTable+"'");
if (maps == null) {
ro.warn("表中没有字段");
return ro;
}
ArrayList<BO> bos = new ArrayList<>();
int i = 1;
for (RowMap map : maps) {
if (map.getString("COLUMN_NAME").equals("ID") || map.getString("COLUMN_NAME").equals("ORGID")
|| map.getString("COLUMN_NAME").equals("BINDID")
|| map.getString("COLUMN_NAME").equals("CREATEDATE") || map.getString("COLUMN_NAME").equals("CREATEUSER")
|| map.getString("COLUMN_NAME").equals("UPDATEDATE")
|| map.getString("COLUMN_NAME").equals("UPDATEUSER") || map.getString("COLUMN_NAME").equals("PROCESSDEFID")
|| map.getString("COLUMN_NAME").equals("ISEND")) {
continue;
} else {
BO bo = new BO();
bo.set("XH", i++);
bo.set("TBB", toTable);
bo.set("TBBZD", map.getString("COLUMN_NAME"));
bo.set("LDB", byTable);
bo.set("LDBZD", map.getString("COLUMN_NAME"));
bo.set("LDBZDM", map.getString("COLUMN_COMMENT"));
bos.add(bo);
}
}
ProcessInstance instanceById = SDK.getProcessAPI().getInstanceById(bindid);
SDK.getBOAPI().create("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB", bos, instanceById, UserContext.fromSessionId(sid));
ro.ok("创建子表数据完成");
return ro;
}
/**
* 手动同步数据按钮使用DataSyncService实现
*
* @param dataStr 同步数据表配置(JSON数组)
* @param sid 用户会话ID
* @param formattedDate 同步时间此实现中不使用该参数由服务内部处理时间范围
* @return 响应结果
*/
@Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_manualSynchronization")
public ResponseObject manualSynchronization(String dataStr, String sid, String formattedDate) {
long methodStartTime = System.currentTimeMillis();
LOGGER.info("【开始】数据同步操作,开始时间:{}", new Date(methodStartTime));
ResponseObject ro = ResponseObject.newOkResponse();
try {
// 1. 解析JSON数据
JSONArray configArray = new JSONArray(dataStr);
List<BO> mainConfigs = new ArrayList<>();
// 3. 创建数据同步服务实例
DataSyncService syncService = null;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
Date endDate = cal.getTime();
// 解析formattedDate为日期对象并计算时间范围
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(formattedDate, dateFormatter);
LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00
Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant());
// 2. 转换为BO对象列表
for (int i = 0; i < configArray.length(); i++) {
JSONObject config = configArray.getJSONObject(i);
String ssyw = config.getString("SSYW");
if ("销售".equals(ssyw)) {
syncService = new SaleDataSyncServiceImpl();
LOGGER.info("销售的接口");
}else {
syncService = new PurchaseDataSyncServiceImpl();
LOGGER.info("采购的接口");
}
String timeField = config.getString("SJZD");
String ccId = config.getString("CC_ID");
String targetTable = config.getString("LDB");
String partitionField = config.getString("FQBZD");
String tableName = config.getString("TBB");
String bkgs = config.getString("SSBK");
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", config.optString("_BINDID"))
.list();
// 删除目标表数据根据时间字段是否为空决定删除范围
if (timeField == null || timeField.isEmpty()) {
// 全量删除
syncService.deleteAllTargetData(targetTable);
} else {
// 获取目标表时间字段名
String targetTimeField = syncService.getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
return ro.warn("无法找到源时间字段["+timeField+"]对应的目标表字段,跳过同步");
}
// 按时间范围删除
syncService.deleteTargetData(targetTable, targetTimeField, startDate, endDate);
}
syncService.querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField,
fieldMappings, targetTable);
}
LOGGER.info("开始使用DataSyncService处理数据同步 ({}条配置)", mainConfigs.size());
ro.put("success", true);
ro.put("message", "数据同步操作已完成");
LOGGER.info("数据同步成功完成");
} catch (Exception e) {
LOGGER.error("手动同步异常", e);
ro.put("success", false);
ro.put("message", "数据同步失败: " + e.getMessage());
}
long methodEndTime = System.currentTimeMillis();
LOGGER.info("【完成】数据同步操作,总耗时:{}ms", methodEndTime - methodStartTime);
return ro;
}
/**
* 执行数据汇总计算使用DataSummaryService实现
* @return 响应结果
*/
@Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_calculateSummary")
public ResponseObject calculateSummary(String dataStr, String sid, String formattedDate) {
long methodStartTime = System.currentTimeMillis();
LOGGER.info("【开始】数据计算汇总操作,开始时间:{}", new Date(methodStartTime));
ResponseObject ro = ResponseObject.newOkResponse();
JSONArray configArray = new JSONArray(dataStr);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
Date endDate = cal.getTime();
// 解析formattedDate为日期对象并计算时间范围
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(formattedDate, dateFormatter);
LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00
Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant());
DataSummaryService summaryService = null;
SaleCountDimensionImpl saleCountDimension = null;
try {
LOGGER.info("开始执行销售数据多维度汇总计算");
DateRange dateRange = new DateRange();
dateRange.setStartDate(startDate);
dateRange.setEndDate(endDate);
LOGGER.info("汇总计算开始时间为:{},结束时间为:{}",startDate,endDate);
// 2. 执行汇总计算
for (int i = 0; i < configArray.length(); i++) {
JSONObject config = configArray.getJSONObject(i);
String timeField = config.getString("SJZD");
String ccId = config.getString("CC_ID");
String targetTable = config.getString("LDB");
String partitionField = config.getString("FQBZD");
String tableName = config.getString("TBB");
String bkgs = config.getString("SSBK");
String ssyw = config.getString("SSYW");
// 1. 创建数据汇总服务实例
if ("销售".equals(ssyw)) {
summaryService = new SaleDataSummaryServiceImpl();
saleCountDimension = new SaleCountDimensionImpl();
LOGGER.info("销售销售的接口");
}else {
summaryService = new PurchaseDataSummaryServiceImpl();
LOGGER.info("采购销售的接口");
}
List<RowMap> bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS");
if (bkgsMaps!=null) {
for (RowMap map : bkgsMaps) {
BO bo = new BO();
bo.set("BKGS", map.getString("BKGS"));
summaryService.calculateSummary(dateRange, bo);
if (saleCountDimension!=null){
//计算销售的维度
LOGGER.info("======== 开始执行销售数据汇总计算 ========");
saleCountDimension.calculateSummary(dateRange, bo);
LOGGER.info("======== 销售数据汇总计算完成 ========");
}
}
}
}
ro.put("success", true);
ro.put("message", "数据汇总计算完成");
LOGGER.info("销售数据多维度汇总计算完成");
} catch (Exception e) {
String errorMsg = "数据汇总计算失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
ro.put("success", false);
ro.put("message", errorMsg);
}
long methodEndTime = System.currentTimeMillis();
LOGGER.info("【完成】数据计算汇总操作,总耗时:{}ms", methodEndTime - methodStartTime);
return ro;
}
/**
* 各板块数据汇总
* @param dataStr 同步数据表配置(JSON数组)
* @param sid 用户会话ID
* @param formattedDate 同步时间此实现中不使用该参数由服务内部处理时间范围
* @return 响应结果
*/
@Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_summaryData")
public ResponseObject summaryData(String dataStr, String sid, String formattedDate){
long methodStartTime = System.currentTimeMillis();
LOGGER.info("【开始】数据汇总操作,开始时间:{}", new Date(methodStartTime));
ResponseObject ro = ResponseObject.newOkResponse();
DataSyncService dataSyncService = null;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
Date endDate = cal.getTime();
// 解析formattedDate为日期对象并计算时间范围
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(formattedDate, dateFormatter);
LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00
Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant());
LOGGER.info("时间范围:{}-{}",startDate,endDate);
JSONArray configArray = new JSONArray(dataStr);
// 2. 转换为BO对象列表
for (int i = 0; i < configArray.length(); i++) {
long configStartTime = System.currentTimeMillis();
JSONObject mainConfig = configArray.getJSONObject(i);
String targetTable = mainConfig.getString("LDB");//落地表
String timeField = mainConfig.getString("SJZD");//时间字段
String bindId = mainConfig.getString("_BINDID");//bindid
String plate = mainConfig.getString("SSBK");// 所属板块
String tablename = mainConfig.getString("TABLENAME");//表名
String ssyw = mainConfig.getString("SSYW");
String hzb = "";//汇总表
LOGGER.info("【开始处理】配置项[{}],板块:{}BindID{},开始时间:{}",
i+1, plate, bindId, new Date(configStartTime));
if ("销售".equals(ssyw)) {
dataSyncService = new SaleDataSyncServiceImpl();
if ("销售表".equals(tablename)){
hzb = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ";
}else {
hzb = "BO_EU_BNBM_DATALINKUP_XS_YSL";
}
LOGGER.info("销售销售的接口");
}else {
dataSyncService = new PurchaseDataSyncServiceImpl();
if ("采购单".equals(tablename)){
hzb = "BO_EU_DWD_ORDER_CGDD_HZ";
} else if ("应付单".equals(tablename)){
hzb = "BO_EU_DWD_ORDER_YFD_HZ";
} else if ("库存".equals(tablename)) {
hzb = "BO_EU_DWD_ORDER_KC_HZ";
} else {
hzb = "BO_EU_DWD_ORDER_RKD_HZ";
}
LOGGER.info("采购销售的接口");
}
LOGGER.info("即将同步的数据:{}",plate);
try {
// 查询子表字段映射配置
long queryStartTime = System.currentTimeMillis();
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", bindId)
.list();
//获取板块公司
String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS");
LOGGER.info("字段映射配置查询完成,耗时:{}ms", System.currentTimeMillis() - queryStartTime);
// 删除目标表数据根据时间字段是否为空决定删除范围
long deleteStartTime = System.currentTimeMillis();
if (timeField == null || timeField.isEmpty()) {
// 全量删除
String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'";
int deletedCount = DBSql.update(deleteSql);
LOGGER.info("已删除目标表["+hzb+"]中{}条数据(时间范围: {} - {}),耗时:{}ms",
deletedCount, startDate, endDate, System.currentTimeMillis() - deleteStartTime);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 全量分页迁移数据到汇总表
long summarizeStartTime = System.currentTimeMillis();
dataSyncService.summarizeScopeData(targetTable, null, null, null, hzb);
LOGGER.info("全量数据汇总完成,耗时:{}ms", System.currentTimeMillis() - summarizeStartTime);
} else {
// 获取目标表时间字段名
String targetTimeField = dataSyncService.getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
}
// 按时间范围删除
String deleteSql = "DELETE FROM " + hzb +
" WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?";
int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate});
LOGGER.info("已删除目标表["+hzb+"]中{}条数据(时间范围: {} - {})",
deletedCount, startDate, endDate);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 按时间范围分页迁移数据到汇总表
long summarizeStartTime = System.currentTimeMillis();
dataSyncService.summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb);
LOGGER.info("范围数据汇总完成,耗时:{}ms", System.currentTimeMillis() - summarizeStartTime);
}
LOGGER.info("【完成处理】配置项[{}],板块:{},总耗时:{}ms",
i+1, plate, System.currentTimeMillis() - configStartTime);
} catch (Exception e) {
LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}",
plate, mainConfig.getString("_BINDID"), e.getMessage(), e);
return ro.err("处理配置失败 [板块='"+plate+"', BindID='"+mainConfig.getString("BINDID")+"']: '"+e.getMessage()+"'");
}
}
long methodEndTime = System.currentTimeMillis();
LOGGER.info("【完成】数据汇总操作,总耗时:{}ms", methodEndTime - methodStartTime);
return ro.ok("汇总数据完成");
}
}

View File

@ -0,0 +1,47 @@
package com.awspaas.user.apps.bnbm.datalinkup.entity;
import java.util.Date;
/**
* @ClassName: DateRange
* @Description:
* @author: 李春洋
* @date: 2025/8/12 13:42
* @Blog: https://
*/
public class DateRange {
private Date startDate;
private Date endDate;
public DateRange() {
}
public DateRange(Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
@Override
public String toString() {
return "DateRange{" +
"startDate=" + startDate +
", endDate=" + endDate +
'}';
}
}

View File

@ -0,0 +1,17 @@
package com.awspaas.user.apps.bnbm.datalinkup.entity;
/**
* 经纬度
*/
public class Location {
private final String longitude;
private final String latitude;
public Location(String longitude, String latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
public String getLongitude() { return longitude; }
public String getLatitude() { return latitude; }
}

View File

@ -0,0 +1,73 @@
package com.awspaas.user.apps.bnbm.datalinkup.job;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.schedule.IJob;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.PurchaseDataSummaryServiceImpl;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.PurchaseDataSyncServiceImpl;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @ClassName: PurchaseDataLinkUpJob
* @Description: 采购数据同步定时任务
*/
public class PurchaseDataLinkUpJob implements IJob {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseDataLinkUpJob.class);
@Override
public void execute(JobExecutionContext job) throws JobExecutionException {
//时间范围数值
ArrayList<DateRange> list = new ArrayList<>();
PurchaseDataSyncServiceImpl syncService = new PurchaseDataSyncServiceImpl();
PurchaseDataSummaryServiceImpl purchaseDataSummaryService = new PurchaseDataSummaryServiceImpl();
LOGGER.info("======== 开始执行销售数据同步任务 ========");
// 查询销售业务的主配置
List<BO> mainConfigs = SDK.getBOAPI().query("BO_EU_BNBM_DATALINKUP_SJGTPZ")
.addQuery("SSYW =", "采购")
.list();
// 使用服务层处理同步逻辑
ArrayList<DateRange> dateRanges = syncService.syncDataByConfigs(mainConfigs);
list.addAll(dateRanges);
LOGGER.info("======== 完成销售数据同步任务 ========");
// 汇总各板块数据
LOGGER.info("======== 开始执行销售汇总各板块数据 ========");
syncService.sumBkTable(mainConfigs);
LOGGER.info("======== 销售汇总各板块数据执行完成 ========");
//计算汇总维度
Set<DateRange> collect = list.stream().filter(o -> o.getStartDate() != null || o.getEndDate() != null)
.collect(Collectors.toSet());
DateRange dateRange = new DateRange();
for (DateRange range : collect) {
if (range!=null || range.getStartDate()==null || range.getEndDate()==null) {
dateRange.setStartDate(range.getStartDate());
dateRange.setEndDate(range.getEndDate());
}
}
LOGGER.info("采购数据汇总计算开始时间为:{},结束时间为:{}",dateRange.getStartDate(),dateRange.getEndDate());
for (BO mainConfig : mainConfigs) {
String targetTable = mainConfig.getString("LDB");
List<RowMap> bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS");
if (bkgsMaps!=null) {
for (RowMap map : bkgsMaps) {
BO bo = new BO();
bo.set("BKGS", map.getString("BKGS"));
purchaseDataSummaryService.calculateSummary(dateRange, bo);
}
}
}
LOGGER.info("======== 销售数据同步任务执行完成 ========");
}
}

View File

@ -0,0 +1,108 @@
package com.awspaas.user.apps.bnbm.datalinkup.job;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.schedule.IJob;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleCountDimensionImpl;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleDataSummaryServiceImpl;
import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleDataSyncServiceImpl;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @ClassName: SaleDataLinkUpJob
* @Description: 销售数据同步定时任务
*/
public class SaleDataLinkUpJob implements IJob {
private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataLinkUpJob.class);
/**
* 定时任务执行入口
* @param context 任务执行上下文
* @throws JobExecutionException 任务执行异常
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//时间范围数值
ArrayList<DateRange> list = new ArrayList<>();
SaleDataSyncServiceImpl syncService = new SaleDataSyncServiceImpl();
SaleDataSummaryServiceImpl summaryService = new SaleDataSummaryServiceImpl();
SaleCountDimensionImpl saleCountDimension = new SaleCountDimensionImpl();
try {
LOGGER.info("======== 开始执行销售数据同步任务 ========");
// 查询销售业务的主配置
List<BO> mainConfigs = SDK.getBOAPI().query("BO_EU_BNBM_DATALINKUP_SJGTPZ")
.addQuery("SSYW =", "销售")
.list();
// 使用服务层处理同步逻辑
ArrayList<DateRange> dateRanges = syncService.syncDataByConfigs(mainConfigs);
list.addAll(dateRanges);
LOGGER.info("======== 完成销售数据同步任务 ========");
// 汇总各板块数据
LOGGER.info("======== 开始执行销售汇总各板块数据 ========");
syncService.sumBkTable(mainConfigs);
LOGGER.info("======== 销售汇总各板块数据执行完成 ========");
// 数据同步完成后执行汇总计算
LOGGER.info("======== 开始执行一体化-销售数据汇总计算 ========");
//获取汇总计算时间
Set<DateRange> collect = list.stream().filter(o -> o.getStartDate() != null || o.getEndDate() != null)
.collect(Collectors.toSet());
DateRange dateRange = new DateRange();
for (DateRange range : collect) {
if (range!=null || range.getStartDate()==null || range.getEndDate()==null) {
dateRange.setStartDate(range.getStartDate());
dateRange.setEndDate(range.getEndDate());
}
}
LOGGER.info("汇总计算开始时间为:{},结束时间为:{}",dateRange.getStartDate(),dateRange.getEndDate());
for (BO mainConfig : mainConfigs) {
String targetTable = mainConfig.getString("LDB");
List<RowMap> bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS");
if (bkgsMaps!=null) {
for (RowMap map : bkgsMaps) {
BO bo = new BO();
bo.set("BKGS", map.getString("BKGS"));
summaryService.calculateSummary(dateRange, bo);
}
}
}
LOGGER.info("======== 一体化-销售数据汇总计算完成 ========");
LOGGER.info("======== 开始执行销售数据汇总计算 ========");
for (BO mainConfig : mainConfigs) {
String targetTable = mainConfig.getString("LDB");
List<RowMap> bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS");
if (bkgsMaps!=null) {
for (RowMap map : bkgsMaps) {
BO bo = new BO();
bo.set("BKGS", map.getString("BKGS"));
saleCountDimension.calculateSummary(dateRange, bo);
}
}
}
LOGGER.info("======== 销售数据汇总计算完成 ========");
LOGGER.info("======== 销售数据同步任务执行完成 ========");
} catch (Exception e) {
String errorMsg = "销售数据同步任务执行失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
throw new JobExecutionException(errorMsg, e);
}
}
}

View File

@ -0,0 +1,15 @@
package com.awspaas.user.apps.bnbm.datalinkup.service;
import com.actionsoft.bpms.bo.engine.BO;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
/**
* @ClassName: DataSummaryService
* @Description: 数据汇总服务接口定义数据汇总的核心操作
*/
public interface DataSummaryService {
/**
* 执行数据汇总计算
*/
void calculateSummary(DateRange dateRange, BO mainConfig);
}

View File

@ -0,0 +1,103 @@
package com.awspaas.user.apps.bnbm.datalinkup.service;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @ClassName: DataSyncService
* @Description: 数据同步服务接口定义数据同步的核心操作
*/
public interface DataSyncService {
/**
* 根据主配置列表执行数据同步
* @param configs 主配置列表BO_EU_BNBM_DATALINKUP_SJGTPZ表记录
*/
ArrayList<DateRange> syncDataByConfigs(List<BO> configs);
/**
* 处理单个主配置的数据同步
* @param mainConfig 主配置对象BO_EU_BNBM_DATALINKUP_SJGTPZ表单条记录
*/
DateRange processMainConfig(BO mainConfig);
/**
* 获取目标表时间字段名
* @param fieldMappings 字段映射配置列表
* @param timeField 源表时间字段名
* @return 目标表时间字段名未找到返回null
*/
String getTargetTimeField(List<BO> fieldMappings, String timeField);
/**
* 删除目标表中指定时间范围的数据
* @param targetTable 目标表名
* @param targetTimeField 目标表时间字段名
* @param startDate 开始时间
* @param endDate 结束时间
* @throws RuntimeException 删除失败时抛出
*/
void deleteTargetData(String targetTable, String targetTimeField, Date startDate, Date endDate);
void deleteAllTargetData(String targetTable);
/**
* 高斯数据库专用查询方法支持分区和分页
* @param tableName
* @param timeField
* @param startDate
* @param endDate
* @param partitionField
* @param fieldMappings
* @param targetTable
*/
// void queryGaussDataWithCondition(String tableName, String timeField, Date startDate, Date endDate, String partitionField, List<BO> fieldMappings, String targetTable);
/**
* 跨库查询源表数据
* @param ccId 跨库连接ID
* @param tableName 源表名
* @param timeField 源表时间字段名
* @param startDate 开始时间
* @param endDate 结束时间
* @return 查询结果数据集
* @throws RuntimeException 查询失败或参数无效时抛出
*/
void querySourceData(String ccId, String tableName, String timeField, Date startDate, Date endDate, String partitionField, List<BO> fieldMappings, String targetTable);
/**
* 各板块数据汇总
* @param configs
*/
void sumBkTable(List<BO> configs);
/**
* 将范围内数据汇总
* @param targetTable
* @param startDate
* @param endDate
* @param targetTimeField
*/
void summarizeScopeData(String targetTable, Date startDate, Date endDate, String targetTimeField, String hzb);
/**
* 处理并插入数据到目标表
* @param sourceData 源数据列表
* @param mappings 字段映射配置
* @param targetTable 目标表名
*/
int processAndInsertData(List<RowMap> sourceData, List<BO> mappings, String targetTable);
/**
* 字段映射转换
* @param source 源数据记录
* @param mappings 字段映射配置
* @return 转换后的BO对象
*/
BO convertFields(RowMap source, List<BO> mappings);
}

View File

@ -0,0 +1,798 @@
package com.awspaas.user.apps.bnbm.datalinkup.service.impl;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* 采购汇总计算实现类
*/
public class PurchaseDataSummaryServiceImpl implements DataSummaryService {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseDataSummaryServiceImpl.class);
// 维度表名常量
/**
* 采购_年月采购明细
*/
private static final String PROCUREMENT_DETAILS_YEAR_MONTH = "BO_EU_CG_NYCGMX";
/**
* 采购_年月日采购明细
*/
private static final String PROCUREMENT_DETAILS_YEAR_MONTH_DAY = "BO_EU_CG_NYRCGMX";
/**
* 采购_基地_年月采购明细
*/
private static final String JD_PROCUREMENT_DETAILS_YEAR_MONTH = "BO_EU_CG_JD_NYCGMX";
/**
*
*/
private static final String BO_EU_CG_NYRKMX = "BO_EU_CG_NYRKMX";
// 原始数据表名
/**
* 采购_采购单_汇总
*/
private static final String PROCUREMENT_PURCHASE_ORDER_SUMMARY = "BO_EU_DWD_ORDER_CGDD_HZ";
/**
*
*/
private static final String BO_EU_DWD_ORDER_RKD_HZ = "BO_EU_DWD_ORDER_RKD_HZ";
// 缓存物料分类列表
private List<String> distinctMaterialList = null;
private long lastMaterialListUpdateTime = 0;
private static final long MATERIAL_LIST_CACHE_TIME = 30 * 60 * 1000; // 30分钟缓存
@Override
public void calculateSummary(DateRange dateRange, BO mainConfig) {
try {
// 从主配置获取BKGS值
String bkgs = mainConfig.getString("BKGS");
if (bkgs == null || bkgs.isEmpty()) {
LOGGER.error("主配置中BKGS为空无法进行汇总计算");
return;
}
// 获取物料分类列表带缓存
List<String> distinctList = getDistinctMaterialList();
if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) {
LOGGER.info("未提供有效时间范围,按当前日期计算");
calculateForCurrentDate(bkgs, distinctList);
} else {
LOGGER.info("开始执行采购数据多维度汇总计算(时间范围: {} 至 {})",
dateRange.getStartDate(), dateRange.getEndDate());
// 计算月度维度数据按月遍历
calculateMonthlyData(dateRange, bkgs, distinctList);
// 计算日度维度数据按天遍历
calculateDailyData(dateRange, bkgs, distinctList);
LOGGER.info("采购数据多维度汇总计算完成");
}
} catch (Exception e) {
String errorMsg = "采购数据汇总计算失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 获取物料分类列表带缓存
*/
private List<String> getDistinctMaterialList() {
long currentTime = System.currentTimeMillis();
if (distinctMaterialList != null && (currentTime - lastMaterialListUpdateTime) < MATERIAL_LIST_CACHE_TIME) {
return distinctMaterialList;
}
try {
List<RowMap> maps = DBSql.getMaps("SELECT WLMC FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + " GROUP BY WLMC");
Set<String> resultSet = new HashSet<>();
for (RowMap row : maps) {
String wlmc = row.getString("WLMC");
if (StringUtils.isBlank(wlmc)) continue;
if (wlmc.contains("|")) {
String[] parts = wlmc.split("\\|");
if (parts.length > 0 && StringUtils.isNotBlank(parts[0])) {
resultSet.add(parts[0].trim());
}
} else {
resultSet.add(wlmc.trim());
}
}
distinctMaterialList = new ArrayList<>(resultSet);
lastMaterialListUpdateTime = currentTime;
LOGGER.info("物料分类列表已更新,共{}种物料", distinctMaterialList.size());
return distinctMaterialList;
} catch (Exception e) {
LOGGER.error("获取物料分类列表失败: {}", e.getMessage(), e);
return Collections.emptyList();
}
}
/**
* 按当前日期计算无时间范围时使用
*/
private void calculateForCurrentDate(String bkgs, List<String> distinctList) {
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;
int day = cal.get(Calendar.DAY_OF_MONTH);
// 当前年月
String yearMonth = String.format("%04d-%02d", year, month);
// 上月
cal.add(Calendar.MONTH, -1);
String yearLastMonth = String.format("%04d-%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1);
// 上年同期
cal.add(Calendar.YEAR, 1); // 回到当前年
cal.add(Calendar.YEAR, -1); // 再减一年
String lastYearMonth = String.format("%04d-%02d", cal.get(Calendar.YEAR), month);
Date currentDate = new Date(System.currentTimeMillis());
// 1. 计算并保存各板块物料采购总额总量单价(按月存储)
monthlyMaterialSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList);
// 2. 根据年月汇总板块基地年月当期上期同期数据
monthlyBaseSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList);
// 3. 计算并保存各板块物料采购总额总量单价(按日存储)
dailyMaterialSummaryBySegment(year, month, day, currentDate, bkgs, distinctList);
// 4. 根据日期入库单号物料名称分页查询 日期入库单号物料编码物料名称规格型号
// 汇总入库数量单位入库单价汇总入库金额供应商订单编号库存数
dailyWarehousingSummary(year, month, day, currentDate, bkgs, distinctList);
}
/**
* 计算月度维度数据
*/
private void calculateMonthlyData(DateRange dateRange, String bkgs, List<String> distinctList) {
Calendar startCal = Calendar.getInstance();
startCal.setTime(dateRange.getStartDate());
startCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为月份的第一天
Calendar endCal = Calendar.getInstance();
endCal.setTime(dateRange.getEndDate());
endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 设置为月份的最后一天
Calendar monthCal = (Calendar) startCal.clone();
while (!monthCal.after(endCal)) {
int year = monthCal.get(Calendar.YEAR);
int month = monthCal.get(Calendar.MONTH) + 1;
// 当前年月
String yearMonth = String.format("%04d-%02d", year, month);
// 上月
Calendar lastMonthCal = (Calendar) monthCal.clone();
lastMonthCal.add(Calendar.MONTH, -1);
String yearLastMonth = String.format("%04d-%02d", lastMonthCal.get(Calendar.YEAR), lastMonthCal.get(Calendar.MONTH) + 1);
// 上年同期
Calendar lastYearMonthCal = (Calendar) monthCal.clone();
lastYearMonthCal.add(Calendar.YEAR, -1);
String lastYearMonth = String.format("%04d-%02d", lastYearMonthCal.get(Calendar.YEAR), lastYearMonthCal.get(Calendar.MONTH) + 1);
LOGGER.info("计算月度汇总数据: {}-{};上月:{};上年同期:{}", year, month, yearLastMonth, lastYearMonth);
// 1. 计算并保存各板块物料采购总额总量单价(按月存储)
monthlyMaterialSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList);
// 2. 根据年月汇总板块基地年月当期上期同期数据
monthlyBaseSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList);
// 移动到下个月
monthCal.add(Calendar.MONTH, 1);
}
}
/**
* 计算日度维度数据
*/
private void calculateDailyData(DateRange dateRange, String bkgs, List<String> distinctList) {
Calendar dayCal = Calendar.getInstance();
dayCal.setTime(dateRange.getStartDate());
Calendar endCal = Calendar.getInstance();
endCal.setTime(dateRange.getEndDate());
while (!dayCal.after(endCal)) {
int year = dayCal.get(Calendar.YEAR);
int month = dayCal.get(Calendar.MONTH) + 1;
int day = dayCal.get(Calendar.DAY_OF_MONTH);
Date currentDate = dayCal.getTime();
LOGGER.info("计算日度汇总数据: {}-{}-{}", year, month, day);
// 计算并保存各板块物料采购总额总量单价(按日存储)
dailyMaterialSummaryBySegment(year, month, day, currentDate, bkgs, distinctList);
// 根据日期入库单号物料名称分页查询 日期入库单号物料编码物料名称规格型号
//汇总入库数量单位入库单价汇总入库金额供应商订单编号库存数
dailyWarehousingSummary(year, month, day, currentDate, bkgs, distinctList);
// 下一天
dayCal.add(Calendar.DATE, 1);
}
}
/**
* 根据年月汇总 板块基地年月当期采购总额采购总量采购平均价上期采购总额采购总量采购平均价同期采购总额采购总量采购平均价
* @param year
* @param month
* @param yearMonth
* @param yearLastMonth
* @param lastYearMonth
* @param bkgs
*/
private void monthlyBaseSummaryBySegment(int year, int month, String yearMonth, String yearLastMonth,
String lastYearMonth, String bkgs, List<String> distinctList) {
try {
LOGGER.info("开始计算{}年{}月物料采购各基地月度汇总数据,板块公司:{}", year, month, bkgs);
// 1. 删除该月份已存在的汇总数据避免重复
String deleteSql = "DELETE FROM " + JD_PROCUREMENT_DETAILS_YEAR_MONTH +
" WHERE BKGS = ? AND YEARMONTH = ?";
DBSql.update(deleteSql, new Object[]{bkgs, yearMonth});
// 2. 一次性查询当前月上月和去年同期数据包含基地信息
// 当前月数据
String currentMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC, XQGC";
List<RowMap> currentMonthData = DBSql.getMaps(currentMonthSql, bkgs, yearMonth + "%");
// 上月数据
List<RowMap> lastMonthData = Collections.emptyList();
if (StringUtils.isNotBlank(yearLastMonth)) {
String lastMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC, XQGC";
lastMonthData = DBSql.getMaps(lastMonthSql, bkgs, yearLastMonth + "%");
}
// 去年同期数据
List<RowMap> lastYearMonthData = Collections.emptyList();
if (StringUtils.isNotBlank(lastYearMonth)) {
String lastYearMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC, XQGC";
lastYearMonthData = DBSql.getMaps(lastYearMonthSql, bkgs, lastYearMonth + "%");
}
// 3. 创建物料名称和基地组合键映射的数据Map
// 当前月数据映射
Map<String, RowMap> currentMonthMap = currentMonthData.stream()
.collect(Collectors.toMap(
row -> {
String wlmc = row.getString("WLMC");
String base = row.getString("XQGC");
String processedWlmc = processMaterialName(wlmc);
return processedWlmc + "|" + base;
},
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 上月数据映射
Map<String, RowMap> lastMonthMap = lastMonthData.stream()
.collect(Collectors.toMap(
row -> {
String wlmc = row.getString("WLMC");
String base = row.getString("XQGC");
String processedWlmc = processMaterialName(wlmc);
return processedWlmc + "|" + base;
},
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 去年同期数据映射
Map<String, RowMap> lastYearMonthMap = lastYearMonthData.stream()
.collect(Collectors.toMap(
row -> {
String wlmc = row.getString("WLMC");
String base = row.getString("XQGC");
String processedWlmc = processMaterialName(wlmc);
return processedWlmc + "|" + base;
},
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 4. 获取所有基地
Set<String> bases = new HashSet<>();
for (RowMap row : currentMonthData) {
bases.add(row.getString("XQGC"));
}
for (RowMap row : lastMonthData) {
bases.add(row.getString("XQGC"));
}
for (RowMap row : lastYearMonthData) {
bases.add(row.getString("XQGC"));
}
// 5. 批量插入
List<BO> bos = new ArrayList<>();
for (String wlmc : distinctList) {
for (String base : bases) {
String key = wlmc + "|" + base;
RowMap currentMonthRow = currentMonthMap.get(key);
if (currentMonthRow == null || currentMonthRow.getDouble("totalAmount") == 0.0) continue;
BO summaryBO = new BO();
summaryBO.set("BKGS", bkgs);
summaryBO.set("JD", base);
List<BO> bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL").
addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list();
if (bnbmCgPzwlfl!=null){
String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|"));
summaryBO.set("WLMC", wlfl);
}else {
summaryBO.set("WLMC", wlmc);
}
summaryBO.set("YEARMONTH", yearMonth);
summaryBO.set("CGZE", currentMonthRow.getDouble("totalAmount"));
summaryBO.set("CGZL", currentMonthRow.getDouble("totalQuantity"));
double avgPrice = currentMonthRow.getDouble("totalQuantity") != 0 ?
currentMonthRow.getDouble("totalAmount") / currentMonthRow.getDouble("totalQuantity") : 0;
summaryBO.set("PJDJ", avgPrice);
// 添加上月数据
RowMap lastMonthRow = lastMonthMap.get(key);
if (lastMonthRow != null) {
summaryBO.set("SQCGZE", lastMonthRow.getDouble("totalAmount"));
summaryBO.set("SQCGZL", lastMonthRow.getDouble("totalQuantity"));
double lastMonthAvgPrice = lastMonthRow.getDouble("totalQuantity") != 0 ?
lastMonthRow.getDouble("totalAmount") / lastMonthRow.getDouble("totalQuantity") : 0;
summaryBO.set("SQPJDJ", lastMonthAvgPrice);
}
// 添加去年同期数据
RowMap lastYearMonthRow = lastYearMonthMap.get(key);
if (lastYearMonthRow != null) {
summaryBO.set("TQCGZE", lastYearMonthRow.getDouble("totalAmount"));
summaryBO.set("TQCGZL", lastYearMonthRow.getDouble("totalQuantity"));
double lastYearMonthAvgPrice = lastYearMonthRow.getDouble("totalQuantity") != 0 ?
lastYearMonthRow.getDouble("totalAmount") / lastYearMonthRow.getDouble("totalQuantity") : 0;
summaryBO.set("TQPJDJ", lastYearMonthAvgPrice);
}
bos.add(summaryBO);
}
}
if (!bos.isEmpty()) {
// 批量插入数据
int batchSize = 1000;
for (int i = 0; i < bos.size(); i += batchSize) {
int end = Math.min(i + batchSize, bos.size());
List<BO> batch = bos.subList(i, end);
SDK.getBOAPI().createDataBO(JD_PROCUREMENT_DETAILS_YEAR_MONTH, batch, UserContext.fromUID("admin"));
}
}
LOGGER.info("成功保存{}条月度基地汇总数据,板块:{}", bos.size(), bkgs);
} catch (Exception e) {
String errorMsg = String.format("月度基地汇总计算失败(年月=%s,板块=%s): %s", yearMonth, bkgs, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 计算并保存各板块物料采购总额总量单价(按月存储)
* 逻辑按物料名称(WLMC)分组计算月度采购总额(SUM(JSHJ))采购总量(SUM(YSSL))和平均单价(采购总额/采购总量)
* @param year 年份
* @param month 月份
* @param yearMonth 年月字符串(格式yyyy-MM)
* @param bkgs 板块公司
*/
private void monthlyMaterialSummaryBySegment(int year, int month, String yearMonth,
String yearLastMonth, String lastYearMonth, String bkgs, List<String> distinctList) {
try {
LOGGER.info("开始计算{}年{}月物料采购月度汇总数据,板块公司:{}", year, month, bkgs);
// 1. 删除该月份已存在的汇总数据避免重复
String deleteSql = "DELETE FROM " + PROCUREMENT_DETAILS_YEAR_MONTH +
" WHERE BKGS = ? AND YEARMONTH = ?";
DBSql.update(deleteSql, new Object[]{bkgs, yearMonth});
// 2. 一次性查询当前月上月和去年同期数据
// 当前月数据
String currentMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC";
List<RowMap> currentMonthData = DBSql.getMaps(currentMonthSql, bkgs, yearMonth + "%");
// 上月数据
List<RowMap> lastMonthData = Collections.emptyList();
if (StringUtils.isNotBlank(yearLastMonth)) {
String lastMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC";
lastMonthData = DBSql.getMaps(lastMonthSql, bkgs, yearLastMonth + "%");
}
// 去年同期数据
List<RowMap> lastYearMonthData = Collections.emptyList();
if (StringUtils.isNotBlank(lastYearMonth)) {
String lastYearMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ LIKE ? " +
"GROUP BY WLMC";
lastYearMonthData = DBSql.getMaps(lastYearMonthSql, bkgs, lastYearMonth + "%");
}
// 3. 创建物料名称映射的数据Map
// 当前月数据映射
Map<String, RowMap> currentMonthMap = currentMonthData.stream()
.collect(Collectors.toMap(
row -> processMaterialName(row.getString("WLMC")),
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 上月数据映射
Map<String, RowMap> lastMonthMap = lastMonthData.stream()
.collect(Collectors.toMap(
row -> processMaterialName(row.getString("WLMC")),
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 去年同期数据映射
Map<String, RowMap> lastYearMonthMap = lastYearMonthData.stream()
.collect(Collectors.toMap(
row -> processMaterialName(row.getString("WLMC")),
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 4. 批量插入
List<BO> bos = new ArrayList<>();
for (String wlmc : distinctList) {
RowMap currentMonthRow = currentMonthMap.get(wlmc);
if (currentMonthRow == null || currentMonthRow.getDouble("totalAmount") == 0.0) continue;
BO summaryBO = new BO();
summaryBO.set("YEARMONTH", yearMonth);
List<BO> bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL").
addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list();
// if (bnbmCgPzwlfl!=null){
// String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|"));
// summaryBO.set("WLMC", wlfl);
// }else {
summaryBO.set("WLMC", wlmc);
// }
summaryBO.set("CGZE", currentMonthRow.getDouble("totalAmount"));
summaryBO.set("CGZL", currentMonthRow.getDouble("totalQuantity"));
double avgPrice = currentMonthRow.getDouble("totalQuantity") != 0 ?
currentMonthRow.getDouble("totalAmount") / currentMonthRow.getDouble("totalQuantity") : 0;
summaryBO.set("PJDJ", avgPrice);
// 添加上月数据
RowMap lastMonthRow = lastMonthMap.get(wlmc);
if (lastMonthRow != null) {
summaryBO.set("SQCGZE", lastMonthRow.getDouble("totalAmount"));
summaryBO.set("SQCGZL", lastMonthRow.getDouble("totalQuantity"));
}
// 添加去年同期数据
RowMap lastYearMonthRow = lastYearMonthMap.get(wlmc);
if (lastYearMonthRow != null) {
summaryBO.set("TQCGZE", lastYearMonthRow.getDouble("totalAmount"));
summaryBO.set("TQCGZL", lastYearMonthRow.getDouble("totalQuantity"));
}
summaryBO.set("BKGS", bkgs);
bos.add(summaryBO);
}
if (!bos.isEmpty()) {
// 批量插入数据
int batchSize = 1000;
for (int i = 0; i < bos.size(); i += batchSize) {
int end = Math.min(i + batchSize, bos.size());
List<BO> batch = bos.subList(i, end);
SDK.getBOAPI().createDataBO(PROCUREMENT_DETAILS_YEAR_MONTH, batch, UserContext.fromUID("admin"));
}
}
LOGGER.info("成功保存{}条月度汇总数据,板块:{}", bos.size(), bkgs);
} catch (Exception e) {
String errorMsg = String.format("月度汇总计算失败(年月=%s,板块=%s): %s", yearMonth, bkgs, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 计算并保存各板块物料采购总额总量单价(按日存储)
* 逻辑按物料名称(WLMC)分组计算当日采购总额(SUM(JSHJ))采购总量(SUM(YSSL))和平均单价(采购总额/采购总量)
* @param year 年份
* @param month 月份
* @param day 日期
* @param currentDate 当前日期对象
* @param bkgs 板块公司
*/
private void dailyMaterialSummaryBySegment(int year, int month, int day, Date currentDate,
String bkgs, List<String> distinctList) {
try {
// 格式化日期字符串 (yyyy-MM-dd)
String dateStr = String.format("%04d-%02d-%02d", year, month, day);
LOGGER.info("开始计算{}物料采购日度汇总数据,板块公司:{}", dateStr, bkgs);
// 1. 删除该日期已存在的汇总数据
String deleteSql = "DELETE FROM " + PROCUREMENT_DETAILS_YEAR_MONTH_DAY +
" WHERE BKGS = ? AND RQ = ?";
DBSql.update(deleteSql, new Object[]{bkgs,dateStr});
// 2. 查询当天数据
String querySql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " +
"FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY +
" WHERE BKGS = ? AND CGRQ = ? " +
"GROUP BY WLMC";
List<RowMap> dailyData = DBSql.getMaps(querySql, bkgs, dateStr);
// 3. 创建物料数据映射
Map<String, RowMap> dailyMap = dailyData.stream()
.collect(Collectors.toMap(
row -> processMaterialName(row.getString("WLMC")),
row -> row,
(existing, replacement) -> {
double existingAmount = existing.getDouble("totalAmount");
double replacementAmount = replacement.getDouble("totalAmount");
existing.put("totalAmount", existingAmount + replacementAmount);
double existingQuantity = existing.getDouble("totalQuantity");
double replacementQuantity = replacement.getDouble("totalQuantity");
existing.put("totalQuantity", existingQuantity + replacementQuantity);
return existing;
}
));
// 4. 批量插入
List<BO> bos = new ArrayList<>();
for (String wlmc : distinctList) {
RowMap row = dailyMap.get(wlmc);
if (row == null || row.getDouble("totalAmount") == 0.0) continue;
BO summaryBO = new BO();
summaryBO.set("RQ", dateStr);
List<BO> bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL").
addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list();
// if (bnbmCgPzwlfl!=null){
// String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|"));
// summaryBO.set("WLMC", wlfl);
// }else {
summaryBO.set("WLMC", wlmc);
// }
summaryBO.set("CGZE", row.getDouble("totalAmount"));
summaryBO.set("CGZL", row.getDouble("totalQuantity"));
double avgPrice = row.getDouble("totalQuantity") != 0 ?
row.getDouble("totalAmount") / row.getDouble("totalQuantity") : 0;
summaryBO.set("PJDJ", avgPrice);
summaryBO.set("BKGS", bkgs);
bos.add(summaryBO);
}
if (!bos.isEmpty()) {
// 批量插入数据
int batchSize = 1000;
for (int i = 0; i < bos.size(); i += batchSize) {
int end = Math.min(i + batchSize, bos.size());
List<BO> batch = bos.subList(i, end);
SDK.getBOAPI().createDataBO(PROCUREMENT_DETAILS_YEAR_MONTH_DAY, batch, UserContext.fromUID("admin"));
}
}
LOGGER.info("成功保存{}条日度汇总数据,板块:{}", bos.size(), bkgs);
} catch (Exception e) {
String errorMsg = String.format("日度汇总计算失败(日期=%s,板块=%s): %s", currentDate, bkgs, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* // 根据日期入库单号物料名称分页查询 日期入库单号物料编码物料名称规格型号
* 汇总入库数量单位入库单价汇总入库金额供应商订单编号库存数
* @param year
* @param month
* @param day
* @param currentDate 开始时间
* @param bkgs 板块公司
* @param distinctList 物料名称数组
*/
private void dailyWarehousingSummary(int year, int month, int day, Date currentDate,
String bkgs, List<String> distinctList) {
String dateStr = String.format("%04d-%02d-%02d", year, month, day);
String yearMonth = String.format("%04d%02d", year, month); // 年月格式YYYYMM
LOGGER.info("开始计算{}入库明细日度汇总数据,板块公司:{}", dateStr, bkgs);
try {
// 1. 删除该日期已存在的汇总数据避免重复
String deleteSql = "DELETE FROM BO_EU_CG_NYRKMX WHERE BKGS = ? AND DATE = ?";
DBSql.update(deleteSql, new Object[]{bkgs, dateStr});
LOGGER.info("已清理{}的旧入库明细数据", dateStr);
// 2. 计算总记录数用于分页
String countSql = "SELECT COUNT(1) AS total FROM BO_EU_DWD_ORDER_RKD_HZ " +
"WHERE BKGS = ? AND DATE(DJRQ) = ?";
RowMap countResult = DBSql.getMap(countSql, bkgs, dateStr);
int totalCount = countResult.getInt("total");
LOGGER.info("共查询到{}条入库记录需要处理", totalCount);
if (totalCount == 0) {
LOGGER.info("无入库数据需要处理,跳过");
return;
}
// 3. 分页处理数据
int pageSize = 1000;
int totalPages = (int) Math.ceil((double) totalCount / pageSize);
LOGGER.info("开始分页处理入库数据,共{}页", totalPages);
for (int page = 0; page < totalPages; page++) {
int offset = page * pageSize;
LOGGER.debug("正在处理第{}页入库数据,偏移量:{}", page + 1, offset);
// 分页查询入库数据
// String querySql = "SELECT DJRQ, DJH, WLBM, WLMC, GGXH, SLGC, RKSL, JLDW, " +
// "HSDJHYF, JEHYF, GYSNAME, CGDDH " +
// "FROM BO_EU_DWD_ORDER_RKD_HZ " +
// "WHERE BKGS = ? AND DATE(DJRQ) = ? " +
// "ORDER BY DJH LIMIT ? OFFSET ?";
String querySql = "SELECT DJRQ, DJH, WLMC, WLBM, GGXH, SLGC, SUM(RKSL) AS RKSL, " +
"JLDW, HSDJHYF, SUM(JEHYF) AS JEHYF, GYSNAME, CGDDH " +
"FROM BO_EU_DWD_ORDER_RKD_HZ WHERE BKGS = ? AND DATE(DJRQ) = ? " +
"GROUP BY DJRQ,DJH,WLMC "+
"ORDER BY DJH LIMIT ? OFFSET ?";
List<RowMap> pageData = DBSql.getMaps(querySql, bkgs, dateStr, pageSize, offset);
LOGGER.debug("第{}页查询到{}条记录", page + 1, pageData.size());
// 转换并批量插入数据
List<BO> bos = new ArrayList<>();
for (RowMap row : pageData) {
BO detailBO = new BO();
detailBO.set("DATE", row.getDate("DJRQ")); // 入库日期
detailBO.set("RKDH", row.getString("DJH")); // 入库单号
detailBO.set("WLBM", row.getString("WLBM")); // 物料编码
detailBO.set("WLMC", row.getString("WLMC")); // 物料名称
detailBO.set("GGXH", row.getString("GGXH")); // 规格型号
detailBO.set("GC", row.getString("SLGC")); // 收料工厂
detailBO.set("RKSL", row.getDouble("RKSL")); // 入库数量
detailBO.set("DW", row.getString("JLDW")); // 单位
detailBO.set("RKDJ", row.getDouble("HSDJHYF")); // 入库单价含税单价含运费
detailBO.set("RKJE", row.getDouble("JEHYF")); // 入库金额金额含运费
detailBO.set("GYS", row.getString("GYSNAME")); // 供应商
detailBO.set("DDBH", row.getString("CGDDH")); // 订单编号
detailBO.set("KCS", 0.00); // 库存数默认为0需后续计算
detailBO.set("BKGS", bkgs); // 板块公司
detailBO.set("YEARMONTH", yearMonth); // 年月
bos.add(detailBO);
}
// 批量插入当前页数据
if (!bos.isEmpty()) {
SDK.getBOAPI().createDataBO(BO_EU_CG_NYRKMX, bos, UserContext.fromUID("admin"));
LOGGER.debug("入库明细日度汇总数据,板块公司:{}, 第{}页数据插入成功,共{}条,数量总共{}条",
bkgs, page + 1, bos.size(),totalCount);
}
}
LOGGER.info("{}入库明细日度汇总完成,共处理{}条数据", dateStr, totalCount);
} catch (Exception e) {
String errorMsg = String.format("日度入库明细汇总失败(日期=%s,板块=%s): %s",
dateStr, bkgs, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 处理物料名称提取竖线前的部分
*/
private String processMaterialName(String wlmc) {
if (StringUtils.isBlank(wlmc)) {
return "";
}
if (wlmc.contains("|")) {
String[] parts = wlmc.split("\\|");
return parts[0].trim();
}
return wlmc.trim();
}
}

View File

@ -0,0 +1,692 @@
package com.awspaas.user.apps.bnbm.datalinkup.service.impl;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.DBUtils;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.bpms.util.UtilDate;
import com.actionsoft.sdk.local.SDK;
import com.actionsoft.sdk.local.api.cc.RDSAPI;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName: PurchaseDataSyncServiceImpl
* @Description:
* @author: 李春洋
* @date: 2025/8/15 15:46
* @Blog: https://
*/
public class PurchaseDataSyncServiceImpl implements DataSyncService {
private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSyncServiceImpl.class);
/**
* 时间范围常量同步最近30天数据不包括当天
*/
private static final int DAYS_BACK = Integer.parseInt(SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "days_back"));
/**
* 增加分页大小常量
*/
private static final int PAGE_SIZE = 1000; // 每页查询1000条记录
private static final String ORACLE_DATE_FORMAT = "YYYY-MM-DD HH24:MI:SS";
@Override
public ArrayList<DateRange> syncDataByConfigs(List<BO> configs) {
ArrayList<DateRange> list = new ArrayList<>();
if (configs.isEmpty()) {
LOGGER.info("未找到有效的同步配置");
return list;
}
// 按所属板块(SSBK)分组配置
Map<String, List<BO>> configsByPlate = configs.stream()
.collect(Collectors.groupingBy(bo -> bo.getString("SSBK")));
// 遍历处理每个板块的配置
for (Map.Entry<String, List<BO>> entry : configsByPlate.entrySet()) {
String plate = entry.getKey();
List<BO> plateConfigs = entry.getValue();
LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size());
// 处理当前板块的每条配置
boolean connectionFailed = false;
String errorMsg = "";
for (BO mainConfig : plateConfigs) {
try {
DateRange dateRange = processMainConfig(mainConfig);
list.add(dateRange);
} catch (Exception e) {
LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}",
plate, mainConfig.getString("BINDID"), e.getMessage(), e);
}
}
}
return list;
}
@Override
public DateRange processMainConfig(BO mainConfig) {
String bindId = mainConfig.getString("BINDID");
String tableName = mainConfig.getString("TBB");
String timeField = mainConfig.getString("SJZD");
String targetTable = mainConfig.getString("LDB");
String ccId = mainConfig.getString("CC_ID");
String partitionField = mainConfig.getString("FQBZD");
String bkgs = mainConfig.getString("BKGS");
DateRange dateRange = new DateRange();
LOGGER.info("处理配置BindID={}, 源表={}, 目标表={}, CC_ID={}, 时间字段={}, 分区字段配置={}",
bindId, tableName, targetTable, ccId,timeField,partitionField);
// 查询子表字段映射配置
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", bindId)
.list();
if (fieldMappings.isEmpty()) {
LOGGER.warn("未找到BindID={}的字段映射配置", bindId);
return dateRange;
}
// 根据时间字段是否为空设置日期范围
Date startDate = null;
Date endDate = null;
// 删除目标表数据根据时间字段是否为空决定删除范围
if (timeField == null || timeField.isEmpty()) {
// 全量删除
deleteAllTargetData(targetTable);
} else {
// 计算时间范围当前日期-30天 ~ 昨天
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
endDate = cal.getTime();
cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前
startDate = cal.getTime();
// 获取目标表时间字段名
String targetTimeField = getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
return dateRange;
}
// 按时间范围删除
deleteTargetData(targetTable, targetTimeField, startDate, endDate);
}
querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField,
fieldMappings, targetTable);
// }
dateRange.setStartDate(startDate);
dateRange.setEndDate(endDate);
return dateRange;
}
/**
* 获取目标表时间字段名
* @param mappings 字段映射配置列表
* @param sourceTimeField 源表时间字段名
* @return 目标表时间字段名未找到返回null
*/
@Override
public String getTargetTimeField(List<BO> mappings, String sourceTimeField) {
for (BO mapping : mappings) {
if (sourceTimeField.equals(mapping.getString("TBBZD"))) {
return mapping.getString("LDBZD");
}
}
return null;
}
/**
* 删除目标表中指定时间范围的数据
* @param targetTable 目标表名
* @param targetTimeField 目标表时间字段名
* @param startDate 开始时间
* @param endDate 结束时间
* @throws RuntimeException 删除失败时抛出
*/
@Override
public void deleteTargetData(String targetTable, String targetTimeField,
Date startDate, Date endDate) {
try {
String deleteSql = "DELETE FROM " + targetTable +
" WHERE " + targetTimeField + " BETWEEN ? AND ?";
int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate});
LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})",
targetTable, deletedCount, startDate, endDate);
} catch (Exception e) {
throw new RuntimeException("删除目标表数据失败: " + e.getMessage(), e);
}
}
@Override
public void deleteAllTargetData(String targetTable) {
try {
String deleteSql = "DELETE FROM " + targetTable ;
int deletedCount = DBSql.update(deleteSql);
LOGGER.info("已全量删除目标表[{}]中{}条数据", targetTable, deletedCount);
} catch (Exception e) {
throw new RuntimeException("全量删除目标表数据失败: " + e.getMessage(), e);
}
}
/**
* 跨库查询源表数据
* @param ccId 跨库连接ID
* @param tableName 源表名
* @param timeField 源表时间字段名
* @param startDated 开始时间
* @param endDated 结束时间
* @return 查询结果数据集
* @throws RuntimeException 查询失败或参数无效时抛出
*/
@Override
public void querySourceData(String ccId, String tableName,
String timeField, Date startDated, Date endDated,String partitionField,
List<BO> fieldMappings, String targetTable) {
int totalRows = 0; // 总查询行数
int totalSuccess = 0; // 总成功插入行数
int pageNo = 1;
boolean hasMore;
RDSAPI rdsapi = null;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startDate = simpleDateFormat.format(startDated);
String endDate = simpleDateFormat.format(endDated);
try {
rdsapi = SDK.getCCAPI().getRDSAPI(ccId);
DBUtils.SUPPLY supply = rdsapi.getSupply();
String DBname = supply.getName();
LOGGER.info("数据库为:{}",DBname);
if ("ORACLE".equalsIgnoreCase(DBname)){
// 构建查询条件
StringBuilder conditionBuilder = new StringBuilder();
List<Object> params = new ArrayList<>(); // 存储查询参数
// 分区字段和时间字段组合查询条件
if (partitionField != null && !partitionField.isEmpty()) {
// 1. 查询最大分区值
String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName;
List<RowMap> maxPartitionResult = rdsapi.getMaps(maxPartitionSql);
if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) {
LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField);
return;
}
String maxPartition = maxPartitionResult.get(0).getString("max_partition");
LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition);
// 添加分区条件
conditionBuilder.append(partitionField)
.append(" = '")
.append(maxPartition)
.append("'");
// 如果时间字段存在添加时间范围条件
if (timeField != null && !timeField.isEmpty()) {
conditionBuilder.append(" AND TO_DATE(")
.append(timeField)
.append(", '")
.append(ORACLE_DATE_FORMAT)
.append("') BETWEEN TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("') AND TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("')");
params.add(startDate);
params.add(endDate);
}
} else if (timeField != null && !timeField.isEmpty()) {
// 没有分区字段但时间字段存在使用时间范围条件
// 仅时间范围条件使用占位符
conditionBuilder.append("TO_DATE(")
.append(timeField)
.append(", '")
.append(ORACLE_DATE_FORMAT)
.append("') BETWEEN TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("') AND TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("')");
params.add(startDate);
params.add(endDate);
} else {
// 既没有分区字段也没有时间字段查询全表
LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!");
conditionBuilder.append("1=1");
}
// 分页查询数据
do {
// 使用Oracle分页语法 (12c+)
String querySql = "SELECT * FROM ( " +
"SELECT t.*, ROWNUM rn FROM " + tableName + " t " +
"WHERE " + conditionBuilder.toString() + " AND ROWNUM <= " + (pageNo * PAGE_SIZE) +
") WHERE rn > " + ((pageNo - 1) * PAGE_SIZE);
LOGGER.debug("执行Oracle查询: {}", querySql);
List<RowMap> pageData;
// 根据条件类型执行查询
if (partitionField != null && !partitionField.isEmpty() &&
timeField != null && !timeField.isEmpty()) {
// 分区+时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else if (timeField != null && !timeField.isEmpty()) {
// 仅时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else {
// 无时间范围查询仅分区或全表
pageData = rdsapi.getMaps(querySql);
}
if (pageData != null && !pageData.isEmpty()) {
// 直接处理当前页数据
int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable);
totalRows += pageData.size();
totalSuccess += successCount;
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} else {
hasMore = false;
}
} while (hasMore);
}else {
// 构建查询条件
StringBuilder conditionBuilder = new StringBuilder();
// 修改点分区字段和时间字段组合查询条件
if (partitionField != null && !partitionField.isEmpty()) {
// 1. 查询最大分区值
String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName;
List<RowMap> maxPartitionResult = rdsapi.getMaps(maxPartitionSql);
if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) {
LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField);
return;
}
String maxPartition = maxPartitionResult.get(0).getString("max_partition");
LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition);
// 添加分区条件
conditionBuilder.append(partitionField)
.append(" = '")
.append(maxPartition)
.append("'");
// 如果时间字段存在添加时间范围条件
if (timeField != null && !timeField.isEmpty()) {
conditionBuilder.append(" AND ")
.append(timeField)
.append(" BETWEEN ? AND ?");
}
} else if (timeField != null && !timeField.isEmpty()) {
// 没有分区字段但时间字段存在使用时间范围条件
conditionBuilder.append(timeField)
.append(" BETWEEN ? AND ?");
} else {
// 既没有分区字段也没有时间字段查询全表实际应避免这种情况
LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!");
conditionBuilder.append("1=1");
}
// 分页查询数据
do {
String querySql = "SELECT * FROM " + tableName +
" WHERE " + conditionBuilder.toString() +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
LOGGER.debug("执行查询: {}", querySql);
List<RowMap> pageData;
// 根据条件类型执行查询
if (partitionField != null && !partitionField.isEmpty() &&
timeField != null && !timeField.isEmpty()) {
// 分区+时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else if (timeField != null && !timeField.isEmpty()) {
// 仅时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else {
// 无时间范围查询仅分区或全表
pageData = rdsapi.getMaps(querySql);
}
if (pageData != null && !pageData.isEmpty()) {
// 直接处理当前页数据
int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable);
totalRows += pageData.size();
totalSuccess += successCount;
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} else {
hasMore = false;
}
} while (hasMore);
}
LOGGER.info("从表[{}]共查询到{}条数据,成功同步{}条数据",
tableName, totalRows, totalSuccess);
} catch (Exception e) {
throw new RuntimeException("查询源表[" + tableName + "]数据失败: " + e.getMessage(), e);
}finally {
}
}
/**
* 处理并插入数据到目标表
* @param sourceData 源数据列表
* @param mappings 字段映射配置
* @param targetTable 目标表名
*/
public int processAndInsertData(List<RowMap> sourceData,
List<BO> mappings, String targetTable) {
if (sourceData.isEmpty()) {
LOGGER.info("没有需要同步的数据");
return 0;
}
List<BO> batchList = new ArrayList<>();
int successCount = 0;
int totalCount = sourceData.size();
int processedCount = 0; // 已处理记录数
for (int i = 0; i < totalCount; i++) {
RowMap record = sourceData.get(i);
processedCount++; // 增加已处理计数
try {
// 字段映射转换
BO targetData = convertFields(record, mappings);
batchList.add(targetData);
// 批量插入条件达到批处理大小或最后一条
if (batchList.size() >= PAGE_SIZE || i == totalCount - 1) {
// 使用管理员权限批量插入
SDK.getBOAPI().createDataBO(targetTable, batchList, UserContext.fromUID("admin"));
successCount += batchList.size();
batchList.clear(); // 清空批次
}
} catch (Exception e) {
LOGGER.error("数据处理失败: {}", e.getMessage(), e);
}
}
// 增加详细日志输出共处理多少条成功同步多少条
LOGGER.info("本次处理{}条数据,成功同步{}条数据到表[{}]",
processedCount, successCount, targetTable);
return successCount;
}
/**
* 字段映射转换
* @param source 源数据记录
* @param mappings 字段映射配置
* @return 转换后的BO对象
*/
public BO convertFields(RowMap source, List<BO> mappings) {
BO target = new BO();
for (BO mapping : mappings) {
String sourceField = mapping.getString("TBBZD");
String targetField = mapping.getString("LDBZD");
// if (!source.containsKey(sourceField)) {
// LOGGER.debug("源字段[{}]不存在于查询结果中", sourceField);
// continue;
// }
String operationExpr = mapping.getString("TBBZDJSLJ");
if (StringUtils.isNotBlank(operationExpr)) {
// 解析运算表达式 (格式: [运算符][数字])
char operator = operationExpr.charAt(0);
String numberPart = operationExpr.substring(1);
try {
// 获取源值并转换为BigDecimal
String sourceValue = source.getString(sourceField);
if (StringUtils.isBlank(sourceValue)) {
target.set(targetField, null);
continue;
}
if ("GG".equals(sourceField)){
String string = source.getString(sourceField);
if (StringUtils.isNotBlank(string) && string.contains("×")){
String[] split = string.split("×");
String s = split[split.length - 1];
target.set(targetField, s);
continue;
}
}
BigDecimal sourceNum = new BigDecimal(sourceValue);
BigDecimal operand = new BigDecimal(numberPart);
BigDecimal result;
// 执行相应运算
switch (operator) {
case '*':
result = sourceNum.multiply(operand);
break;
case '/':
if (BigDecimal.ZERO.compareTo(operand) == 0) {
LOGGER.error("除零错误: 源字段[{}] 除数为0", sourceField);
result = sourceNum; // 避免除零异常
} else {
// 除法保留10位小数并四舍五入
result = sourceNum.divide(operand, 10, RoundingMode.HALF_UP);
}
break;
case '+':
result = sourceNum.add(operand);
break;
case '-':
result = sourceNum.subtract(operand);
break;
default:
LOGGER.error("未知运算符: {} 字段[{}]", operator, sourceField);
result = sourceNum;
}
target.set(targetField, result);
} catch (NumberFormatException e) {
LOGGER.error("数值转换失败: 源字段[{}]={}, 操作数={}",
sourceField, source.getString(sourceField), numberPart, e);
target.set(targetField, source.getString(sourceField));
}
} else {
if ("GG".equals(sourceField)){
String string = source.getString(sourceField);
if (StringUtils.isNotBlank(string) && string.contains("×")){
String[] split = string.split("×");
String s = split[split.length - 1];
target.set(targetField, s);
}else {
// 无运算表达式时直接复制原始值
target.set(targetField, StringUtils.isNotBlank(string)?string:"");
}
}else {
// 无运算表达式时直接复制原始值
target.set(targetField, source.getString(sourceField));
}
}
String ldzdmrz = mapping.getString("LDZDMRZ");
if (StringUtils.isNotBlank(ldzdmrz)){
target.set(mapping.getString("LDBZD"),ldzdmrz);
}
}
return target;
}
/**
* 销售各板块数据汇总表
* @param configs
*/
@Override
public void sumBkTable(List<BO> configs) {
Map<String, List<BO>> configsByPlate = configs.stream()
.collect(Collectors.groupingBy(bo -> bo.getString("SSBK")));
// 遍历处理每个板块的配置
for (Map.Entry<String, List<BO>> entry : configsByPlate.entrySet()) {
String plate = entry.getKey();
List<BO> plateConfigs = entry.getValue();
LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size());
// 处理当前板块的每条配置
for (BO mainConfig : plateConfigs) {
String targetTable = mainConfig.getString("LDB");//落地表
String timeField = mainConfig.getString("SJZD");//时间字段
String bindId = mainConfig.getString("BINDID");//bindid
String tablename = mainConfig.getString("TABLENAME");//同步表名
String hzb = "";
try {
if ("采购单".equals(tablename)){
hzb = "BO_EU_DWD_ORDER_CGDD_HZ";
}else if ("应付单".equals(tablename)){
hzb = "BO_EU_DWD_ORDER_YFD_HZ";
} else if ("库存".equals(tablename)) {
hzb = "BO_EU_DWD_ORDER_KC_HZ";
} else {
hzb = "BO_EU_DWD_ORDER_RKD_HZ";
}
// 查询子表字段映射配置
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", bindId)
.list();
//获取板块公司
String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS");
// 根据时间字段是否为空设置日期范围
Date startDate = null;
Date endDate = null;
// 删除目标表数据根据时间字段是否为空决定删除范围
if (timeField == null || timeField.isEmpty()) {
// 全量删除
String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'";
int deletedCount = DBSql.update(deleteSql);
LOGGER.info("已删除目标表[{}}]中{}条数据(时间范围: {} - {})",
hzb,deletedCount, startDate, endDate);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 全量分页迁移数据到汇总表
summarizeScopeData(targetTable, null, null, null, hzb);
} else {
// 计算时间范围当前日期-30天 ~ 昨天
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
endDate = cal.getTime();
cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前
startDate = cal.getTime();
// 获取目标表时间字段名
String targetTimeField = getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
}
// 按时间范围删除
String deleteSql = "DELETE FROM " + hzb +
" WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?";
int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate});
LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})",
hzb,deletedCount, startDate, endDate);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 按时间范围分页迁移数据到汇总表
summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb);
}
} catch (Exception e) {
LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}",
plate, mainConfig.getString("BINDID"), e.getMessage(), e);
}
}
}
}
/**
* 汇总各板块销售数据汇总
* @param targetTable
* @param startDated
* @param endDated
* @param targetTimeField
*/
@Override
public void summarizeScopeData(String targetTable, Date startDated, Date endDated, String targetTimeField, String hzb) {
int pageNo = 1;
boolean hasMore;
String pageSql = "";
List<RowMap> pageData = null;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startDate = "";
String endDate = "";
try {
do {
if (startDated == null || endDated == null) {
pageSql = "SELECT * FROM " + targetTable +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
pageData = DBSql.getMaps(pageSql);
} else {
startDate = simpleDateFormat.format(startDated);
endDate = simpleDateFormat.format(endDated);
pageSql = "SELECT * FROM " + targetTable +
" WHERE " + targetTimeField + " BETWEEN '" + startDate + "' AND '" + endDate + "' " +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
LOGGER.info("执行查询的sql{}", pageSql);
pageData = DBSql.getMaps(pageSql);
}
if (pageData.isEmpty()) break;
List<BO> bos = new ArrayList<>();
for (RowMap map : pageData) {
BO bo = new BO();
// 复制所有字段排除系统字段
for (String key : map.keySet()) {
if (!key.equalsIgnoreCase("ID") &&
!key.equalsIgnoreCase("ORGID") &&
!key.equalsIgnoreCase("CREATEDATE") &&
!key.equalsIgnoreCase("CREATEUSER") &&
!key.equalsIgnoreCase("UPDATEDATE") &&
!key.equalsIgnoreCase("UPDATEUSER") &&
!key.equalsIgnoreCase("ISEND") &&
!key.equalsIgnoreCase("BINDID")) {
if (StringUtils.isNotBlank(targetTimeField)) {
String targetTimeField1 = map.getString(targetTimeField);
Date parse = UtilDate.parse(targetTimeField1);
int year = UtilDate.getYear(parse);
String monthFormat = UtilDate.monthFormat(parse);
int day = UtilDate.getDay(parse);
bo.set("YEARMONTH", year + monthFormat);
bo.set("YEAR", year);
bo.set("MONTH", monthFormat);
bo.set("DAY", day);
}
bo.set(key, map.get(key));
}
}
bos.add(bo);
}
SDK.getBOAPI().createDataBO(hzb, bos, UserContext.fromUID("admin"));
LOGGER.info("已迁移{}条数据到汇总表(页号: {},时间范围: {} - {}",
bos.size(), pageNo, startDate, endDate);
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} while (hasMore);
}catch (Exception e){
LOGGER.error("汇总数据失败 [汇总表={}, 第几页={}]: {}",
hzb, PAGE_SIZE, e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,585 @@
package com.awspaas.user.apps.bnbm.datalinkup.service.impl;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.entity.Location;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* 销售汇总维度
*/
public class SaleCountDimensionImpl implements DataSummaryService {
private static final Logger LOGGER = LoggerFactory.getLogger(SaleCountDimensionImpl.class);
// 维度表名常量
/**
* 销售_营业收入_年月
*/
private static final String BO_EU_XS_YESR = "BO_EU_XS_YESR";
/**
* 销售_销量销额_年月
*/
private static final String BO_EU_XS_XLXE = "BO_EU_XS_XLXE";
/**
* 销售_应收账款_年月
*/
private static final String BO_EU_XS_YSZK = "BO_EU_XS_YSZK";
/**
* 销售_区域两金占比_年月
*/
private static final String BO_EU_XS_QYLJZB = "BO_EU_XS_QYLJZB";
// 原始数据表名
/**
* 销售_销售类_汇总表
*/
private static final String SALES_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ";
/**
* 销售_应收类_汇总
*/
private static final String RECEIVABLE_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_YSL";
/**
* 采购_库存_汇总
*/
private static final String BO_EU_DWD_ORDER_KC_HZ = "BO_EU_DWD_ORDER_KC_HZ";
// 日期格式化
private static final SimpleDateFormat YEAR_MONTH_FORMAT = new SimpleDateFormat("yyyy-MM");
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
// 批量处理大小
private static final int BATCH_SIZE = 1000;
// 批量分页大小
private static final int PAGE_SIZE = 1000;
@Override
public void calculateSummary(DateRange dateRange, BO mainConfig) {
try {
// 从主配置获取BKGS值
String bkgs = mainConfig.getString("BKGS");
if (bkgs == null || bkgs.isEmpty()) {
LOGGER.error("主配置中BKGS为空无法进行汇总计算");
return;
}
if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) {
LOGGER.info("未提供有效时间范围,按当前日期计算");
calculateForCurrentDate(bkgs);
} else {
LOGGER.info("开始执行销售数据多维度汇总计算(时间范围: {} 至 {})",
dateRange.getStartDate(), dateRange.getEndDate());
// 计算月度维度数据按月遍历
calculateMonthlyData(dateRange, bkgs);
LOGGER.info("销售数据多维度汇总计算完成");
}
} catch (Exception e) {
String errorMsg = "销售数据汇总计算失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 按当前日期计算无时间范围时使用
*/
private void calculateForCurrentDate(String bkgs) {
Calendar cal = Calendar.getInstance();
DateRange dateRange = new DateRange();
// 设置为当前月的第一天
cal.set(Calendar.DAY_OF_MONTH, 1);
dateRange.setStartDate(cal.getTime());
// 设置为当前月的最后一天
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
dateRange.setEndDate(cal.getTime());
calculateMonthlyData(dateRange, bkgs);
}
/**
* 计算月度维度数据
*/
private void calculateMonthlyData(DateRange dateRange, String bkgs) {
Connection conn = null;
try {
conn = DBSql.open();
conn.setAutoCommit(false);
// 获取时间范围内的所有月份
List<String> yearMonths = getYearMonthsBetweenDates(dateRange.getStartDate(), dateRange.getEndDate());
for (String yearMonth : yearMonths) {
LOGGER.info("开始处理{}月份的数据,板块公司: {}", yearMonth, bkgs);
// 1. 处理营业收入数据
LOGGER.info("开始营业收入数据");
processRevenueData(conn, yearMonth, bkgs);
// 2. 处理销量销额数据
LOGGER.info("开始销量销额数据");
processSalesVolumeData(conn, yearMonth, bkgs);
// 3. 处理应收账款数据
LOGGER.info("开始应收账款数据");
processReceivableData(conn, yearMonth, bkgs);
// 4. 处理区域两金占比
LOGGER.info("开始区域两金占比");
processRegionTwoFundsRatio(conn, yearMonth, bkgs);
}
conn.commit();
LOGGER.info("所有月份数据处理完成");
}catch (Exception e) {
try {
if (conn != null) conn.rollback();
} catch (Exception rollbackEx) {
LOGGER.error("回滚事务失败", rollbackEx);
}
String errorMsg = "月度数据处理失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
} finally {
DBSql.close(conn);
try {
LOGGER.info("销售数据汇总计算完成关闭连接conn状态为:{}",conn.getClientInfo());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 处理营业收入数据
*/
private void processRevenueData(Connection conn, String yearMonth, String bkgs) throws Exception {
LOGGER.info("开始处理营业收入数据,年月: {}, 板块公司: {}", yearMonth, bkgs);
int totalCount = 0;
// 删除已存在的记录
String deleteSql = "DELETE FROM " + BO_EU_XS_YESR + " WHERE YEARMONTH = ? AND BKGS = ?";
try {
int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs});
LOGGER.info("营业收入数据-已删除{}条营业收入记录", deleted);
}catch (Exception e){
LOGGER.error("营业收入-删除数据{}数据错误删除sql为{},请检查数据库链接:{}",bkgs,deleteSql,e.getMessage());
throw e;
}
// 获取公司位置信息
List<RowMap> companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD");
Map<String, Location> resultMap = companyList.stream()
.filter(row -> row.get("GSMC") != null)
.collect(Collectors.toMap(
row -> row.get("GSMC").toString(),
row -> new Location(
row.get("JD") != null ? row.get("JD").toString() : null,
row.get("WD") != null ? row.get("WD").toString() : null
),
(existing, replacement) -> existing
));
// 分页查询营业收入数据
int page = 0;
boolean hasMore = true;
while (hasMore) {
int offset = page * PAGE_SIZE;
String querySql = "SELECT QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY, " +
"SUM(SSJERMB) as YYSR " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEARMONTH = ? AND BKGS = ? " +
"GROUP BY QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY " +
"LIMIT " + PAGE_SIZE + " OFFSET " + offset;
LOGGER.debug("营业收入数据查询第{}页SQL: {}", page + 1, querySql);
List<RowMap> maps = DBSql.getMaps(conn, querySql, yearMonth.replace("-", ""), bkgs);
if (maps.isEmpty()) {
hasMore = false;
LOGGER.debug("营业收入数据第{}页无数据,停止分页查询", page + 1);
} else {
ArrayList<BO> bos = new ArrayList<>();
for (RowMap map : maps) {
String gc = map.getString("KCZZ");
BO bo = new BO();
bo.set("YEARMONTH", yearMonth);
bo.set("BKGS", bkgs);
bo.set("QYGS", map.getString("QYGS"));
bo.set("GC", gc);
bo.set("LB_1", map.getString("LB_1"));
bo.set("LB_2", map.getString("LB_2"));
bo.set("LB_3", map.getString("LB_3"));
bo.set("SQ", map.getString("SQ"));
bo.set("CITY", map.getString("CS"));
bo.set("QX", map.getString("QY"));
if (resultMap.containsKey(gc)) {
Location location = resultMap.get(gc);
bo.set("JD", location.getLongitude());
bo.set("WD", location.getLatitude());
}
bo.set("YYSR", map.getDouble("YYSR"));
bos.add(bo);
}
// 批量新增BO
if (!bos.isEmpty()) {
for (int i = 0; i < bos.size(); i += BATCH_SIZE) {
int end = Math.min(bos.size(), i + BATCH_SIZE);
List<BO> batchList = bos.subList(i, end);
SDK.getBOAPI().createDataBO(BO_EU_XS_YESR, batchList, UserContext.fromUID("admin"));
}
totalCount += bos.size();
LOGGER.info("营业收入数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount);
}
page++;
}
}
LOGGER.info("营业收入数据处理完成,共处理{}条记录", totalCount);
}
/**
* 处理销量销额数据分页查询
*/
private void processSalesVolumeData(Connection conn, String yearMonth, String bkgs) throws Exception {
LOGGER.info("开始处理销量销额数据,年月: {}, 板块公司: {}", yearMonth, bkgs);
int totalCount = 0;
// 删除已存在的记录
String deleteSql = "DELETE FROM " + BO_EU_XS_XLXE + " WHERE YEARMONTH = ? AND BKGS = ?";
try {
int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs});
LOGGER.info("销量销额-已删除{}条记录", deleted);
} catch (Exception e) {
LOGGER.error("销量销额-删除{}数据错误删除sql为{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage());
throw e;
}
// 获取公司位置信息
List<RowMap> companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD");
Map<String, Location> resultMap = companyList.stream()
.filter(row -> row.get("GSMC") != null)
.collect(Collectors.toMap(
row -> row.get("GSMC").toString(),
row -> new Location(
row.get("JD") != null ? row.get("JD").toString() : null,
row.get("WD") != null ? row.get("WD").toString() : null
),
(existing, replacement) -> existing
));
// 分页查询销量销额数据
int page = 0;
boolean hasMore = true;
while (hasMore) {
int offset = page * PAGE_SIZE;
String querySql = "SELECT QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY, " +
"SUM(ZSSL) as XL, SUM(SSJERMB) as XE ,(SUM(XSSL))/10000 AS TSXL " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEARMONTH = ? AND BKGS = ? " +
"GROUP BY QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY " +
"LIMIT " + PAGE_SIZE + " OFFSET " + offset;
LOGGER.debug("销量销额数据查询第{}页SQL: {}", page + 1, querySql);
List<RowMap> maps = DBSql.getMaps(conn, querySql, yearMonth.replace("-", ""), bkgs);
if (maps.isEmpty()) {
hasMore = false;
LOGGER.debug("销量销额数据第{}页无数据,停止分页查询", page + 1);
} else {
ArrayList<BO> bos = new ArrayList<>();
for (RowMap map : maps) {
String gc = map.getString("KCZZ");
BO bo = new BO();
bo.set("YEARMONTH", yearMonth);
bo.set("BKGS", bkgs);
bo.set("QYGS", map.getString("QYGS"));
bo.set("GC", gc);
bo.set("LB_1", map.getString("LB_1"));
bo.set("LB_2", map.getString("LB_2"));
bo.set("LB_3", map.getString("LB_3"));
bo.set("SQ", map.getString("SQ"));
bo.set("CITY", map.getString("CS"));
bo.set("QX", map.getString("QY"));
if (resultMap.containsKey(gc)) {
Location location = resultMap.get(gc);
bo.set("JD", location.getLongitude());
bo.set("WD", location.getLatitude());
}
if (bkgs.equals("泰山石膏") && "石膏板".equals(map.getString("LB_1"))){
bo.set("XL", map.getDouble("TSXL"));
}else {
bo.set("XL", map.getDouble("XL"));
}
bo.set("XE", map.getDouble("XE"));
bo.set("MC_JC", Math.random()%2==0?"面材":"基材");
bo.set("JZ_GZ", Math.random()%2==0?"家装":"工装");
bos.add(bo);
}
// 批量新增BO
if (!bos.isEmpty()) {
for (int i = 0; i < bos.size(); i += BATCH_SIZE) {
int end = Math.min(bos.size(), i + BATCH_SIZE);
List<BO> batchList = bos.subList(i, end);
SDK.getBOAPI().createDataBO(BO_EU_XS_XLXE, batchList, UserContext.fromUID("admin"));
}
totalCount += bos.size();
LOGGER.info("销量销额数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount);
}
page++;
}
}
LOGGER.info("销量销额数据处理完成,共处理{}条记录", totalCount);
}
/**
* 处理应收账款数据分页查询
*/
private void processReceivableData(Connection conn, String yearMonth, String bkgs) throws Exception {
LOGGER.info("开始处理应收账款数据,年月: {}, 板块公司: {}", yearMonth, bkgs);
int totalCount = 0;
// 删除已存在的记录
String deleteSql = "DELETE FROM " + BO_EU_XS_YSZK + " WHERE YEARMONTH = ? AND BKGS = ?";
try {
int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs});
LOGGER.info("应收账款-已删除{}条记录", deleted);
} catch (Exception e) {
LOGGER.error("应收账款-删除{}数据错误删除sql为{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage());
throw e;
}
// 获取指定年月最后一天
Calendar cal = Calendar.getInstance();
cal.setTime(YEAR_MONTH_FORMAT.parse(yearMonth));
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
String lastDayOfMonth = DATE_FORMAT.format(cal.getTime());
// 获取公司位置信息
List<RowMap> companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD");
Map<String, Location> resultMap = companyList.stream()
.filter(row -> row.get("GSMC") != null)
.collect(Collectors.toMap(
row -> row.get("GSMC").toString(),
row -> new Location(
row.get("JD") != null ? row.get("JD").toString() : null,
row.get("WD") != null ? row.get("WD").toString() : null
),
(existing, replacement) -> existing
));
// 分页查询应收账款数据
int page = 0;
boolean hasMore = true;
while (hasMore) {
int offset = page * PAGE_SIZE;
String querySql = "SELECT QYGS, XSZZ, SHENGQU, SHIQU, QX, QCYE, LJXS, LJHK, YSYE, " +
"ZLFX0_60, ZLFX60_1, ZLFX1_2, ZLFX2_3, ZLFX3_4, ZLFX4_5, ZLFX5 " +
"FROM " + RECEIVABLE_DETAIL_TABLE + " " +
"WHERE DATE(RQ) = ? AND BKGS = ? " +
"LIMIT " + PAGE_SIZE + " OFFSET " + offset;
LOGGER.debug("应收账款数据查询第{}页SQL: {}", page + 1, querySql);
List<RowMap> maps = DBSql.getMaps(conn, querySql, lastDayOfMonth, bkgs);
if (maps.isEmpty()) {
hasMore = false;
LOGGER.debug("应收账款数据第{}页无数据,停止分页查询", page + 1);
} else {
ArrayList<BO> bos = new ArrayList<>();
for (RowMap map : maps) {
String xszz = map.getString("XSZZ");
BO bo = new BO();
bo.set("YEARMONTH", yearMonth);
bo.set("BKGS", bkgs);
bo.set("QYGS", map.getString("QYGS"));
bo.set("GC", xszz);
bo.set("SQ", map.getString("SHENGQU"));
bo.set("CITY", map.getString("SHIQU"));
bo.set("QX", map.getString("QX"));
if (resultMap.containsKey(xszz)) {
Location location = resultMap.get(xszz);
bo.set("JD", location.getLongitude());
bo.set("WD", location.getLatitude());
}
bo.set("QCYE", map.getDouble("QCYE"));
bo.set("NFH", map.getDouble("LJXS"));
bo.set("NHK", map.getDouble("LJHK"));
bo.set("YSZE", map.getDouble("YSYE"));
bo.set("ZL60", map.getDouble("ZLFX0_60"));
bo.set("ZL60_1", map.getDouble("ZLFX60_1"));
bo.set("ZL1_2", map.getDouble("ZLFX1_2"));
bo.set("ZL2_3", map.getDouble("ZLFX2_3"));
bo.set("ZL3_4", map.getDouble("ZLFX3_4"));
bo.set("ZL4_5", map.getDouble("ZLFX4_5"));
bo.set("ZL5", map.getDouble("ZLFX5"));
bos.add(bo);
}
// 批量新增BO
if (!bos.isEmpty()) {
for (int i = 0; i < bos.size(); i += BATCH_SIZE) {
int end = Math.min(bos.size(), i + BATCH_SIZE);
List<BO> batchList = bos.subList(i, end);
SDK.getBOAPI().createDataBO(BO_EU_XS_YSZK, batchList, UserContext.fromUID("admin"));
}
totalCount += bos.size();
LOGGER.info("应收账款数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount);
}
page++;
}
}
LOGGER.info("应收账款数据处理完成,共处理{}条记录", totalCount);
}
/**
* 处理区域两金占比数据分页查询
*/
private void processRegionTwoFundsRatio(Connection conn, String yearMonth, String bkgs) throws Exception {
LOGGER.info("开始处理区域两金占比数据,年月: {}, 板块公司: {}", yearMonth, bkgs);
int totalCount = 0;
// 删除已存在的记录
String deleteSql = "DELETE FROM " + BO_EU_XS_QYLJZB + " WHERE YEARMONTH = ? AND BKGS = ?";
try {
int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs});
LOGGER.info("区域两金占比-已删除{}条记录", deleted);
} catch (Exception e) {
LOGGER.error("区域两金占比-删除{}数据错误删除sql为{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage());
throw e;
}
// 获取指定年月最后一天
Calendar cal = Calendar.getInstance();
cal.setTime(YEAR_MONTH_FORMAT.parse(yearMonth));
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
String lastDayOfMonth = DATE_FORMAT.format(cal.getTime());
// 分页查询区域两金占比数据
int page = 0;
boolean hasMore = true;
while (hasMore) {
int offset = page * PAGE_SIZE;
// 第一个SQL查询应收账款数据
String receivableSql = "SELECT QYGS, XSZZ, SUM(YSYE) as YSZK " +
"FROM " + RECEIVABLE_DETAIL_TABLE + " " +
"WHERE YEAR(RQ) = YEAR(?) AND MONTH(RQ) = MONTH(?) AND BKGS = ? " +
"GROUP BY QYGS, BKGS" +
"LIMIT " + PAGE_SIZE + " OFFSET " + offset;
LOGGER.debug("应收账款数据查询第{}页SQL: {}", page + 1, receivableSql);
List<RowMap> receivableMaps = DBSql.getMaps(conn, receivableSql,
lastDayOfMonth, lastDayOfMonth, bkgs);
if (receivableMaps.isEmpty()) {
hasMore = false;
LOGGER.debug("应收账款数据第{}页无数据,停止分页查询", page + 1);
} else {
ArrayList<BO> bos = new ArrayList<>();
// 收集所有销售组织用于库存查询
List<String> xszzList = new ArrayList<>();
Map<String, RowMap> receivableMap = new HashMap<>();
for (RowMap map : receivableMaps) {
String xszz = map.getString("XSZZ");
xszzList.add(xszz);
receivableMap.put(xszz, map);
}
// 第二个SQL查询库存金额数据
if (!xszzList.isEmpty()) {
String placeholders = String.join(",", Collections.nCopies(xszzList.size(), "?"));
String inventorySql = "SELECT STOCKORGNAME, SUM(BALANCE_AMOUNT) as KCJE " +
"FROM " + BO_EU_DWD_ORDER_KC_HZ + " " +
"WHERE STOCKORGNAME IN (" + placeholders + ") " +
"AND CATEGORY = '产成品' " +
"AND YEAR(INDATE) = YEAR(?) " +
"AND MONTH(INDATE) = MONTH(?) " +
"GROUP BY STOCKORGNAME";
LOGGER.debug("库存金额数据查询SQL: {}", inventorySql);
List<RowMap> inventoryMaps = DBSql.getMaps(conn, inventorySql, lastDayOfMonth,lastDayOfMonth);
Map<String, Double> inventoryMap = inventoryMaps.stream()
.collect(Collectors.toMap(
row -> row.getString("STOCKORGNAME"),
row -> row.getDouble("KCJE"),
(existing, replacement) -> existing
));
// 合并数据并创建BO对象
for (RowMap receivable : receivableMaps) {
String xszz = receivable.getString("XSZZ");
double yszk = receivable.getDouble("YSZK");
double kcje = inventoryMap.getOrDefault(xszz, 0.0);
double ljzb = 0.0;
if ((yszk + kcje) > 0) {
ljzb = (yszk / (yszk + kcje)) * 100;
}
BO bo = new BO();
bo.set("YEARMONTH", yearMonth);
bo.set("BKGS", bkgs);
bo.set("QYGS", receivable.getString("QYGS"));
bo.set("YSZK", yszk);
bo.set("KCJE", kcje);
bos.add(bo);
}
}
// 批量新增BO
if (!bos.isEmpty()) {
for (int i = 0; i < bos.size(); i += BATCH_SIZE) {
int end = Math.min(bos.size(), i + BATCH_SIZE);
List<BO> batchList = bos.subList(i, end);
SDK.getBOAPI().createDataBO(BO_EU_XS_QYLJZB, batchList, UserContext.fromUID("admin"));
}
totalCount += bos.size();
LOGGER.info("区域两金占比数据第{}页处理完成,本页{}条记录,累计{}条",
page + 1, bos.size(), totalCount);
}
page++;
}
}
LOGGER.info("区域两金占比数据处理完成,共处理{}条记录", totalCount);
}
/**
* 获取两个日期之间的所有年月
*/
private List<String> getYearMonthsBetweenDates(Date startDate, Date endDate) {
List<String> yearMonths = new ArrayList<>();
Calendar startCal = Calendar.getInstance();
startCal.setTime(startDate);
startCal.set(Calendar.DAY_OF_MONTH, 1);
Calendar endCal = Calendar.getInstance();
endCal.setTime(endDate);
endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH));
Calendar currentCal = (Calendar) startCal.clone();
while (!currentCal.after(endCal)) {
yearMonths.add(YEAR_MONTH_FORMAT.format(currentCal.getTime()));
currentCal.add(Calendar.MONTH, 1);
}
return yearMonths;
}
}

View File

@ -0,0 +1,827 @@
package com.awspaas.user.apps.bnbm.datalinkup.service.impl;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 一体化销售汇总计算实现类
*/
public class SaleDataSummaryServiceImpl implements DataSummaryService {
private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSummaryServiceImpl.class);
// 维度表名常量
/**
* 一体化_销售_产品配套率月度表
*/
private static final String MATCHING_RATE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_MATCHING_RATE_YEAR";
/**
* 一体化_销售_产品销售月明细表
*/
private static final String PRODUCT_MONTHLY_TABLE = "BO_EU_DATALINKUP_FACT_PRODUCT_MONTHLY";
/**
* 一体化_销售_应收账款品牌月明细表
*/
private static final String RECEIVABLE_BRAND_MONTHLY_TABLE = "BO_EU_DATALINKUP_FACT_RECEIVABLE_BRAND_MONTHLY";
/**
* 一体化_销售_应收账款月度汇总表
*/
private static final String RECEIVABLE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_RECEIVABLE_YEAR";
/**
* 一体化_销售_营业收入月度汇总
*/
private static final String REVENUE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_REVENUE_YEAR";
/**
* 一体化_销售_产品单价日明细表
*/
private static final String UNIT_PRICE_DAILY_TABLE = "BO_EU_DATALINKUP_FACT_UNIT_PRICE_DAILY";
/**
* 一体化_销售_净利润月度汇总
*/
private static final String NET_PROFIT_MONTHLY_TABLE = "BO_EU_DATALINKUP_JLR_MONTH";
// 原始数据表名
/**
* 销售_销售类_汇总表
*/
private static final String SALES_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ";
/**
* 销售_应收类_汇总
*/
private static final String RECEIVABLE_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_YSL";
@Override
public void calculateSummary(DateRange dateRange, BO mainConfig) {
try {
// 从主配置获取BKGS值
String bkgs = mainConfig.getString("BKGS");
if (bkgs == null || bkgs.isEmpty()) {
LOGGER.error("主配置中BKGS为空无法进行汇总计算");
return;
}
if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) {
LOGGER.info("未提供有效时间范围,按当前日期计算");
calculateForCurrentDate(bkgs);
} else {
LOGGER.info("开始执行销售数据多维度汇总计算(时间范围: {} 至 {})",
dateRange.getStartDate(), dateRange.getEndDate());
// 计算月度维度数据按月遍历
calculateMonthlyData(dateRange, bkgs);
// 计算日度维度数据按天遍历
calculateDailyData(dateRange, bkgs);
LOGGER.info("销售数据多维度汇总计算完成");
}
} catch (Exception e) {
String errorMsg = "销售数据汇总计算失败: " + e.getMessage();
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 按当前日期计算无时间范围时使用
*/
private void calculateForCurrentDate(String bkgs) {
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;
int day = cal.get(Calendar.DAY_OF_MONTH);
String yearMonth = String.format("%04d-%02d", year, month);
Date currentDate = new Date(System.currentTimeMillis());
// 1. 计算并保存产品配套率年度数据(按月存储)
calculateAndSaveMatchingRate(year, month, yearMonth, bkgs);
// 2. 计算并保存产品销售月明细数据
calculateAndSaveProductMonthly(year, month, yearMonth, bkgs);
// 3. 计算并保存应收账款品牌月明细
calculateAndSaveReceivableBrandMonthly(year, month, yearMonth, bkgs);
// 4. 计算并保存应收账款年度汇总(按月存储)
calculateAndSaveReceivableYear(year, month, yearMonth, bkgs);
// 5. 计算并保存营业收入年度汇总(按月存储)
calculateAndSaveRevenueYear(year, month, yearMonth, bkgs);
// 6. 计算并保存产品单价日明细
calculateAndSaveUnitPriceDaily(year, month, day, currentDate, bkgs);
}
/**
* 计算月度维度数据
*/
private void calculateMonthlyData(DateRange dateRange, String bkgs) {
Calendar startCal = Calendar.getInstance();
startCal.setTime(dateRange.getStartDate());
startCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为月份的第一天
Calendar endCal = Calendar.getInstance();
endCal.setTime(dateRange.getEndDate());
endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 设置为月份的最后一天
Calendar monthCal = (Calendar) startCal.clone();
while (!monthCal.after(endCal)) {
int year = monthCal.get(Calendar.YEAR);
int month = monthCal.get(Calendar.MONTH) + 1;
String yearMonth = String.format("%04d-%02d", year, month);
LOGGER.info("计算月度汇总数据: {}-{}", year, month);
// 1. 产品配套率年度数据
calculateAndSaveMatchingRate(year, month, yearMonth, bkgs);
// 2. 产品销售月明细数据
calculateAndSaveProductMonthly(year, month, yearMonth, bkgs);
// 3. 应收账款品牌月明细
calculateAndSaveReceivableBrandMonthly(year, month, yearMonth, bkgs);
// 4. 应收账款年度汇总
calculateAndSaveReceivableYear(year, month, yearMonth, bkgs);
// 5. 营业收入年度汇总
calculateAndSaveRevenueYear(year, month, yearMonth, bkgs);
// 移动到下个月
monthCal.add(Calendar.MONTH, 1);
}
}
/**
* 计算日度维度数据
*/
private void calculateDailyData(DateRange dateRange, String bkgs) {
Calendar dayCal = Calendar.getInstance();
dayCal.setTime(dateRange.getStartDate());
Calendar endCal = Calendar.getInstance();
endCal.setTime(dateRange.getEndDate());
while (!dayCal.after(endCal)) {
int year = dayCal.get(Calendar.YEAR);
int month = dayCal.get(Calendar.MONTH) + 1;
int day = dayCal.get(Calendar.DAY_OF_MONTH);
Date currentDate = dayCal.getTime();
LOGGER.info("计算日度汇总数据: {}-{}-{}", year, month, day);
// 6. 产品单价日明细
calculateAndSaveUnitPriceDaily(year, month, day, currentDate, bkgs);
// 下一天
dayCal.add(Calendar.DATE, 1);
}
}
// ==================== 各维度计算方法 ====================
/**
* 计算并保存产品配套率年度数据(按月存储)
* 板骨配套率 = 石膏板销售数量 / 轻钢龙骨销售数量
*/
private void calculateAndSaveMatchingRate(int year, int month, String yearMonth, String bkgs) {
try {
LOGGER.info("开始计算{}年{}月产品配套率数据", year, month);
// 获取石膏板年度销售量万平方米 - 从当年1月到当前月
BigDecimal gypsumSales = getSalesToMonth(year, month, "石膏板", bkgs);
// 获取轻钢龙骨年度销售量 - 从当年1月到当前月
BigDecimal keelSales = getSalesToMonth(year, month, "轻钢龙骨", bkgs);
// 计算板骨配套率保留4位小数
BigDecimal ratio = BigDecimal.ZERO;
if (keelSales.compareTo(BigDecimal.ZERO) != 0) {
// 使用精确除法并设置舍入模式
ratio = gypsumSales.divide(keelSales, 4, RoundingMode.HALF_UP);
}
// 创建BO对象并填充数据
BO matchingRateBO = createBaseBO(bkgs);
matchingRateBO.set("YEARMONTH", yearMonth);
matchingRateBO.set("GYPSUM_SALES_VOLUME", gypsumSales.setScale(2, RoundingMode.HALF_UP));
matchingRateBO.set("STEEL_SALES_VOLUME", keelSales.setScale(2, RoundingMode.HALF_UP));
matchingRateBO.set("MATCHING_RATE", ratio.setScale(4, RoundingMode.HALF_UP));
// 保存数据
saveSummaryData(matchingRateBO, MATCHING_RATE_YEAR_TABLE);
LOGGER.info("产品配套率月度累计数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("产品配套率月度累计数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 计算并保存产品销售月明细数据
* 包含石膏板和轻钢龙骨的销量营收及占比计算
*/
private void calculateAndSaveProductMonthly(int year, int month, String yearMonth, String bkgs) {
try {
LOGGER.info("开始计算{}年{}月产品销售明细数据", year, month);
List<RowMap> maps = DBSql.getMaps("SELECT LB_1 FROM " + SALES_DETAIL_TABLE + " WHERE BKGS = '" + bkgs + "'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询
" GROUP BY LB_1");
for (RowMap map : maps) {
String lb_1 = map.getString("LB_1");
processProductData(lb_1, year, month, yearMonth, bkgs);
}
// 处理石膏板数据
// processProductData("石膏板", year, month, yearMonth, bkgs);
// 处理轻钢龙骨数据
// processProductData("轻钢龙骨", year, month, yearMonth, bkgs);
LOGGER.info("产品销售月明细数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("产品销售月明细数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 处理指定产品的月明细数据
* 计算各品牌的销量/营收总量占比
*/
private void processProductData(String productType, int year, int month, String yearMonth, String bkgs) {
try {
// 获取销量数据
Map<String, BigDecimal> salesMap = getMonthlyData(year, month, productType, "sales", bkgs);
BigDecimal totalSales = calculateTotal(salesMap);
// 获取营收数据
Map<String, BigDecimal> revenueMap = getMonthlyData(year, month, productType, "revenue", bkgs);
BigDecimal totalRevenue = calculateTotal(revenueMap);
// 为每个品牌创建记录
for (String brand : salesMap.keySet()) {
BO productBO = createBaseBO(bkgs);
productBO.set("YEARMONTH", yearMonth);
if ("北新嘉宝莉".equals(bkgs)) {
if ("其他".equals(productType)){
productBO.set("PRODUCT_TYPE", "其他");
}else {
productBO.set("PRODUCT_TYPE", "涂料");
}
} else if ("石膏板".equals(productType) || "轻钢龙骨".equals(productType) ) {
productBO.set("PRODUCT_TYPE", productType);
} else {
productBO.set("PRODUCT_TYPE", "其他");
}
productBO.set("BRAND", brand);
// 设置销量及占比
BigDecimal brandSales = salesMap.get(brand);
productBO.set("SALES_VOLUME", brandSales.setScale(2, RoundingMode.HALF_UP));
// 计算销量占比百分比保留2位小数
if (totalSales.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal salesRatio = brandSales.divide(totalSales, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
productBO.set("SALES_VOLUME_RATIO", salesRatio.setScale(2, RoundingMode.HALF_UP));
} else {
productBO.set("SALES_VOLUME_RATIO", BigDecimal.ZERO);
}
// 设置营收及占比
BigDecimal brandRevenue = revenueMap.get(brand);
productBO.set("REVENUE_AMOUNT", brandRevenue.setScale(2, RoundingMode.HALF_UP));
// 计算营收占比百分比保留2位小数
if (totalRevenue.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal revenueRatio = brandRevenue.divide(totalRevenue, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
productBO.set("REVENUE_RATIO", revenueRatio.setScale(2, RoundingMode.HALF_UP));
} else {
productBO.set("REVENUE_RATIO", BigDecimal.ZERO);
}
saveSummaryData(productBO, PRODUCT_MONTHLY_TABLE);
}
} catch (Exception e) {
String errorMsg = String.format("处理产品[%s]月明细数据失败: %s", productType, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 计算并保存应收账款品牌月明细
* 区分板骨龙牌/泰山/梦牌和涂料北新涂料/嘉宝莉
*/
private void calculateAndSaveReceivableBrandMonthly(int year, int month, String yearMonth, String bkgs) {
try {
LOGGER.info("开始计算{}年{}月应收账款品牌明细数据", year, month);
// 处理应收账款
processReceivableData(getBoneReceivable(year, month, bkgs), yearMonth, bkgs);
// 处理涂料应收账款
// processReceivableData(getPaintReceivable(year, month, bkgs), yearMonth, bkgs);
LOGGER.info("应收账款品牌月明细数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("应收账款品牌月明细数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 处理指定产品线的应收账款数据
*/
private void processReceivableData(Map<String, BigDecimal> dataMap, String yearMonth, String bkgs) {
try {
for (Map.Entry<String, BigDecimal> entry : dataMap.entrySet()) {
BO receivableBO = createBaseBO(bkgs);
receivableBO.set("YEARMONTH", yearMonth);
// receivableBO.set("PRODUCT_LINE", productLine);
receivableBO.set("BRAND", entry.getKey());
receivableBO.set("RECEIVABLE_AMOUNT", entry.getValue().setScale(2, RoundingMode.HALF_UP));
saveSummaryData(receivableBO, RECEIVABLE_BRAND_MONTHLY_TABLE);
}
} catch (Exception e) {
String errorMsg = String.format("处理板块[%s]应收账款失败: %s", bkgs, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 计算并保存应收账款年度汇总(按月存储)
* 应收账款 = 期初余额 + 累计销售 - 累计回款
*/
private void calculateAndSaveReceivableYear(int year, int month, String yearMonth, String bkgs) {
try {
LOGGER.info("开始计算{}年{}月应收账款累计数据", year, month);
int lastYear = year - 1;
int lastYearMonth = month; // 去年同月
// 获取板骨应收账款数据 - 当年1月到当前月
BigDecimal boneTotal = getReceivableToMonth(year, month, bkgs);
// 获取去年同期的板骨应收账款数据 - 去年1月到去年同月
BigDecimal boneLastYearTotal = getReceivableToMonth(lastYear, lastYearMonth, bkgs);
BigDecimal boneYoy = calculateYoy(boneTotal, boneLastYearTotal);
// 获取涂料应收账款数据 - 当年1月到当前月
BigDecimal paintTotal = getReceivableToMonth(year, month, bkgs);
// 获取去年同期的涂料应收账款数据 - 去年1月到去年同月
BigDecimal paintLastYearTotal = getReceivableToMonth(lastYear, lastYearMonth, bkgs);
BigDecimal paintYoy = calculateYoy(paintTotal, paintLastYearTotal);
// 保存板骨数据
saveReceivableYearData(yearMonth, boneTotal, boneYoy, bkgs);
// 保存涂料数据
saveReceivableYearData(yearMonth, paintTotal, paintYoy, bkgs);
LOGGER.info("应收账款月度累计数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("应收账款月度累计数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 保存应收账款月度累计数据
*/
private void saveReceivableYearData(String yearMonth, BigDecimal total, BigDecimal yoy, String bkgs) {
BO receivableBO = createBaseBO(bkgs);
receivableBO.set("YEARMONTH", yearMonth);
// receivableBO.set("RECEIVABLE_TYPE", type);
receivableBO.set("ENDING_BALANCE", total.setScale(2, RoundingMode.HALF_UP)); // 期末余额
receivableBO.set("YOY_RATE", yoy.setScale(2, RoundingMode.HALF_UP));
saveSummaryData(receivableBO, RECEIVABLE_YEAR_TABLE);
}
/**
* 计算并保存营业收入年度汇总(按月存储)
* 营业收入总量 = 石膏板 + 轻钢龙骨 + 涂料
* 同比 = (当年营收 - 去年营收) / 去年营收 * 100%
*/
private void calculateAndSaveRevenueYear(int year, int month, String yearMonth, String bkgs) {
try {
LOGGER.info("开始计算{}年{}月营业收入累计数据", year, month);
int lastYear = year - 1;
int lastYearMonth = month; // 去年同月
// 处理石膏板数据 - 当年1月到当前月
saveRevenueYearData(yearMonth, "石膏板",
getRevenueToMonth(year, month, "石膏板", bkgs),
getRevenueToMonth(lastYear, lastYearMonth, "石膏板", bkgs),
bkgs);
// 处理轻钢龙骨数据 - 当年1月到当前月
saveRevenueYearData(yearMonth, "轻钢龙骨",
getRevenueToMonth(year, month, "轻钢龙骨", bkgs),
getRevenueToMonth(lastYear, lastYearMonth, "轻钢龙骨", bkgs),
bkgs);
// 处理涂料数据 - 当年1月到当前月
saveRevenueYearData(yearMonth, "涂料",
getRevenueToMonth(year, month, "涂料", bkgs),
getRevenueToMonth(lastYear, lastYearMonth, "涂料", bkgs),
bkgs);
LOGGER.info("营业收入月度累计数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("营业收入月度累计数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 保存营业收入月度累计数据
*/
private void saveRevenueYearData(String yearMonth, String category,
BigDecimal currentRevenue, BigDecimal lastRevenue, String bkgs) {
BigDecimal yoy = calculateYoy(currentRevenue, lastRevenue);
BO revenueBO = createBaseBO(bkgs);
revenueBO.set("YEARMONTH", yearMonth);
if ("北新嘉宝莉".equals(bkgs)){
if ("其他".equals(category)){
revenueBO.set("PRODUCT_CATEGORY", category);
}else {
revenueBO.set("PRODUCT_CATEGORY", "涂料");
}
} else if ("石膏板".equals(category) || "轻钢龙骨".equals(category)) {
revenueBO.set("PRODUCT_CATEGORY", category);
}else {
revenueBO.set("PRODUCT_CATEGORY", "其他");
}
revenueBO.set("REVENUE_AMOUNT", currentRevenue.setScale(2, RoundingMode.HALF_UP));
revenueBO.set("YOY_RATE", yoy.setScale(2, RoundingMode.HALF_UP));
saveSummaryData(revenueBO, REVENUE_YEAR_TABLE);
}
/**
* 计算并保存产品单价日明细
* 单价 = 营业收入 / 销售数量
*/
private void calculateAndSaveUnitPriceDaily(int year, int month, int day, Date date, String bkgs) {
try {
LOGGER.info("开始计算{}-{}-{}产品单价日明细数据", year, month, day);
List<RowMap> maps = DBSql.getMaps("SELECT LB_1 FROM " + SALES_DETAIL_TABLE + " WHERE BKGS = '" + bkgs + "'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询
" GROUP BY LB_1");
for (RowMap map : maps) {
String lb_1 = map.getString("LB_1");
saveUnitPriceData(date, lb_1, year, month, day, bkgs);
}
// 处理石膏板数据
// saveUnitPriceData(date, "石膏板", year, month, day, bkgs);
// 处理轻钢龙骨数据
// saveUnitPriceData(date, "轻钢龙骨", year, month, day, bkgs);
LOGGER.info("产品单价日明细数据保存成功");
} catch (Exception e) {
String errorMsg = String.format("产品单价日明细数据计算失败: %s", e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 保存产品单价数据支持日/月计算
*/
private void saveUnitPriceData(Date date, String productType, int year, int month, int day, String bkgs) {
try {
// 获取销量和营收
Object[] params = {year, month, productType, bkgs};
String sql = "";
if (bkgs.contains("龙牌")) {
sql = "SELECT SUM(ZSSL)*10000 AS sales, SUM(SSJERMB) AS revenue " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询
} else if (bkgs.contains("泰山")) {
sql = "SELECT SUM(XSSL) AS sales, SUM(SSJERMB) AS revenue " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询
}else {
sql = "SELECT SUM(ZSSL) AS sales, SUM(SSJERMB) AS revenue " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询
}
if (day > 0) {
sql += " AND DAY(DZRQ) = '"+day+"'";
params = new Object[]{year, month, day, productType,bkgs};
}
RowMap result = DBSql.getMap(sql);
System.out.println("result = " + result);
if (result != null && result.get("sales") != null && result.get("revenue") != null) {
BigDecimal sales = BigDecimal.valueOf(result.getDouble("sales"));
BigDecimal revenue = BigDecimal.valueOf(result.getDouble("revenue"));
BigDecimal unitPrice = BigDecimal.ZERO;
// 计算单价避免除零错误
if (sales.compareTo(BigDecimal.ZERO) > 0) {
// 石膏板单位转换万元/万平方米 /平方米
if ("石膏板".equals(productType)) {
unitPrice = revenue.divide(sales, 4, RoundingMode.HALF_UP);
}
// 轻钢龙骨单位/
// else
if ("轻钢龙骨".equals(productType)) {
unitPrice = revenue.divide(sales, 2, RoundingMode.HALF_UP);
}
}
BO priceBO = createBaseBO(bkgs);
priceBO.set("DATE", new Timestamp(date.getTime()));
if ("北新嘉宝莉".equals(bkgs)) {
priceBO.set("PRODUCT_TYPE", "涂料");
}else {
priceBO.set("PRODUCT_TYPE", productType);
}
priceBO.set("SALES_VOLUME", sales.setScale(2, RoundingMode.HALF_UP));
priceBO.set("REVENUE_AMOUNT", revenue.setScale(2, RoundingMode.HALF_UP));
priceBO.set("UNIT_PRICE", unitPrice.setScale(2, RoundingMode.HALF_UP));
saveSummaryData(priceBO, UNIT_PRICE_DAILY_TABLE);
} else {
LOGGER.warn("未找到{}-{}-{}的{}销售数据", year, month, day, productType);
}
} catch (Exception e) {
String errorMsg = String.format("计算产品[%s]单价失败: %s", productType, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
// ==================== 工具方法 ====================
/**
* 创建基础BO对象填充公共字段
*/
private BO createBaseBO(String bkgs) {
BO bo = new BO();
// bo.set("ORGID", "");
// bo.set("BINDID", "");
// bo.set("CREATEDATE", new Timestamp(System.currentTimeMillis()));
// bo.set("CREATEUSER", "admin");
// bo.set("PROCESSDEFID", "");
// bo.set("ISEND", 0);
bo.set("BKGS", bkgs); // 新增BKGS字段
return bo;
}
/**
* 计算同比变化率
* 公式(当期 - 同期) / 同期 * 100%
*/
private BigDecimal calculateYoy(BigDecimal current, BigDecimal last) {
if (last == null || last.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return current.subtract(last)
.divide(last, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
}
/**
* 计算总值
*/
private BigDecimal calculateTotal(Map<String, BigDecimal> dataMap) {
return dataMap.values().stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// ==================== 数据访问方法 ====================
/**
* 获取产品从1月到指定月份的累计销售量
*/
private BigDecimal getSalesToMonth(int year, int month, String productType, String bkgs) {
String sql = "SELECT SUM(ZSSL) AS sales " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = '"+year+"' AND MONTH(DZRQ) BETWEEN 1 AND '"+month+"' AND LB_1 = '"+productType+"' AND BKGS = '"+bkgs+"'" +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询
double value = DBSql.getDouble(sql, "sales");
return BigDecimal.valueOf(value);
}
/**
* 获取产品从1月到指定月份的累计营收
*/
private BigDecimal getRevenueToMonth(int year, int month, String category, String bkgs) {
String sql = "SELECT SUM(SSJERMB) AS revenue " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = '"+year+"' AND MONTH(DZRQ) BETWEEN 1 AND '"+month+"' AND LB_1 LIKE '%"+category+"%' AND BKGS = '"+bkgs+"'"+
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询
double value = DBSql.getDouble(sql, "revenue");
return BigDecimal.valueOf(value);
}
/**
* 获取应收账款从1月到指定月份的累计值
* 公式期初余额 + 累计销售 - 累计回款
*/
private BigDecimal getReceivableToMonth(int year, int month, String bkgs) {
StringBuilder sql = new StringBuilder("SELECT (SUM(QCYE) + SUM(LJXS) - SUM(LJXS)) AS receivable ")
.append("FROM " + RECEIVABLE_DETAIL_TABLE + " ")
.append("WHERE YEAR(RQ) = '"+year+"' AND MONTH(RQ) BETWEEN 1 AND '"+month+"' ")
.append("AND BKGS = '"+bkgs+"' ");
// 构建品牌列表参数占位符
// for (int i = 0; i < brands.size(); i++) {
// sql.append(brands.get(i));
// if (i < brands.size() - 1) sql.append(",");
// }
// sql.append(")");
double value = DBSql.getDouble(sql.toString(), "receivable");
return BigDecimal.valueOf(value);
}
/**
* 获取月维度产品数据销量/营收
*/
private Map<String, BigDecimal> getMonthlyData(int year, int month, String productType, String dataType, String bkgs) {
String field = "";
String sql = "";
if (bkgs.contains("泰山")){
field = "sales".equals(dataType) ? "SUM(XSSL)/10000" : "SUM(SSJERMB)";
sql = "SELECT LB_2 AS brand, " + field + " AS value " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = ? AND MONTH(DZRQ) = ? AND LB_1 = ? AND BKGS = ? " +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询
"GROUP BY LB_2";
}else {
field = "sales".equals(dataType) ? "SUM(ZSSL)" : "SUM(SSJERMB)";
sql = "SELECT LB_2 AS brand, " + field + " AS value " +
"FROM " + SALES_DETAIL_TABLE + " " +
"WHERE YEAR(DZRQ) = ? AND MONTH(DZRQ) = ? AND LB_1 = ? AND BKGS = ? " +
"AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询
"GROUP BY LB_2";
}
Map<String, BigDecimal> result = new HashMap<>();
List<RowMap> rows = DBSql.getMaps(sql, year, month, productType, bkgs);
for (RowMap row : rows) {
if (row.get("value") != null) {
result.put(row.getString("brand"), BigDecimal.valueOf(row.getDouble("value")));
}
}
return result;
}
/**
* 获取应收账款数据分品牌
*/
private Map<String, BigDecimal> getReceivableData(int year, int month, String bkgs) {
StringBuilder sql = new StringBuilder("SELECT SUM(YSYE) AS receivable ")
.append("FROM " + RECEIVABLE_DETAIL_TABLE + " ")
.append("WHERE YEAR(RQ) = '"+year+"' AND MONTH(RQ) = '"+month+"' AND BKGS = '"+bkgs+"' ");
// for (int i = 0; i < brands.size(); i++) {
// sql.append(brands.get(i));
// if (i < brands.size() - 1) sql.append(",");
// }
// 构建参数数组
// Object[] params = new Object[brands.size() + 3];
// params[0] = year;
// params[1] = month;
// params[2] = bkgs;
// for (int i = 0; i < brands.size(); i++) {
// params[i + 3] = brands.get(i);
// }
Map<String, BigDecimal> result = new HashMap<>();
List<RowMap> rows = DBSql.getMaps(sql.toString());
for (RowMap row : rows) {
if (row.get("receivable") != null) {
result.put(bkgs, BigDecimal.valueOf(row.getDouble("receivable")));
}
}
return result;
}
private Map<String, BigDecimal> getBoneReceivable(int year, int month, String bkgs) {
return getReceivableData(year, month, bkgs);
}
private Map<String, BigDecimal> getPaintReceivable(int year, int month, String bkgs) {
return getReceivableData(year, month, bkgs);
}
// ==================== 数据保存方法 ====================
/**
* 保存汇总数据到指定表
*/
private void saveSummaryData(BO summary, String tableName) {
try {
// 根据表名构建唯一性查询条件
String whereClause = buildUniqueCondition(tableName, summary);
if (whereClause == null) {
SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin"));
LOGGER.debug("{} 表创建新数据: {}", tableName, summary);
return;
}
// 查询已存在记录
String sql = "SELECT * FROM " + tableName + " WHERE " + whereClause;
LOGGER.info("查询已存在sql{}",sql);
RowMap existing = DBSql.getMap(sql);
if (existing!=null){
if (StringUtils.isNotBlank(existing.getString("ID"))) {
// 更新现有记录
String id = existing.getString("ID");
summary.set("ID", id);
summary.set("ORGID", existing.getString("ORGID"));
summary.set("BINDID", existing.getString("BINDID"));
summary.set("CREATEDATE", existing.getString("CREATEDATE"));
summary.set("CREATEUSER", existing.getString("CREATEUSER"));
summary.set("UPDATEDATE",new Timestamp(System.currentTimeMillis()));
summary.set("PROCESSDEFID", existing.getString("PROCESSDEFID"));
summary.set("ISEND", existing.getString("ISEND"));
SDK.getBOAPI().update(tableName, summary);
LOGGER.debug("{} 表数据更新成功: ID={}", tableName, id);
} else {
// 创建新记录
SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin"));
LOGGER.debug("{} 表创建新数据: {}", tableName, summary);
}
}
} catch (Exception e) {
String errorMsg = String.format("%s 表数据保存失败: %s", tableName, e.getMessage());
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
// try {
// SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin"));
// LOGGER.debug("{} 表数据保存成功: {}", tableName, summary);
// } catch (Exception e) {
// String errorMsg = String.format("%s 表数据保存失败: %s", tableName, e.getMessage());
// LOGGER.error(errorMsg, e);
// throw new RuntimeException(errorMsg, e);
// }
}
/**
* 构建表唯一性条件基于业务键
*/
private String buildUniqueCondition(String tableName, BO bo) {
String bkgs = bo.getString("BKGS");
switch (tableName) {
case MATCHING_RATE_YEAR_TABLE:
return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'";
case PRODUCT_MONTHLY_TABLE:
return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" +
" AND PRODUCT_TYPE = '" + bo.getString("PRODUCT_TYPE") + "'" +
" AND BRAND = '" + bo.getString("BRAND") + "'";
case RECEIVABLE_BRAND_MONTHLY_TABLE:
return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" +
// " AND PRODUCT_LINE = '" + bo.getString("PRODUCT_LINE") + "'" +
" AND BRAND = '" + bo.getString("BRAND") + "'";
case RECEIVABLE_YEAR_TABLE:
return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'";
// +
// " AND RECEIVABLE_TYPE = '" + bo.getString("RECEIVABLE_TYPE") + "'";
case REVENUE_YEAR_TABLE:
return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" +
" AND PRODUCT_CATEGORY = '" + bo.getString("PRODUCT_CATEGORY") + "'";
case UNIT_PRICE_DAILY_TABLE:
// 日期字段特殊处理精确到天
Date date = bo.get("DATE", Date.class);
LOGGER.info("日期字段特殊处理(精确到天):{}",date);
String dateValue = new SimpleDateFormat("yyyy-MM-dd").format(date);
return "BKGS = '" + bkgs + "' AND DATE = '" + dateValue + "'" +
" AND PRODUCT_TYPE = '" + bo.getString("PRODUCT_TYPE") + "'";
default:
LOGGER.warn("表 {} 未配置唯一性条件,直接创建新记录", tableName);
return null;
}
}
}

View File

@ -0,0 +1,855 @@
package com.awspaas.user.apps.bnbm.datalinkup.service.impl;
import cn.hutool.core.date.DateTime;
import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.commons.database.DBUtils;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.bpms.util.UtilDate;
import com.actionsoft.sdk.local.SDK;
import com.actionsoft.sdk.local.api.cc.RDSAPI;
import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange;
import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.stream.Collectors;
/**
* @ClassName: SaleDataSyncServiceImpl
* @Description: 销售数据同步服务实现类处理数据同步的核心逻辑
*/
public class SaleDataSyncServiceImpl implements DataSyncService {
private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSyncServiceImpl.class);
// 时间范围常量同步最近30天数据不包括当天
private static final int DAYS_BACK = Integer.parseInt(SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "days_back"));
// 高斯数据库配置常量生产环境中应改为从配置文件读取
// private static final String GAUSSIAN_JDBC_URL = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_url");
// private static final String GAUSSIAN_USERNAME = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_act");
// private static final String GAUSSIAN_PASSWORD = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_pw");
// 增加分页大小常量
private static final int PAGE_SIZE = 1000; // 每页查询1000条记录
private static final String ORACLE_DATE_FORMAT = "YYYY-MM-DD HH24:MI:SS";
/**
* 根据主配置列表执行数据同步
* @param configs 主配置列表按所属板块分组处理
*/
@Override
public ArrayList<DateRange> syncDataByConfigs(List<BO> configs) {
ArrayList<DateRange> list = new ArrayList<>();
if (configs.isEmpty()) {
LOGGER.info("未找到有效的同步配置");
return list;
}
// 按所属板块(SSBK)分组配置
Map<String, List<BO>> configsByPlate = configs.stream()
.collect(Collectors.groupingBy(bo -> bo.getString("SSBK")));
// 遍历处理每个板块的配置
for (Map.Entry<String, List<BO>> entry : configsByPlate.entrySet()) {
String plate = entry.getKey();
List<BO> plateConfigs = entry.getValue();
LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size());
// 处理当前板块的每条配置
boolean connectionFailed = false;
String errorMsg = "";
for (BO mainConfig : plateConfigs) {
try {
// RDSAPI ccId = SDK.getCCAPI().getRDSAPI(mainConfig.getString("CC_ID"));
// Connection open = ccId.open();
// open.close();
DateRange dateRange = processMainConfig(mainConfig);
list.add(dateRange);
} catch (Exception e) {
LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}",
plate, mainConfig.getString("BINDID"), e.getMessage(), e);
}
}
}
return list;
}
/**
* 处理单个主配置的数据同步流程
* @param mainConfig 主配置对象
* 步骤
* 1. 获取字段映射配置
* 2. 计算时间范围
* 3. 删除目标表旧数据
* 4. 查询源表数据
* 5. 转换并插入目标表
*/
@Override
public DateRange processMainConfig(BO mainConfig) {
String bindId = mainConfig.getString("BINDID");
String tableName = mainConfig.getString("TBB");
String timeField = mainConfig.getString("SJZD");
String targetTable = mainConfig.getString("LDB");
String ccId = mainConfig.getString("CC_ID");
String partitionField = mainConfig.getString("FQBZD");
String bkgs = mainConfig.getString("BKGS");
DateRange dateRange = new DateRange();
LOGGER.info("处理配置BindID={}, 源表={}, 目标表={}, CC_ID={}, 时间字段={}, 分区字段配置={}",
bindId, tableName, targetTable, ccId,timeField,partitionField);
// 查询子表字段映射配置
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", bindId)
.list();
if (fieldMappings.isEmpty()) {
LOGGER.warn("未找到BindID={}的字段映射配置", bindId);
return dateRange;
}
// 根据时间字段是否为空设置日期范围
Date startDate = null;
Date endDate = null;
// 删除目标表数据根据时间字段是否为空决定删除范围
if (timeField == null || timeField.isEmpty()) {
// 全量删除
deleteAllTargetData(targetTable);
} else {
// 计算时间范围当前日期-30天 ~ 昨天
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
endDate = cal.getTime();
cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前
startDate = cal.getTime();
// 获取目标表时间字段名
String targetTimeField = getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
return dateRange;
}
// 按时间范围删除
deleteTargetData(targetTable, targetTimeField, startDate, endDate);
}
// 新增判断是否为高斯数据库
// if ("gaosi".equalsIgnoreCase(ccId)) {
// LOGGER.info("检测到高斯数据库特殊配置使用JDBC连接池查询");
// queryGaussDataWithCondition(tableName, timeField, startDate, endDate, partitionField,
// fieldMappings, targetTable);
// } else {
// 查询源表数据跨库查询
querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField,
fieldMappings, targetTable);
// }
dateRange.setStartDate(startDate);
dateRange.setEndDate(endDate);
return dateRange;
}
/**
* 跨库查询源表数据
* @param ccId 跨库连接ID
* @param tableName 源表名
* @param timeField 源表时间字段名
* @param startDated 开始时间
* @param endDated 结束时间
* @return 查询结果数据集
* @throws RuntimeException 查询失败或参数无效时抛出
*/
@Override
public void querySourceData(String ccId, String tableName,
String timeField, Date startDated, Date endDated,String partitionField,
List<BO> fieldMappings, String targetTable) {
int totalRows = 0; // 总查询行数
int totalSuccess = 0; // 总成功插入行数
int pageNo = 1;
boolean hasMore;
RDSAPI rdsapi = null;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startDate = simpleDateFormat.format(startDated);
String endDate = simpleDateFormat.format(endDated);
try {
rdsapi = SDK.getCCAPI().getRDSAPI(ccId);
DBUtils.SUPPLY supply = rdsapi.getSupply();
String DBname = supply.getName();
LOGGER.info("数据库为:{}",DBname);
if ("ORACLE".equalsIgnoreCase(DBname)){
// 构建查询条件
StringBuilder conditionBuilder = new StringBuilder();
List<Object> params = new ArrayList<>(); // 存储查询参数
// 分区字段和时间字段组合查询条件
if (partitionField != null && !partitionField.isEmpty()) {
// 1. 查询最大分区值
String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName;
List<RowMap> maxPartitionResult = rdsapi.getMaps(maxPartitionSql);
if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) {
LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField);
return;
}
String maxPartition = maxPartitionResult.get(0).getString("max_partition");
LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition);
// 添加分区条件
conditionBuilder.append(partitionField)
.append(" = '")
.append(maxPartition)
.append("'");
// 如果时间字段存在添加时间范围条件
if (timeField != null && !timeField.isEmpty()) {
conditionBuilder.append(" AND TO_DATE(")
.append(timeField)
.append(", '")
.append(ORACLE_DATE_FORMAT)
.append("') BETWEEN TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("') AND TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("')");
params.add(startDate);
params.add(endDate);
}
} else if (timeField != null && !timeField.isEmpty()) {
// 没有分区字段但时间字段存在使用时间范围条件
// 仅时间范围条件使用占位符
conditionBuilder.append("TO_DATE(")
.append(timeField)
.append(", '")
.append(ORACLE_DATE_FORMAT)
.append("') BETWEEN TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("') AND TO_DATE(?, '")
.append(ORACLE_DATE_FORMAT)
.append("')");
params.add(startDate);
params.add(endDate);
} else {
// 既没有分区字段也没有时间字段查询全表
LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!");
conditionBuilder.append("1=1");
}
// 分页查询数据
do {
// 使用Oracle分页语法 (12c+)
String querySql = "SELECT * FROM ( " +
"SELECT t.*, ROWNUM rn FROM " + tableName + " t " +
"WHERE " + conditionBuilder.toString() + " AND ROWNUM <= " + (pageNo * PAGE_SIZE) +
") WHERE rn > " + ((pageNo - 1) * PAGE_SIZE);
LOGGER.debug("执行Oracle查询: {}", querySql);
List<RowMap> pageData;
// 根据条件类型执行查询
if (partitionField != null && !partitionField.isEmpty() &&
timeField != null && !timeField.isEmpty()) {
// 分区+时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else if (timeField != null && !timeField.isEmpty()) {
// 仅时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else {
// 无时间范围查询仅分区或全表
pageData = rdsapi.getMaps(querySql);
}
if (pageData != null && !pageData.isEmpty()) {
// 直接处理当前页数据
int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable);
totalRows += pageData.size();
totalSuccess += successCount;
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} else {
hasMore = false;
}
} while (hasMore);
}else {
// 构建查询条件
StringBuilder conditionBuilder = new StringBuilder();
// 修改点分区字段和时间字段组合查询条件
if (partitionField != null && !partitionField.isEmpty()) {
// 1. 查询最大分区值
String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName;
List<RowMap> maxPartitionResult = rdsapi.getMaps(maxPartitionSql);
if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) {
LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField);
return;
}
String maxPartition = maxPartitionResult.get(0).getString("max_partition");
LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition);
// 添加分区条件
conditionBuilder.append(partitionField)
.append(" = '")
.append(maxPartition)
.append("'");
// 如果时间字段存在添加时间范围条件
if (timeField != null && !timeField.isEmpty()) {
conditionBuilder.append(" AND ")
.append(timeField)
.append(" BETWEEN ? AND ?");
}
} else if (timeField != null && !timeField.isEmpty()) {
// 没有分区字段但时间字段存在使用时间范围条件
conditionBuilder.append(timeField)
.append(" BETWEEN ? AND ?");
} else {
// 既没有分区字段也没有时间字段查询全表实际应避免这种情况
LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!");
conditionBuilder.append("1=1");
}
// 分页查询数据
do {
String querySql = "SELECT * FROM " + tableName +
" WHERE " + conditionBuilder.toString() +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
LOGGER.debug("执行查询: {}", querySql);
List<RowMap> pageData;
// 根据条件类型执行查询
if (partitionField != null && !partitionField.isEmpty() &&
timeField != null && !timeField.isEmpty()) {
// 分区+时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else if (timeField != null && !timeField.isEmpty()) {
// 仅时间范围查询
pageData = rdsapi.getMaps(querySql, startDate, endDate);
} else {
// 无时间范围查询仅分区或全表
pageData = rdsapi.getMaps(querySql);
}
if (pageData != null && !pageData.isEmpty()) {
// 直接处理当前页数据
int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable);
totalRows += pageData.size();
totalSuccess += successCount;
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} else {
hasMore = false;
}
} while (hasMore);
}
LOGGER.info("从表[{}]共查询到{}条数据,成功同步{}条数据",
tableName, totalRows, totalSuccess);
} catch (Exception e) {
throw new RuntimeException("查询源表[" + tableName + "]数据失败: " + e.getMessage(), e);
}finally {
}
}
/**
* 获取目标表时间字段名
* @param mappings 字段映射配置列表
* @param sourceTimeField 源表时间字段名
* @return 目标表时间字段名未找到返回null
*/
@Override
public String getTargetTimeField(List<BO> mappings, String sourceTimeField) {
for (BO mapping : mappings) {
if (sourceTimeField.equals(mapping.getString("TBBZD"))) {
return mapping.getString("LDBZD");
}
}
return null;
}
/**
* 删除目标表中指定时间范围的数据
* @param targetTable 目标表名
* @param targetTimeField 目标表时间字段名
* @param startDate 开始时间
* @param endDate 结束时间
* @throws RuntimeException 删除失败时抛出
*/
@Override
public void deleteTargetData(String targetTable, String targetTimeField,
Date startDate, Date endDate) {
try {
String deleteSql = "DELETE FROM " + targetTable +
" WHERE " + targetTimeField + " BETWEEN ? AND ?";
int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate});
LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})",
targetTable, deletedCount, startDate, endDate);
} catch (Exception e) {
throw new RuntimeException("删除目标表数据失败: " + e.getMessage(), e);
}
}
/**
* 删除目标表所有数据
*/
@Override
public void deleteAllTargetData(String targetTable) {
try {
String deleteSql = "DELETE FROM " + targetTable ;
int deletedCount = DBSql.update(deleteSql);
LOGGER.info("已全量删除目标表[{}]中{}条数据", targetTable, deletedCount);
} catch (Exception e) {
throw new RuntimeException("全量删除目标表数据失败: " + e.getMessage(), e);
}
}
/**
* 处理并插入数据到目标表
* @param sourceData 源数据列表
* @param mappings 字段映射配置
* @param targetTable 目标表名
*/
public int processAndInsertData(List<RowMap> sourceData,
List<BO> mappings, String targetTable) {
if (sourceData.isEmpty()) {
LOGGER.info("没有需要同步的数据");
return 0;
}
List<BO> batchList = new ArrayList<>();
int successCount = 0;
int totalCount = sourceData.size();
int processedCount = 0; // 已处理记录数
for (int i = 0; i < totalCount; i++) {
RowMap record = sourceData.get(i);
processedCount++; // 增加已处理计数
try {
// 字段映射转换
BO targetData = convertFields(record, mappings);
batchList.add(targetData);
// 批量插入条件达到批处理大小或最后一条
if (batchList.size() >= PAGE_SIZE || i == totalCount - 1) {
// 使用管理员权限批量插入
SDK.getBOAPI().createDataBO(targetTable, batchList, UserContext.fromUID("admin"));
successCount += batchList.size();
batchList.clear(); // 清空批次
}
} catch (Exception e) {
LOGGER.error("数据处理失败: {}", e.getMessage(), e);
}
}
// 增加详细日志输出共处理多少条成功同步多少条
LOGGER.info("本次处理{}条数据,成功同步{}条数据到表[{}]",
processedCount, successCount, targetTable);
return successCount;
}
/**
* 字段映射转换
* @param source 源数据记录
* @param mappings 字段映射配置
* @return 转换后的BO对象
*/
public BO convertFields(RowMap source, List<BO> mappings) {
BO target = new BO();
for (BO mapping : mappings) {
String sourceField = mapping.getString("TBBZD");
String targetField = mapping.getString("LDBZD");
// if (!source.containsKey(sourceField)) {
// LOGGER.debug("源字段[{}]不存在于查询结果中", sourceField);
// continue;
// }
String operationExpr = mapping.getString("TBBZDJSLJ");
if (StringUtils.isNotBlank(operationExpr)) {
// 解析运算表达式 (格式: [运算符][数字])
char operator = operationExpr.charAt(0);
String numberPart = operationExpr.substring(1);
try {
// 获取源值并转换为BigDecimal
String sourceValue = source.getString(sourceField);
if (StringUtils.isBlank(sourceValue)) {
target.set(targetField, null);
continue;
}
if ("GG".equals(sourceField)){
String string = source.getString(sourceField);
if (StringUtils.isNotBlank(string) && string.contains("×")){
String[] split = string.split("×");
String s = split[split.length - 1];
target.set(targetField, s);
continue;
}
}
BigDecimal sourceNum = new BigDecimal(sourceValue);
BigDecimal operand = new BigDecimal(numberPart);
BigDecimal result;
// 执行相应运算
switch (operator) {
case '*':
result = sourceNum.multiply(operand);
break;
case '/':
if (BigDecimal.ZERO.compareTo(operand) == 0) {
LOGGER.error("除零错误: 源字段[{}] 除数为0", sourceField);
result = sourceNum; // 避免除零异常
} else {
// 除法保留10位小数并四舍五入
result = sourceNum.divide(operand, 10, RoundingMode.HALF_UP);
}
break;
case '+':
result = sourceNum.add(operand);
break;
case '-':
result = sourceNum.subtract(operand);
break;
default:
LOGGER.error("未知运算符: {} 字段[{}]", operator, sourceField);
result = sourceNum;
}
target.set(targetField, result);
} catch (NumberFormatException e) {
LOGGER.error("数值转换失败: 源字段[{}]={}, 操作数={}",
sourceField, source.getString(sourceField), numberPart, e);
target.set(targetField, source.getString(sourceField));
}
} else {
if ("GG".equals(sourceField)){
String string = source.getString(sourceField);
if (StringUtils.isNotBlank(string) && string.contains("×")){
String[] split = string.split("×");
String s = split[split.length - 1];
target.set(targetField, s);
}else {
// 无运算表达式时直接复制原始值
target.set(targetField, StringUtils.isNotBlank(string)?string:"");
}
}else {
// 无运算表达式时直接复制原始值
target.set(targetField, source.getString(sourceField));
}
}
String ldzdmrz = mapping.getString("LDZDMRZ");
if (StringUtils.isNotBlank(ldzdmrz)){
target.set(mapping.getString("LDBZD"),ldzdmrz);
}
}
return target;
}
/**
* 销售各板块数据汇总表
* @param configs
*/
@Override
public void sumBkTable(List<BO> configs) {
Map<String, List<BO>> configsByPlate = configs.stream()
.collect(Collectors.groupingBy(bo -> bo.getString("SSBK")));
// 遍历处理每个板块的配置
for (Map.Entry<String, List<BO>> entry : configsByPlate.entrySet()) {
String plate = entry.getKey();
List<BO> plateConfigs = entry.getValue();
LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size());
// 处理当前板块的每条配置
for (BO mainConfig : plateConfigs) {
String targetTable = mainConfig.getString("LDB");//落地表
String timeField = mainConfig.getString("SJZD");//时间字段
String bindId = mainConfig.getString("BINDID");//bindid
String tablename = mainConfig.getString("TABLENAME");//同步表名
String hzb = "";
try {
if ("销售表".equals(tablename)){
hzb = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ";
}else {
hzb = "BO_EU_BNBM_DATALINKUP_XS_YSL";
}
// 查询子表字段映射配置
List<BO> fieldMappings = SDK.getBOAPI()
.query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB")
.addQuery("BINDID =", bindId)
.list();
//获取板块公司
String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS");
// 根据时间字段是否为空设置日期范围
Date startDate = null;
Date endDate = null;
// 删除目标表数据根据时间字段是否为空决定删除范围
if (timeField == null || timeField.isEmpty()) {
// 全量删除
String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'";
int deletedCount = DBSql.update(deleteSql);
LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})",
hzb,deletedCount, startDate, endDate);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 全量分页迁移数据到汇总表
summarizeScopeData(targetTable, null, null, null, hzb);
} else {
// 计算时间范围当前日期-30天 ~ 昨天
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1); // 昨天
endDate = cal.getTime();
cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前
startDate = cal.getTime();
// 获取目标表时间字段名
String targetTimeField = getTargetTimeField(fieldMappings, timeField);
if (targetTimeField == null) {
LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField);
}
// 按时间范围删除
String deleteSql = "DELETE FROM " + hzb +
" WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?";
int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate});
LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})",
hzb,deletedCount, startDate, endDate);
// 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ
// 按时间范围分页迁移数据到汇总表
summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb);
}
} catch (Exception e) {
LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}",
plate, mainConfig.getString("BINDID"), e.getMessage(), e);
}
}
}
}
/**
* 汇总各板块销售数据汇总
* @param targetTable
* @param startDated
* @param endDated
* @param targetTimeField
*/
@Override
public void summarizeScopeData(String targetTable, Date startDated, Date endDated, String targetTimeField, String hzb) {
int pageNo = 1;
boolean hasMore;
String pageSql = "";
List<RowMap> pageData = null;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startDate = "";
String endDate = "";
try {
do {
if (startDated == null || endDated == null) {
pageSql = "SELECT * FROM " + targetTable +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
pageData = DBSql.getMaps(pageSql);
} else {
startDate = simpleDateFormat.format(startDated);
endDate = simpleDateFormat.format(endDated);
pageSql = "SELECT * FROM " + targetTable +
" WHERE " + targetTimeField + " BETWEEN '" + startDate + "' AND '" + endDate + "' " +
" LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
LOGGER.info("执行查询的sql{}", pageSql);
pageData = DBSql.getMaps(pageSql);
}
if (pageData.isEmpty()) break;
List<BO> bos = new ArrayList<>();
for (RowMap map : pageData) {
BO bo = new BO();
// 复制所有字段排除系统字段
for (String key : map.keySet()) {
if (!key.equalsIgnoreCase("ID") &&
!key.equalsIgnoreCase("ORGID") &&
!key.equalsIgnoreCase("CREATEDATE") &&
!key.equalsIgnoreCase("CREATEUSER") &&
!key.equalsIgnoreCase("UPDATEDATE") &&
!key.equalsIgnoreCase("UPDATEUSER") &&
!key.equalsIgnoreCase("ISEND") &&
!key.equalsIgnoreCase("BINDID")) {
if (StringUtils.isNotBlank(targetTimeField)) {
String targetTimeField1 = map.getString(targetTimeField);
Date parse = UtilDate.parse(targetTimeField1);
int year = UtilDate.getYear(parse);
String monthFormat = UtilDate.monthFormat(parse);
int day = UtilDate.getDay(parse);
bo.set("YEARMONTH", year + monthFormat);
bo.set("YEAR", year);
bo.set("MONTH", monthFormat);
bo.set("DAY", day);
}
bo.set(key, map.get(key));
}
}
bos.add(bo);
}
SDK.getBOAPI().createDataBO(hzb, bos, UserContext.fromUID("admin"));
LOGGER.info("已迁移{}条数据到汇总表(页号: {},时间范围: {} - {}",
bos.size(), pageNo, startDate, endDate);
hasMore = pageData.size() == PAGE_SIZE;
pageNo++;
} while (hasMore);
}catch (Exception e){
LOGGER.error("汇总数据失败 [汇总表={}, 第几页={}]: {}",
hzb, PAGE_SIZE, e.getMessage(), e);
}
}
// 高斯数据库连接池简化版
// public static class GaussDataSource {
// private static final String URL = GAUSSIAN_JDBC_URL;
// private static final String USER = GAUSSIAN_USERNAME;
// private static final String PASSWORD = GAUSSIAN_PASSWORD;
//
// public static Connection getConnection() throws SQLException {
// String driver = "com.huawei.gaussdb.jdbc.Driver";
// try {
// //加载数据库驱动
// Class.forName(driver).newInstance();
// } catch (Exception e) {
// e.printStackTrace();
// return null;
// }
// Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
// boolean autoCommit = connection.getAutoCommit();
// LOGGER.info("autoCommit:{}",autoCommit);
// return connection;
// }
// }
// /**
// * 高斯数据库专用查询方法支持分区和分页
// */
// @Override
// public void queryGaussDataWithCondition(String tableName, String timeField,
// Date startDate, Date endDate, String partitionField,
// List<BO> fieldMappings, String targetTable) {
// int totalRows = 0; // 总查询行数
// int totalSuccess = 0; // 总成功插入行数
// int pageNo = 1;
// boolean hasMore;
// Connection conn = null;
//
// try {
// conn = GaussDataSource.getConnection();
// LOGGER.info("成功连接高斯数据库");
//
// // 构建查询条件
// StringBuilder conditionBuilder = new StringBuilder();
//
// // 修改点分区字段和时间字段组合查询条件
// if (partitionField != null && !partitionField.isEmpty()) {
// // 1. 查询最大分区值
// String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName;
// try (Statement stmt = conn.createStatement();
// ResultSet rs = stmt.executeQuery(maxPartitionSql)) {
//
// if (rs.next()) {
// String maxPartition = rs.getString("max_partition");
// // 添加分区条件
// conditionBuilder.append(partitionField)
// .append(" = '")
// .append(maxPartition)
// .append("'");
// LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition);
// } else {
// LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField);
// return;
// }
// }
// }
//
// // 添加时间范围条件无论是否有分区字段只要时间字段存在
// if (timeField != null && !timeField.isEmpty()) {
// if (conditionBuilder.length() > 0) {
// conditionBuilder.append(" AND ");
// }
// conditionBuilder.append(timeField)
// .append(" BETWEEN '")
// .append(new Timestamp(startDate.getTime()))
// .append("' AND '")
// .append(new Timestamp(endDate.getTime()))
// .append("'");
// } else if (conditionBuilder.length() == 0) {
// // 既没有分区字段也没有时间字段查询全表实际应避免
// LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!");
// conditionBuilder.append("1=1");
// }
//
// // 分页查询数据
// do {
// String querySql = "SELECT * FROM " + tableName;
// // 如果有条件则添加WHERE子句
// if (conditionBuilder.length() > 0) {
// querySql += " WHERE " + conditionBuilder.toString();
// }
// querySql += " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE;
//
// LOGGER.debug("执行高斯查询: {}", querySql);
//
// try (Statement stmt = conn.createStatement();
// ResultSet rs = stmt.executeQuery(querySql)) {
//
// List<RowMap> pageData = new ArrayList<>();
// ResultSetMetaData metaData = rs.getMetaData();
// int columnCount = metaData.getColumnCount();
//
// while (rs.next()) {
// BO row = new BO();
// for (int i = 1; i <= columnCount; i++) {
// String colName = metaData.getColumnName(i);
// Object value = rs.getObject(i);
// row.set(colName, value);
// }
// RowMap map = new RowMap(row.asMap());
// pageData.add(map);
// }
//
// if (!pageData.isEmpty()) {
// // 直接处理当前页数据
// int successCount = processAndInsertData(pageData, fieldMappings, targetTable);
// totalRows += pageData.size();
// totalSuccess += successCount;
// hasMore = pageData.size() == PAGE_SIZE;
// pageNo++;
// } else {
// hasMore = false;
// }
// }
// } while (hasMore);
//
// LOGGER.info("从高斯表[{}]共查询到{}条数据,成功同步{}条数据",
// tableName, totalRows, totalSuccess);
// } catch (SQLException e) {
// throw new RuntimeException("高斯数据库查询失败: " + e.getMessage(), e);
// } finally {
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// LOGGER.error("关闭高斯数据库连接失败", e);
// }
// }
// }
// }
}