diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/controller/dbinterface/WmsSAPDbInterfaceController.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/controller/dbinterface/WmsSAPDbInterfaceController.java new file mode 100644 index 0000000..ced5e8c --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/controller/dbinterface/WmsSAPDbInterfaceController.java @@ -0,0 +1,91 @@ +package cn.estsh.i3plus.ext.mes.apiservice.controller.dbinterface; + +import cn.estsh.i3plus.ext.mes.apiservice.dbinterface.MesSAPDbAdapter; +import cn.estsh.i3plus.icloud.softswitch.sdk.IBsSuitServiceCloud; +import cn.estsh.i3plus.mes.apiservice.serviceimpl.engine.script.EngineScriptManager; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceEnumUtil; +import cn.estsh.impp.framework.boot.exception.ImppExceptionBuilder; +import cn.estsh.impp.framework.boot.util.ResultBean; +import cn.estsh.impp.framework.boot.util.SpringContextsUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * @Description : SAP接口同步控制器 + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 12:08 + * @Modify: + **/ +@RestController +@RequestMapping("/impp/white/mes-sap-if/") +@Api(description = "SAP接口同步控制器") +public class WmsSAPDbInterfaceController { + private static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbInterfaceController.class); + + @Autowired + IBsSuitServiceCloud bsSuitServiceCloud; + + // 注入脚本引擎对象 + @Autowired + private EngineScriptManager scriptManager; + + @GetMapping(value = "sync-data/do") + @ApiOperation(value = "执行接口数据同步,支持的参数:SAP2MES, MES2SAP ") + public ResultBean doSyncSAPData(String syncParam) { + try { + // 判断作业参数是否支持 + if (MesInterfaceEnumUtil.DIRECTION_TYPE.nameOf(syncParam) == -1) { + throw new IllegalArgumentException("不支持的同步参数:" + syncParam); + } + LOGGER.info("WmsSAPDbInterfaceController.doSyncSAPData start....."); + MesSAPDbAdapter wmsSAPDbAdapter = new MesSAPDbAdapter(); + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbAdapter); + } + wmsSAPDbAdapter.syncData(syncParam,null); + LOGGER.info("WmsSAPDbInterfaceController.doSyncSAPData end....."); + + return new ResultBean(true).setMsg("SAP-->WMS接口数据同步执行成功"); + } catch (Exception e) { + return ImppExceptionBuilder.newInstance().buildExceptionResult(e).setResultObject(e); + } + } + + @GetMapping(value = "sync-data-id/do") + @ApiOperation(value = "按ID单个执行接口数据同步,支持的参数:SAP2MES, MES2SAP ") + public ResultBean doSyncSAPData(String syncParam,String o, String id) { + try { + // 判断作业参数是否支持 + if (MesInterfaceEnumUtil.DIRECTION_TYPE.nameOf(syncParam) == -1) { + throw new IllegalArgumentException("不支持的同步参数:" + syncParam); + } + LOGGER.info("WmsSAPDbInterfaceController.doSyncSAPData start....."); + // 开始同步数据 + // 一定要 new 对象,不然会有并发问题 + MesSAPDbAdapter wmsSAPDbAdapter = new MesSAPDbAdapter(); + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbAdapter); + } + wmsSAPDbAdapter.syncData(syncParam, id); + LOGGER.info("WmsSAPDbInterfaceController.doSyncSAPData end....."); + + return new ResultBean(true).setMsg("SAP-->WMS接口数据同步执行成功"); + } catch (Exception e) { + return ImppExceptionBuilder.newInstance().buildExceptionResult(e).setResultObject(e); + } + } + + +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbAdapter.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbAdapter.java new file mode 100644 index 0000000..a2d28fe --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbAdapter.java @@ -0,0 +1,479 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataSet; +import cn.estsh.i3plus.platform.common.tool.TimeTool; +import cn.estsh.i3plus.platform.common.util.MesConstWords; +import cn.estsh.i3plus.platform.plugin.datasource.DynamicDataSourceProxy; +import cn.estsh.i3plus.pojo.base.codemaker.SnowflakeIdMaker; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceDataMapper; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceEnumUtil; +import cn.estsh.impp.framework.boot.util.ImppRedis; +import cn.estsh.impp.framework.boot.util.SpringContextsUtil; +import com.alibaba.fastjson.JSON; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.sql.Connection; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @Description : 接口数据适配器 + * 从接口规则表中读取所有需要转换的接口表信息并调用读取类进行数据获取,再调用转换类进行转换,最后调用写入类把数据保存到业务表 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-06-11 19:34 + * @Modify: 1. + **/ +@Component +@NoArgsConstructor +public class MesSAPDbAdapter { + + public static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbWriter.class); + + @Resource(name = "yfasDataSource") + private DynamicDataSourceProxy sapDataSourceProxy; + + @Resource(name = "mesDataSource") + private DynamicDataSourceProxy mesDataSourceProxy; + + @Value("${sync.redis.time:1800}") + private Integer redisTime; + + private WmsSAPDbExpression wmsSAPDbExpression; + + private WmsSAPDbWriter writer; + + private WmsSAPDbReader reader; + + private WmsSAPDbTranslator translator; + + private MesSAPDbDataMapper dbDataMapper; + + private Connection srcConn; + private Connection destConn; + private Connection mesConn; + + @Resource(name = "redisMes") + private ImppRedis redisMes; + + // 获取 IMPP ID + @Autowired + private SnowflakeIdMaker snowflakeIdMaker; + + private WmsSAPDbWriter buildWriter(String groupName, + DynamicDataSourceProxy sapDataSourceProxy, + DynamicDataSourceProxy mesDataSourceProxy) throws Exception { + + WmsSAPDbWriter wmsSAPDbWriter = null; + + if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.SAP2MES.getName())) { + wmsSAPDbWriter = new WmsSAPDbWriter(sapDataSourceProxy, mesDataSourceProxy); + } + + if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.MES2SAP.getName())) { + wmsSAPDbWriter = new WmsSAPDbWriter(mesDataSourceProxy, sapDataSourceProxy); + } + + + + + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbWriter); + } + + this.srcConn = wmsSAPDbWriter.getSrcDataSourceProxy().getWriteConnectionWithoutPool(); + this.destConn = wmsSAPDbWriter.getDestDataSourceProxy().getWriteConnectionWithoutPool(); + this.mesConn = mesDataSourceProxy.getWriteConnectionWithoutPool(); + + if (wmsSAPDbWriter != null) { + wmsSAPDbExpression = new WmsSAPDbExpression(); + wmsSAPDbExpression.setSrcConn(mesConn); + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbExpression); + } + wmsSAPDbWriter.setWmsSAPDbExpression(wmsSAPDbExpression); + } + + + return wmsSAPDbWriter; + } + + private WmsSAPDbReader buildReader(String groupName, + DynamicDataSourceProxy sapDataSourceProxy, + DynamicDataSourceProxy mesDataSourceProxy) { + + WmsSAPDbReader wmsSAPDbReader = null; + + if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.SAP2MES.getName())) { + wmsSAPDbReader = new WmsSAPDbReader(sapDataSourceProxy); + } + + if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.MES2SAP.getName())) { + wmsSAPDbReader = new WmsSAPDbReader(mesDataSourceProxy); + } + +// if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.WMS2MES.getName())) { +// wmsSAPDbReader = new WmsSAPDbReader(mesDataSourceProxy); +// } +// +// if (groupName.equals(MesInterfaceEnumUtil.DIRECTION_TYPE.MES2WMS.getName())) { +// wmsSAPDbReader = new WmsSAPDbReader(mesDataSourceProxy); +// } + + + if (wmsSAPDbReader != null && SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbReader); + } + + return wmsSAPDbReader; + } + + private String getTime() { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 + return df.format(new Date());// new Date()为获取当前系统时间 + } + + /** + * 按照配置表同步所有的数据 + * 单一数据源,要防止并发 + */ + public void syncData(String groupName, String id) throws Exception { + String redisKey = MesConstWords.SAP_TRANS_JOB_LOCK_TAG + ":" + groupName; + + // 按ID单个调试时 key 要带上 id,防止跟正常 job 冲突 + if (id != null && !id.isEmpty()) { + redisKey = redisKey + ":" + id; + } + + //添加redis锁(半小时失效) + if (redisMes.getObject(redisKey) != null) { + LOGGER.error("数据正在处理中!不能并发处理!RedisKey: {}", redisKey); + // 直接返回 + return; + } + + LOGGER.info("WmsSAPDbAdapter.syncData start..."); + + // 存放错误信息 + StringBuilder errorMessage = new StringBuilder(); + try { + redisMes.putObject(redisKey, "starting...", redisTime); + + // 一次性拿到数据源连接,处理完成后再统一关闭 + // 不需要在这里处理 + //initConnection(); + + writer = buildWriter(groupName, sapDataSourceProxy, mesDataSourceProxy); + writer.setSrcConn(this.srcConn); + writer.setDestConn(this.destConn); + + reader = buildReader(groupName, sapDataSourceProxy, mesDataSourceProxy); + reader.setSrcConn(this.srcConn); + + + // 映射关系始终从 WMS 表获取 + dbDataMapper = new MesSAPDbDataMapper(mesDataSourceProxy); + dbDataMapper.setSrcConn(mesConn); + + translator = new WmsSAPDbTranslator(wmsSAPDbExpression); + + List dataMappers = dbDataMapper.readDataMappersBySQL(groupName, id, null, null, null); + + LOGGER.info("WmsSAPDbAdapter.foreach dataMapper ..."); + + // 遍历所有已配置的接口表 + for (MesInterfaceDataMapper dataMapper : dataMappers) { + try { + // 检查同步周期是否达到预设值 + if (!checkSyncTime(dataMapper)) { + // 未到达同步时间,继续判断下一个接口 + continue; + } + } catch (Exception e) { + LOGGER.error("同步数据发生异常", e); + } + + try { + // 设置本此发送的 SID + dataMapper.setSid(snowflakeIdMaker.nextId()); + // 用当前配置信息初始化对象 + reader.setDataMapper(dataMapper); + writer.setDataMapper(dataMapper); + translator.setDataMapper(dataMapper); + + // 从来源表读取数据 + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} read data", dataMapper.getSoftAdaptorCode()); + + List> srcData = reader.readData(); + // 来源表中没有数据,跳过当前循环,处理下一个表 + if (srcData == null || srcData.size() == 0) { + continue; + } + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{},size:{}", dataMapper.getSoftAdaptorCode(), srcData.size()); + // 把来源表转换成目标表 + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} start translateData", dataMapper.getSoftAdaptorCode()); + + DataSet dataSet = translator.translateData(srcData); + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} end translateData", dataMapper.getSoftAdaptorCode()); + + // 没有获取到转换后的数据,不执行写入操作,处理下一个表 + if (dataSet == null || dataSet.size() == 0) { + continue; + } + LOGGER.info("WmsSAPDbAdapter.syncData.saveData softAdapter:{} start", dataMapper.getSoftAdaptorCode()); + + // 写入目标表信息并更新来源表标志字段 + writer.saveData(srcData, dataSet, reader.getOriginReadData()); + LOGGER.info("WmsSAPDbAdapter.syncData.saveData softAdapter:{} end", dataMapper.getSoftAdaptorCode()); + String updateSql = String.format("update mes_interface_data_mapper set" + + " last_sync_time ='%s' where id =%s", TimeTool.getNowTime(true), dataMapper.getId()); + mesDataSourceProxy.execute(mesConn, updateSql); + mesConn.setAutoCommit(false); + // 把当前时间写入 redis,便于跟踪 + redisMes.putObject(redisKey, getTime(), 1800); + + dbDataMapper.updateWmsInterfaceDataMapperLastSyncTimeById(dataMapper.getId()); + // 更新下次执行时间 + dbDataMapper.updateNextSyncTime(dataMapper); + + } catch (Exception e) { + LOGGER.error("接口同步错误! 接口对象:" + JSON.toJSONString(dataMapper) + "\n" + e.getMessage()); + + errorMessage.append("接口同步错误! 接口对象:" + JSON.toJSONString(dataMapper) + "\n"); + errorMessage.append(e.getMessage() + ", " + e.getCause()); + errorMessage.append("\n\n"); + } + } + + + } catch (Exception e) { + LOGGER.error("WmsSAPDbAdapter: " + e.getStackTrace().toString()); + throw e; + } finally { + // 解除REDIS执行锁,这个要放到第一行,否则执行有异常时可能导致 KEY 删不掉 + redisMes.deleteKey(redisKey); + // 手工关闭连接 + //closeConnection(); + mesDataSourceProxy.closeConnectionWithoutPoll(this.srcConn); + mesDataSourceProxy.closeConnectionWithoutPoll(this.destConn); + mesDataSourceProxy.closeConnectionWithoutPoll(this.mesConn); + } + LOGGER.info("WmsSAPDbAdapter.syncData stop..."); + + // 执行过程中出现了异常,抛出异常 + if (errorMessage.length() > 0) { + throw new RuntimeException(errorMessage.toString()); + } + } + + /** + * 检查距离上次同步的间隔时间是否满足预设的同步间隔时间 + * + * @param dataMapper 接口实体 + * @return 是否满足同步条件 + */ + private boolean checkSyncTime(MesInterfaceDataMapper dataMapper) { + try { + DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + int syncFrequency = dataMapper.getSyncFrequency();//间隔分钟数 + String lastSyncTimes = dataMapper.getLastSyncTime();//时分秒的三种数据 + LocalDateTime localDateTimeSatrt = LocalDateTime.parse(lastSyncTimes, df); + LocalDateTime localDateTimeEnd = LocalDateTime.now(); + Duration duration = Duration.between(localDateTimeSatrt, localDateTimeEnd); + long minutes = duration.toMinutes(); + // 间隔时间未到达预设的同步周期 + if (minutes < syncFrequency) { + String msg = String.format("上次同步频率时间相差太短!syncFrequency=%s " + + "--- localDateTimeSatrt=%s " + + "--- localDateTimeEnd=%s", + syncFrequency, localDateTimeSatrt, localDateTimeEnd); + LOGGER.info(msg); + return false; + } + } catch (Exception e) { + + } + + return true; + } + + /** + * 按照配置表+工厂同步所有的数据 + * 单一数据源,要防止并发 + */ + public void syncDataByOrganizeCode(String groupName, String organizeCode, List destBeanNameList, List destBeanNameNotList) throws Exception { + + //分组代码+工厂代码作业KEY值 + String redisKey = MesConstWords.SAP_TRANS_JOB_LOCK_TAG + ":" + groupName + ":" + organizeCode + destBeanNameList + destBeanNameNotList; + + //添加redis锁(半小时失效) + if (redisMes.getObject(redisKey) != null) { + LOGGER.error("数据正在处理中!不能并发处理!RedisKey: {}", redisKey); + // 直接返回 + return; + } + + LOGGER.info("WmsSAPDbAdapter.syncData organizeCode:{},start...", organizeCode); + + // 存放错误信息 + StringBuilder errorMessage = new StringBuilder(); + try { + redisMes.putObject(redisKey, "starting...", redisTime); + + // 一次性拿到数据源连接,处理完成后再统一关闭 + // 不需要在这里处理 + //initConnection(); + + writer = buildWriter(groupName, sapDataSourceProxy, mesDataSourceProxy); + writer.setSrcConn(this.srcConn); + writer.setDestConn(this.destConn); + + reader = buildReader(groupName, sapDataSourceProxy, mesDataSourceProxy); + reader.setSrcConn(this.srcConn); + + + // 映射关系始终从 WMS 表获取 + dbDataMapper = new MesSAPDbDataMapper(mesDataSourceProxy); + dbDataMapper.setSrcConn(mesConn); + + translator = new WmsSAPDbTranslator(wmsSAPDbExpression); + + List dataMappers = dbDataMapper.readDataMappersBySQL(groupName, null, organizeCode, destBeanNameList, destBeanNameNotList); + + LOGGER.info("WmsSAPDbAdapter.foreach dataMapper ..."); + + // 遍历所有已配置的接口表 + for (MesInterfaceDataMapper dataMapper : dataMappers) { + try { + // 检查同步周期是否达到预设值 + if (!checkSyncTime(dataMapper)) { + // 未到达同步时间,继续判断下一个接口 + continue; + } + } catch (Exception e) { + LOGGER.error("同步数据发生异常", e); + } + + try { + // 设置本此发送的 SID + dataMapper.setSid(snowflakeIdMaker.nextId()); + // 用当前配置信息初始化对象 + reader.setDataMapper(dataMapper); + writer.setDataMapper(dataMapper); + translator.setDataMapper(dataMapper); + + // 从来源表读取数据 + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} read data", dataMapper.getSoftAdaptorCode()); + + List> srcData = reader.readData(); + // 来源表中没有数据,跳过当前循环,处理下一个表 + if (srcData == null || srcData.size() == 0) { + continue; + } + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{},size:{}", dataMapper.getSoftAdaptorCode(), srcData.size()); + // 把来源表转换成目标表 + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} start translateData", dataMapper.getSoftAdaptorCode()); + + DataSet dataSet = translator.translateData(srcData); + LOGGER.info("WmsSAPDbAdapter.syncData softAdapter:{} end translateData", dataMapper.getSoftAdaptorCode()); + + // 没有获取到转换后的数据,不执行写入操作,处理下一个表 + if (dataSet == null || dataSet.size() == 0) { + continue; + } + LOGGER.info("WmsSAPDbAdapter.syncData.saveData softAdapter:{} start", dataMapper.getSoftAdaptorCode()); + + // 写入目标表信息并更新来源表标志字段 + writer.saveData(srcData, dataSet, reader.getOriginReadData()); + LOGGER.info("WmsSAPDbAdapter.syncData.saveData softAdapter:{} end", dataMapper.getSoftAdaptorCode()); + String updateSql = String.format("update mes_interface_data_mapper set" + + " last_sync_time ='%s' where id =%s", TimeTool.getNowTime(true), dataMapper.getId()); + mesDataSourceProxy.execute(mesConn, updateSql); + mesConn.setAutoCommit(false); + // 把当前时间写入 redis,便于跟踪 + redisMes.putObject(redisKey, getTime(), 1800); + + dbDataMapper.updateWmsInterfaceDataMapperLastSyncTimeById(dataMapper.getId()); + // 更新下次执行时间 + dbDataMapper.updateNextSyncTime(dataMapper); + + } catch (Exception e) { + LOGGER.error("接口同步错误! 接口对象:" + JSON.toJSONString(dataMapper) + "\n" + e.getMessage()); + + errorMessage.append("接口同步错误! 接口对象:" + JSON.toJSONString(dataMapper) + "\n"); + errorMessage.append(e.getMessage() + ", " + e.getCause()); + errorMessage.append("\n\n"); + } + } + + + } catch (Exception e) { + LOGGER.error("WmsSAPDbAdapter: " + e.getStackTrace().toString()); + throw e; + } finally { + // 解除REDIS执行锁,这个要放到第一行,否则执行有异常时可能导致 KEY 删不掉 + redisMes.deleteKey(redisKey); + // 手工关闭连接 + //closeConnection(); + mesDataSourceProxy.closeConnectionWithoutPoll(this.srcConn); + mesDataSourceProxy.closeConnectionWithoutPoll(this.destConn); + mesDataSourceProxy.closeConnectionWithoutPoll(this.mesConn); + } + LOGGER.info("WmsSAPDbAdapter.syncData organizeCode:{} stop...", organizeCode); + + // 执行过程中出现了异常,抛出异常 + if (errorMessage.length() > 0) { + throw new RuntimeException(errorMessage.toString()); + } + } + + /** + * Rock.Yu 2019-07-26 + * 读写用2个数据源有问题,写进去的数据从读数据源查不到(可能是事务问题,没有深入分析) + * 实际上配置文件中的读写连接串都是相同的,没必要用2个连接(做读写分离可以用 MyCAT) + * 此处读写都是用的同一个连接(写连接),可以解决上述问题 + * + * @throws Exception + */ +// private void initConnection() throws Exception { +// sapDataSourceProxy.setReadConnHold(sapDataSourceProxy.getWriteConnectionWithoutPool()); +// sapDataSourceProxy.setWriteConnHold(sapDataSourceProxy.getWriteConnectionWithoutPool()); +// mesDataSourceProxy.setReadConnHold(mesDataSourceProxy.getWriteConnectionWithoutPool()); +// mesDataSourceProxy.setWriteConnHold(mesDataSourceProxy.getWriteConnectionWithoutPool()); +// mesDataSourceProxy.setReadConnHold(mesDataSourceProxy.getWriteConnectionWithoutPool()); +// mesDataSourceProxy.setWriteConnHold(mesDataSourceProxy.getWriteConnectionWithoutPool()); +// swebDataSourceProxy.setReadConnHold(swebDataSourceProxy.getWriteConnectionWithoutPool()); +// swebDataSourceProxy.setWriteConnHold(swebDataSourceProxy.getWriteConnectionWithoutPool()); +// } +// +// private void closeConnection() throws Exception { +// try { +// sapDataSourceProxy.closeConnectionWithoutPoll(sapDataSourceProxy.getReadConnHold()); +// sapDataSourceProxy.closeConnectionWithoutPoll(sapDataSourceProxy.getWriteConnHold()); +// mesDataSourceProxy.closeConnectionWithoutPoll(mesDataSourceProxy.getReadConnHold()); +// mesDataSourceProxy.closeConnectionWithoutPoll(mesDataSourceProxy.getWriteConnHold()); +// mesDataSourceProxy.closeConnectionWithoutPoll(mesDataSourceProxy.getReadConnHold()); +// mesDataSourceProxy.closeConnectionWithoutPoll(mesDataSourceProxy.getWriteConnHold()); +// swebDataSourceProxy.closeConnectionWithoutPoll(swebDataSourceProxy.getReadConnHold()); +// swebDataSourceProxy.closeConnectionWithoutPoll(swebDataSourceProxy.getWriteConnHold()); +// } catch (Exception e) { +// LOGGER.error(e.getStackTrace().toString()); +// } +// } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbDataMapper.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbDataMapper.java new file mode 100644 index 0000000..bed2da5 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/MesSAPDbDataMapper.java @@ -0,0 +1,171 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.ext.mes.pojo.util.CronUtil; +import cn.estsh.i3plus.ext.mes.pojo.util.OverwriteStringJoin; +import cn.estsh.i3plus.platform.common.tool.TimeTool; +import cn.estsh.i3plus.platform.plugin.datasource.DynamicDataSourceProxy; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceDataMapper; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +/** + * @Description : + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +public class MesSAPDbDataMapper extends WmsSAPDbSyncBase { + + public static final Logger LOGGER = LoggerFactory.getLogger(MesSAPDbDataMapper.class); + + private DynamicDataSourceProxy mesDataSourceProxy; + + @Getter + @Setter + private Connection srcConn; + + public MesSAPDbDataMapper(DynamicDataSourceProxy mesDataSourceProxy) { + this.mesDataSourceProxy = mesDataSourceProxy; + } + + /** + * 查询 SQL 返回数据转换配置对象的列表 + * @return + * @throws Exception + */ + public List readDataMappersBySQL(String groupName, String id,String organizeCode,List destBeanNameList,List destBeanNameNotList) throws Exception { + String sqlString = "select * from mes_interface_data_mapper " + + "where is_deleted=2 and is_valid=1 and group_name='" + + groupName + "' "; + + // 如果 id 不为空,则按照单个 id 查询 + if (id != null && !id.isEmpty()) { + // 带 id 调试时不考虑删除和有效2个字段 + sqlString = sqlString.replace("is_deleted=2 and is_valid=1 and", " "); + sqlString = sqlString + " and id=" + id; + } + + // 如果 organizeCode 不为空,则按照 organizeCode 查询 + if (organizeCode != null && !organizeCode.isEmpty()) { + // 带 id 调试时不考虑删除和有效2个字段 + sqlString = sqlString + " and organize_code= '" + organizeCode +"'"; + } + // 按照目标表进行拆分,参数不为空则in + if (!CollectionUtils.isEmpty(destBeanNameList)) { + sqlString = sqlString + " and dest_bean_name in ("+ OverwriteStringJoin.join(",",destBeanNameList)+")"; + LOGGER.info("接口映射添加目标表参数,sql为:{}",sqlString); + } + // 按照目标表进行拆分,参数不为空则 not in + if (!CollectionUtils.isEmpty(destBeanNameNotList)) { + sqlString = sqlString + " and dest_bean_name not in ("+ OverwriteStringJoin.join(",",destBeanNameNotList)+")"; + LOGGER.info("接口映射不添加目标表参数,sql为:{}",sqlString); + } + + sqlString = sqlString + " order by seq"; + + List> rows = mesDataSourceProxy.queryMapList(sqlString, srcConn); + + if (rows == null || rows.size() == 0) { + LOGGER.error("not set data in talbe: mes_interface_data_mapper !"); + throw new IllegalArgumentException("not set data in talbe: mes_interface_data_mapper !"); + } + + List dataMappers = new ArrayList<>(); + + for(Map row : rows) { + MesInterfaceDataMapper mapper = new MesInterfaceDataMapper(); + + mapper.setSeq(this.getSafetyInteger(row.get("seq"))); + mapper.setOrganizeCode(this.getSafetyString(row.get("organize_code"))); + mapper.setSrcGetLimit(this.getSafetyInteger(row.get("src_get_limit"))); + mapper.setDataSource(this.getSafetyString(row.get("src_data_source"))); + mapper.setSrcTableName(this.getSafetyString(row.get("src_table_name"))); + mapper.setSrcOrderBy(this.getSafetyString(row.get("src_order_by"))); + mapper.setSrcWhere(this.getSafetyString(row.get("src_where"))); + mapper.setSrcGroupColumns(this.getSafetyString(row.get("src_group_columns"))); + mapper.setSrcUpdateSync(this.getSafetyString(row.get("src_update_sync"))); + mapper.setSrcPkColumns(this.getSafetyString(row.get("src_pk_columns"))); + mapper.setCopyByOrgs(this.getSafetyString(row.get("copy_py_orgs"))); + mapper.setDestBeanName(this.getSafetyString(row.get("dest_bean_name"))); + mapper.setDestPkProperties(this.getSafetyString(row.get("dest_pk_properties"))); + mapper.setDestColumnMapping(this.getSafetyString(row.get("dest_column_mapping"))); + mapper.setBusiRoute(this.getSafetyString(row.get("busi_route"))); + mapper.setId(this.getSafetyLong(row.get("id"))); + mapper.setCopyByOrgs(this.getSafetyString(row.get("copy_by_orgs"))); + mapper.setSyncCron(this.getSafetyString(row.get("sync_cron"))); + mapper.setNextSyncTime(this.getSafetyString(row.get("next_sync_time"))); + + mapper.setSyncFrequency(this.getSafetyInteger(row.get("sync_frequency"))); + mapper.setLastSyncTime(this.getSafetyString(row.get("last_sync_time"))); + mapper.setSoftDataType(this.getSafetyInteger(row.get("soft_data_type"))); + + // 与软适配和脚本对接相关的属性 + mapper.softAdaptorCode = this.getSafetyString(row.get("soft_adapator_code")); + mapper.scriptNo = this.getSafetyString(row.get("script_no")); + mapper.useScriptPull = this.getSafetyInteger(row.get("use_script_pull")); + mapper.useScriptPush = this.getSafetyInteger(row.get("use_script_push")); + mapper.useScriptMark = this.getSafetyInteger(row.get("use_script_mark")); + mapper.useScriptFilter = this.getSafetyInteger(row.get("use_script_filter")); + + // 备份表名 + mapper.setCutToTableName(this.getSafetyString(row.get("cut_to_table_name"))); + + dataMappers.add(mapper); + } + + return dataMappers; + } + + public void updateWmsInterfaceDataMapperLastSyncTimeById(Long id) throws SQLException { + StringBuffer sql = new StringBuffer("update mes_interface_data_mapper"); + sql.append(" set last_sync_time='" + TimeTool.getNowTime(true) + "'"); + sql.append(" where id=" + id); + mesDataSourceProxy.execute(sql.toString()); + } + + /** + * 更新接口的下次执行时间 + * @param mapper + * @throws SQLException + */ + public void updateNextSyncTime(MesInterfaceDataMapper mapper) throws SQLException { + if (mapper != null && StringUtils.isNotEmpty(mapper.getSyncCron()) + && StringUtils.isNotEmpty(mapper.getNextSyncTime())) { + // 计算下次执行时间 + String nextSyncTime = CronUtil.getRecentTriggerTime(mapper.getSyncCron()); + String sqlString = String.format("update mes_interface_data_mapper set next_sync_time='%s' where id=%s", + nextSyncTime, mapper.getId()); + mesDataSourceProxy.execute(sqlString); + LOGGER.debug("接口ID {} 名称 {} 下次执行时间更新为 {}", mapper.getId(), mapper.getInterfaceName(), nextSyncTime); + } + } + + /** + * 重写判断对象相等的方法 + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * 重写 hashCode 方法 + * @return + */ + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbConstWords.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbConstWords.java new file mode 100644 index 0000000..7774736 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbConstWords.java @@ -0,0 +1,21 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +/** + * @Description : 脚本引擎中可以实现的方法 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-10-14 19:14 + * @Modify: + * 1. + **/ +public class WmsSAPDbConstWords { + + // 脚本读取数据的方法名,入参:WmsInterfaceDataMapper 返回值: List> + public static final String SCRIPT_PULL_FUN_NAME = "readData"; + // 脚本写入目标数据的方法名,入参:WmsInterfaceDataMapper,List> 返回值:void + public static final String SCRIPT_PUSH_FUN_NAME = "saveDestData"; + // 脚本更新来源数据标志的方法名,入参:WmsInterfaceDataMapper,List> 返回值:void + public static final String SCRIPT_MARK_FUN_NAME = "saveSrcData"; + // 脚本过滤来源数据的方法名,入参: List> 返回值:List> + public static final String SCRIPT_FILTER_FUN_NAME = "filterData"; +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbExpression.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbExpression.java new file mode 100644 index 0000000..e2c3727 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbExpression.java @@ -0,0 +1,121 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.mes.apiservice.serviceimpl.engine.script.EngineScriptManager; +import cn.estsh.impp.framework.boot.util.SpringContextsUtil; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Description : + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +@Component +public class WmsSAPDbExpression { + /** + * 获取系统相关的 ID + */ + @Autowired + private WmsSAPDbID wmsSAPDbID; + + /** + * 脚本引擎 + */ + @Autowired + private EngineScriptManager engineScriptManager; + + @Getter + @Setter + private Connection srcConn; + + WmsSAPDbQuery wmsSAPDbQuery = null; + + // 更新时不处理的特殊标志 + public static final String NOT_TRANS = "NotTrans"; + + /** + * 执行表达式计算 + * @param expString 表达式 + * @param varMap 临时变量 + * @throws Exception + */ + public Object parse(String expString, Map varMap) { + ExpressionParser parser = new SpelExpressionParser(); + + StandardEvaluationContext ctx = new StandardEvaluationContext(); + + // 先设置基础变量 + ctx.setVariables(getConstVarMap()); + + // 再设置临时变量 + if (varMap != null) { + ctx.setVariables(varMap); + } + + return parser.parseExpression(expString).getValue(ctx); + } + + /** + * 执行表达式计算,不带变量 + * @param expString + * @return + * @throws Exception + */ + public Object parse(String expString) { + return parse(expString, null); + } + + /** + * 初始化基础变量 + */ + public Map getConstVarMap() { + if (wmsSAPDbQuery == null) { + wmsSAPDbQuery = new WmsSAPDbQuery(); + // 初始化查询对象 + wmsSAPDbQuery.setSrcConn(srcConn); + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(wmsSAPDbQuery); + } + } + + Map constVarMap = new HashMap<>(); + + constVarMap.put("date", getFormatDate("yyyy-MM-dd")); + constVarMap.put("time", getFormatDate("HH:mm:ss")); + constVarMap.put("sap_date", getFormatDate("yyyyMMdd")); + constVarMap.put("sap_time", getFormatDate("HHmmss")); + constVarMap.put("now", getFormatDate("yyyy-MM-dd HH:mm:ss")); + constVarMap.put("Q", wmsSAPDbQuery); + constVarMap.put("ID", wmsSAPDbID); + constVarMap.put("I", wmsSAPDbID); + constVarMap.put("S", engineScriptManager); + + return constVarMap; + } + + /** + * 获取格式化后的日期字符串 + * @param format + * @return + */ + public static String getFormatDate(String format) { + DateFormat df = new SimpleDateFormat(format); + return df.format(new Date()); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbID.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbID.java new file mode 100644 index 0000000..dff4628 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbID.java @@ -0,0 +1,39 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.pojo.base.codemaker.SnowflakeIdMaker; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +/** + * @Description :用于生成系统 ID、GUID + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +@Component +public class WmsSAPDbID { + + // 获取 IMPP ID + @Autowired + private SnowflakeIdMaker snowflakeIdMaker; + + + /** + * 获取系统 ID,线程安全 + * @return + */ + public synchronized long nextId() { + return snowflakeIdMaker.nextId(); + } + + /** + * 获取 GUID(Globals Unique Identifiers) + * @return + */ + public synchronized String guid() { + return UUID.randomUUID().toString(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbQuery.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbQuery.java new file mode 100644 index 0000000..e063da2 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbQuery.java @@ -0,0 +1,105 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.platform.common.tool.TimeTool; +import cn.estsh.i3plus.platform.plugin.datasource.DynamicDataSourceProxy; +import cn.estsh.i3plus.pojo.base.codemaker.SnowflakeIdMaker; +import cn.estsh.impp.framework.boot.util.LocaleUtils; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.sql.Connection; + +/** + * @Description : + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +@Component +public class WmsSAPDbQuery { + public static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbQuery.class); + public static boolean needI18N = true; +// @Resource(name = "wmsDataSource") +// private DynamicDataSourceProxy wmsDataSourceProxy; + + @Resource(name = "mesDataSource") + private DynamicDataSourceProxy mesDataSourceProxy; + + @Autowired + private SnowflakeIdMaker snowflakeIdMaker; + + + @Getter + @Setter + private MesSAPDbDataMapper mesSAPDbDataMapper; + + @Getter + @Setter + private Connection srcConn; + + /** + * 根据单据类型获取单据存放的表名 + * + * @param orderType 单据类型 + * @return 单据的表名 + */ + private String getOrderTypeTable(String orderType,String organizeCode) { + String sqlTableName = ""; + + switch (orderType.trim().toUpperCase()) { + case "ASN": + sqlTableName = "wms_doc_asn_details_"+organizeCode; + break; + case "PO": + sqlTableName = "wms_doc_po_details"; + break; + case "MOVE": + sqlTableName = "wms_doc_movement_details_"+organizeCode; + break; + case "CS": + sqlTableName = "wms_doc_cs_details_"+organizeCode; + break; + default: + break; + } + + if (StringUtils.isEmpty(sqlTableName)) { + throw new RuntimeException("单据类型必须是 ASN/PO/MOVE/CS 中的一种!"); + } + + return sqlTableName; + } + + /** + * 将字符串转换成指定的格式(需要指定源字符串的格式) + * + * @param value 要转换的字符串 + * @param srcFormat 原始格式 + * @param destFormat 目标格式 + * @return + * @throws Exception + */ + public String strFormat(String value, String srcFormat, String destFormat) throws Exception { + return TimeTool.parseStringFormat(value, srcFormat, destFormat); + } + /** + * 翻译带参数的字符串 + * + * @param msg 字符串 + * @param args String.format 参数 + * @return 翻译后的字符串 + */ + public static String lz(String msg, Object... args) { + if (!needI18N) { + return String.format(msg, args); + } + return String.format(LocaleUtils.getLocaleRes(msg), args); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbReader.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbReader.java new file mode 100644 index 0000000..738900b --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbReader.java @@ -0,0 +1,256 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.mes.apiservice.serviceimpl.engine.script.EngineScriptManager; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataTable; +import cn.estsh.i3plus.platform.plugin.datasource.DynamicDataSourceProxy; +import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceDataMapper; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.util.List; +import java.util.Map; + +/** + * @Description : 接口数据读取 + * 按照配置规则调用 DAO 层读取数据 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-06-11 19:34 + * @Modify: 1. 2019-10-14 19:13 增加了调用脚本引擎处理数据的逻辑 + * 1. + **/ +@Component +@NoArgsConstructor +public class WmsSAPDbReader extends WmsSAPDbSyncBase { + public static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbReader.class); + + // 动态数据源,靠外部调用类传入此对象 + private DynamicDataSourceProxy dataSourceProxy; + + // 数据库读写用的数据源 + @Getter + @Setter + private Connection srcConn; + + /** + * 使用脚本过滤之前的原始数据集要保留下来,有肯能用这个数据集回写同步标识 + */ + @Getter + private List> originReadData = null; + + // 脚本引擎 + @Autowired + private EngineScriptManager engineScriptManager; + + public WmsSAPDbReader(DynamicDataSourceProxy dataSourceProxy) { + this.dataSourceProxy = dataSourceProxy; + } + + public WmsSAPDbReader(MesInterfaceDataMapper dataMapper) { + super(dataMapper); + } + + /** + * 读取数据 + * @return 读取到的接口表数据 + * @throws Exception 接口映射关系不存在时抛出异常 + */ + public List> readData() throws Exception { + List> resultList; + + this.originReadData = null; + if (this.dataMapper == null) { + throw new IllegalArgumentException("not set data mapper!"); + } + + // 配置了使用脚本引擎获取数据 + if (this.dataMapper.getUseScriptPull() == CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValue()) { + resultList = getDataByScript(); + } else { + resultList = getDataBySQL(); + } + + // 配置了使用脚本引擎过滤来源数据 + if (this.dataMapper.getUseScriptFilter() == CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValue()) { + // 保留原始数据集 + this.originReadData = resultList; + resultList = getDataByFilter(resultList); + } + + return resultList; + } + + /** + * 执行 SQL 语句获取原始数据 + * @return 原始数据 + * @throws Exception 数据库异常 + */ + private List> getDataBySQL() throws Exception { + // 构建查询语句 + String sqlString = buildQuerySQL(isMSSQL()); + LOGGER.info("WmsSAPDbReader.getDataBySQL:{}", sqlString); + return dataSourceProxy.queryMapList(sqlString, srcConn); + } + + /** + * 根据映射表的配置参数构建查询数据的 SQL 语句 + * @param isMSSql 是否 MSSQL 数据库,目前只支持了 MSSQL 和 MYSQL 的判断 + * @return 查询 SQL + */ + public String buildQuerySQL(boolean isMSSql) { + String sqlString; + + if (isMSSql) { + if (StringUtils.isEmpty(this.dataMapper.getSrcGroupColumns())) { + sqlString = "select top " + this.dataMapper.getSrcGetLimit() + + " * from " + this.dataMapper.getSrcTableName() + " model " + + " where " + this.dataMapper.getSrcWhere() + + " order by " + this.dataMapper.getSrcOrderBy(); + } else { + sqlString = "select * from " + this.dataMapper.getSrcTableName() + " model " + + " where " + this.dataMapper.getSrcWhere() + + " and " + this.dataMapper.getSrcGroupColumns() + " in ( " + + " select top " + this.dataMapper.getSrcGetLimit() + " " + + this.dataMapper.getSrcGroupColumns() + + " from " + this.dataMapper.getSrcTableName() + + " where " + this.dataMapper.getSrcWhere() + + " group by " + this.dataMapper.getSrcGroupColumns() + + " )" + " order by " + this.dataMapper.getSrcOrderBy(); + } + } else { + if (StringUtils.isEmpty(this.dataMapper.getSrcGroupColumns())) { + sqlString = "select * from " + this.dataMapper.getSrcTableName() + " model " + + " where " + this.dataMapper.getSrcWhere() + + " order by " + this.dataMapper.getSrcOrderBy() + + " limit " + this.dataMapper.getSrcGetLimit(); + } else { + sqlString = "select * from " + this.dataMapper.getSrcTableName() + + " where " + this.dataMapper.getSrcWhere() + + " and " + this.dataMapper.getSrcGroupColumns() + " in ( " + + " select * from ( " + + " select " + this.dataMapper.getSrcGroupColumns() + + " from " + this.dataMapper.getSrcTableName() + " model " + + " where " + this.dataMapper.getSrcWhere() + + " group by " + this.dataMapper.getSrcGroupColumns() + + " order by " + this.dataMapper.getSrcOrderBy() + " limit " + + this.dataMapper.getSrcGetLimit() + " ) as ds ) order by "+ this.dataMapper.getSrcOrderBy(); + } + } + + return sqlString; + } + + /** + * 执行脚本获取原始数据 + * @return 原始数据 + * @throws Exception 脚本异常 + */ + private List> getDataByScript() throws Exception { + String scriptNo = this.dataMapper.getScriptNo(); + Object returnObj; + + // 如果脚本编号配置的是 groovy 文件,就直接调用文件,便于运行中打断点调试 + if (scriptNo.indexOf(".groovy") > 0) { + returnObj = engineScriptManager.invokeGroovyScript( + scriptNo, + WmsSAPDbConstWords.SCRIPT_PULL_FUN_NAME, + this.dataMapper); + } else { + returnObj = engineScriptManager.invokeFuncation( + this.dataMapper.getOrganizeCode(), + scriptNo, + WmsSAPDbConstWords.SCRIPT_PULL_FUN_NAME, + this.dataMapper); + } + + if (!(returnObj instanceof List)) { + throw new RuntimeException("Script pull data result object type error !"); + } + + return (List>)returnObj; + } + + /** + * 执行脚本过滤原始数据 + * @param srcData 原始数据 + * @return 过滤后的原始数据 + * @throws Exception 脚本异常 + */ + private List> getDataByFilter(List> srcData) throws Exception { + String scriptNo = this.dataMapper.getScriptNo(); + Object returnObj; + + // 如果脚本编号配置的是 groovy 文件,就直接调用文件,便于运行中打断点调试 + if (scriptNo.indexOf(".groovy") > 0) { + returnObj = engineScriptManager.invokeGroovyScript( + scriptNo, + WmsSAPDbConstWords.SCRIPT_FILTER_FUN_NAME, + this.dataMapper, srcData); + } else { + returnObj = engineScriptManager.invokeFuncation( + this.dataMapper.getOrganizeCode(), + this.dataMapper.getScriptNo(), + WmsSAPDbConstWords.SCRIPT_FILTER_FUN_NAME, + this.dataMapper, srcData); + } + + if (!(returnObj instanceof List)) { + throw new RuntimeException("Script filter data result object type error !"); + } + + return (List>)returnObj; + } + + /** + * 判断数据库是不是 MSSQL + * @return + * @throws Exception + */ + private boolean isMSSQL() throws Exception { + return srcConn + .getMetaData().getURL().contains("sqlserver"); + } + + /** + * 读取数据(DataTable 格式) + * @return + */ + public DataTable readDataTable() { + DataTable dataTable = null; + + try { + dataTable = new DataTable(this.dataMapper.getSrcTableName(), this.readData()); + } catch (Exception e) { + e.printStackTrace(); + } + + return dataTable; + } + + /** + * 重写判断对象相等的方法 + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * 重写 hashCode 方法 + * @return + */ + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbSyncBase.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbSyncBase.java new file mode 100644 index 0000000..545848d --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbSyncBase.java @@ -0,0 +1,246 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceDataMapper; +import cn.estsh.i3plus.pojo.wms.dbinterface.MappingItem; +import cn.estsh.i3plus.pojo.wms.dbinterface.UpdateSyncItem; +import com.alibaba.fastjson.JSONObject; +import lombok.Data; +import org.springframework.stereotype.Component; + +import java.util.*; + +/** + * @Description : 接口数据同步基类 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-06-11 19:34 + * @Modify: + * 1. + **/ +@Data +@Component +public class WmsSAPDbSyncBase { + + /** + * 数据映射对象,对应接口库的一张表 + */ + protected MesInterfaceDataMapper dataMapper; + /** + * 源表的排序字段 + */ + protected List scrOrderBy; + /** + * 源表的查询条件 + */ + protected List srcWhere; + /** + * 源表同步成功后需要更新的字段以及值(用表达式替换变量值) + */ + protected List srcUpdateSync; + /** + * 源表的主键 + */ + protected List srcPkColumn; + /** + * 如果此属性不为空,则把源记录复制多份到每个组织下面 + */ + protected List copyByOrg; + /** + * 目标对象的名称(或者表名) + */ + protected List destBeanName; + /** + * 目标表的业务主键,用于判断记录是否存在 + */ + protected List destPkProperty; + /** + * 源表与目标表的字段映射关系,当源表中不存在此字段时用配置的默认值写入到目标表 + */ + protected List mappingItem; + + public WmsSAPDbSyncBase() { } + + public WmsSAPDbSyncBase(MesInterfaceDataMapper dataMapper) { + setDataMapper(dataMapper); + } + + public void setDataMapper(MesInterfaceDataMapper dataMapper) { + this.dataMapper = dataMapper; + + this.setScrOrderBy(getSplitArray(dataMapper.srcOrderBy)); + this.setSrcWhere(getSplitArray(dataMapper.srcWhere)); + this.setSrcUpdateSync(getJSONItems(dataMapper.srcUpdateSync, UpdateSyncItem.class)); + this.setSrcPkColumn(getSplitArray(dataMapper.srcPkColumns)); + this.setCopyByOrg(getSplitArray(dataMapper.copyByOrgs)); + this.setDestPkProperty(getSplitArray(dataMapper.destPkProperties)); + this.setMappingItem(sortedMappingItem(getJSONItems(dataMapper.destColumnMapping, MappingItem.class))); + if (this.getMappingItem() == null) { + this.setMappingItem(new ArrayList<>()); + } + // 依赖于 this.mappingItem 的值,赋值顺序不能变 + this.setDestBeanName(this.getDistinctDestBeanName()); + } + + + /** + * 根据目标表的名称获取主键列表 + * @param beanName 目标表的名称 + * @return 主键列表 + */ + public List getDestPkByBeanName(String beanName) { + List pkList = new ArrayList<>(); + + for (MappingItem item : this.getMappingItem()) { + if (item.getDestBeanName().equals(beanName) && isPk(item)) { + pkList.add(item.getDestName()); + } + } + + return pkList; + } + + /** + * 判断该字段是不是主键 + * @param item 自对对照关系 + * @return 是否主键 + */ + private Boolean isPk(MappingItem item) { + return item.getDestPk() != null && item.getDestPk() == 1; + } + + /** + * 从字段映射关系中获取所有的目标表名 + * @return + */ + private List getDistinctDestBeanName() { + Map beanNamesMap = new HashMap<>(); + + for (MappingItem item : this.getMappingItem()) { + if (!beanNamesMap.containsKey(item.getDestBeanName())) { + beanNamesMap.put(item.getDestBeanName(), null); + } + } + + return new ArrayList(beanNamesMap.keySet()); + } + + /** + * 把半角逗号分隔的字符串转换成字符串列表 + * @param text 半角逗号分隔的字符串 + * @return 分割后的字符串数组 + */ + private List getSplitArray(String text) { + if (text == null || text.isEmpty()) { + return null; + } + + String[] items = text.split(","); + + return Arrays.asList(items); + } + + /** + * 获取字段映射列表 + * @param jsonString JSON 字符串 + * @return 字段映射列表 + */ + public List getJSONItems(String jsonString, Class objClass) { + if (jsonString == null || jsonString.isEmpty()) { + return null; + } + + return JSONObject.parseArray(jsonString, objClass); + } + + /** + * 根据 seq 对 MappingItem 列表进行排序 + * @param mappingItems 字段映射关系集合 + * @return 排序后的字段映射关系集合 + */ + public List sortedMappingItem(List mappingItems) { + if (mappingItems == null) { + return null; + } + + if (mappingItems.size() <= 1) { + return mappingItems; + } + + Collections.sort(mappingItems); + + return mappingItems; + } + + /** + * 安全获取整数值 + * @param obj + * @return + */ + public Integer getSafetyInteger(Object obj) { + if (obj == null) { + return 0; + } + + return Integer.valueOf(String.valueOf(obj)); + } + + /** + * 安全获取整数值 + * + * @param obj + * @return + */ + public Long getSafetyLong(Object obj) { + if (obj == null) { + return 0L; + } + + return Long.valueOf(String.valueOf(obj)); + } + /** + * 安全获取字符串值 + * @param obj + * @return + */ + public String getSafetyString(Object obj) { + if (obj == null) { + return ""; + } + + return String.valueOf(obj); + } + + /** + * 重写判断对象相等的方法 + * 用 JSON 字符串比较可能会性能比较低,逐个字段判断又太繁琐 + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (obj instanceof WmsSAPDbSyncBase) { + WmsSAPDbSyncBase newObj = (WmsSAPDbSyncBase)obj; + + if (JSONObject.toJSONString(this). + equals(JSONObject.toJSONString(newObj))) { + return true; + } + } + + return false; + } + + /** + * 重写 hashCode 方法,实际上这个对象不会作为 key 放到 Hash 中 + * 用 JSON 字符串比较可能会性能比较低,逐个字段判断又太繁琐 + * @return + */ + @Override + public int hashCode() { + return JSONObject.toJSONString(this).hashCode(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbTranslator.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbTranslator.java new file mode 100644 index 0000000..c0143cf --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbTranslator.java @@ -0,0 +1,246 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataRow; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataSet; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataTable; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataTypes; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceDataMapper; +import cn.estsh.i3plus.pojo.wms.dbinterface.MappingItem; +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Description : 接口数据转换 + * 按照配置规则把读取的接口数据转换成业务数据 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-06-11 19:34 + * @Modify: + * 1. + **/ +@Component +public class WmsSAPDbTranslator extends WmsSAPDbSyncBase { + + + @Getter + @Setter + private WmsSAPDbExpression wmsSAPDbExpression; + + public WmsSAPDbTranslator() { } + + public WmsSAPDbTranslator(WmsSAPDbExpression wmsSAPDbExpression) { + this.wmsSAPDbExpression = wmsSAPDbExpression; + } + + public WmsSAPDbTranslator(MesInterfaceDataMapper dataMapper) { + super(dataMapper); + } + + /** + * 把原始数据表按照字段对应关系转换成目标数据集(存在一对多的关系) + * @param originData + * @return + * @throws Exception + */ + public DataSet translateData(List> originData) throws Exception { + if (this.dataMapper == null) { + throw new IllegalArgumentException("not set data mapper!"); + } + + // 原始数据表 + DataTable originTable = new DataTable(this.dataMapper.srcTableName, originData); + + // 构建目标数据集 + DataSet destDataSet = buildDestDataSet(this.getDestBeanName(), this.getMappingItem()); + + for(DataRow row : originTable.getRows()) { + // 拆分数据行 + Map destRowsMap = splitDataRow(destDataSet, row); + + // 把拆分好的数据行放到目标数据表中 + for(DataTable table : destDataSet.getDataTables()) { + if (destRowsMap.containsKey(table.getTableName())) { + table.addRow(destRowsMap.get(table.getTableName())); + } + } + } + + return destDataSet; + } + + /** + * 把原始数据行拆分成目标数据行,存在一对多的情况 + * @param dataSet + * @param originRow + * @return + * @throws Exception + */ + private Map splitDataRow(DataSet dataSet, DataRow originRow) throws Exception { + // 最终返回的 Map + Map rowsMap = new HashMap<>(); + + // 构建临时数据行对象 + for (DataTable table : dataSet.getDataTables()) { + rowsMap.put(table.getTableName(), table.newRow()); + } + + // 遍历字段映射列表 + for (MappingItem item : this.getMappingItem()) { + // 原始表中有这个字段 + if (originRow.getColumns().contains(item.getSrcName())) { + // 把来源数据行的值放到对应的目标数据行中 + rowsMap.get(item.getDestBeanName()). + setValue(item.getDestName(), originRow.getValue(item.getSrcName())); + } else { + // 原始表中没有这个字段,用预设的默认值填充此字段 + rowsMap.get(item.getDestBeanName()). + setValue(item.getDestName(), item.getDefaultValue()); + } + } + + // 遍历第二遍,进行默认值的表达式计算,此时所有属性都已赋值,在配置字段映射关系就可以不用管属性的配置顺序 + // 如果强制要求配置字段映射关系时把有表达式的属性放在后面,也可以不用再循环一遍,直接在上面的循环解析即可 + for (MappingItem item : this.getMappingItem()) { + if (item.getDefaultValue() != null // 不是空对象 + && (item.getDefaultValue() instanceof String) // 字符串类型 + && !item.getDefaultValue().toString().isEmpty() // 字符串不为空 + && item.getDefaultValue().toString().indexOf("#") > -1) { // 包含表达式 + + + // 取出当前字段的值 + //Object valueObj = rowsMap.get(item.getDestBeanName()).getValue(item.getDestName()); + // 把当前行的数据(原始数据行和目标数据行都参与计算)和表达式进行计算 + Object valueObj = parseDefaultValue( + combineRowMap(originRow.getItemMap(), rowsMap.get(item.getDestBeanName()).getItemMap()), + item.getDefaultValue()); + // 把计算后的值设置回数据行中 + rowsMap.get(item.getDestBeanName()).setValue(item.getDestName(), valueObj); + } + } + + return rowsMap; + } + + /** + * 合并来源表的所有字段和目标表的所有字段 + * 为了防止字段重复,来源表的字段名统一加上 _o 后缀(暂时不加后缀,因为两套系统的字段编码格式很少相同) + * @param originMap 来源表的字段 + * @param destMap 目标表的字段 + * @return 合并后的 Map + */ + private Map combineRowMap(Map originMap, Map destMap) { + Map combineMap = new HashMap<>(); + +// if (originMap != null && originMap.size() > 0) { +// for(Map.Entry entry : originMap.entrySet()) { +// // 来源表的字段名加上 _o 后缀,防止跟目标表的字段重名 +// combineMap.put(entry.getKey() + "_o", entry.getValue()); +// } +// } + + if (originMap != null && originMap.size() > 0) { + combineMap.putAll(originMap); + } + + if (destMap != null && destMap.size() > 0) { + combineMap.putAll(destMap); + } + + return combineMap; + } + + /** + * 构建目标数据集(可能存在一张原始表对应多张目标表) + * @param destBeanNames + * @param mappingItems + * @return + * @throws Exception + */ + private DataSet buildDestDataSet(List destBeanNames, List mappingItems) throws Exception { + // 目标数据集 + DataSet destDataSet = new DataSet(); + + // 构建目标表 + for(String tableName : destBeanNames) { + DataTable table = new DataTable(tableName); + + // 构建目标表的结构 + for(MappingItem item : mappingItems) { + // 当前字段属于这张表,并且目标字段不为空 + if (item.getDestBeanName().equals(tableName) && item.destName != null && !item.destName.isEmpty()) { + // 加入字段 + // 此处暂不进行表达式计算 + table.addColumn(item.getDestName(), DataTypes.getDataType(item.getDefaultValue())); + } + } + + // 把数据表加入数据集 + destDataSet.add(table); + } + + return destDataSet; + } + + /** + * 根据表达式计算默认值 + * @param rowMap 当前行的数据 Map + * @param defaultValue 原始默认值(可能包含表达式) + * @return 计算后的默认值 + */ + private Object parseDefaultValue(Map rowMap, Object defaultValue) throws Exception { + if (defaultValue == null) { + return null; + } + + // 不是字符串类型,直接返回 + if (!(defaultValue instanceof String)) { + return defaultValue; + } + + // 没有包含表达式,直接返回 + if (defaultValue.toString().indexOf("#") == -1) { + return defaultValue; + } + + Object objResult; + + try { + objResult = wmsSAPDbExpression.parse(defaultValue.toString(), rowMap); + } catch (Exception e) { + e.printStackTrace(); + + throw new IllegalArgumentException("表达式计算异常,请检查表达式语法是否正确! exp: " + + defaultValue.toString() + " Exception: " + + e.getMessage() + " rowMap:" + + JSONObject.toJSONString(rowMap)); + } + + // 计算后返回 + return objResult; + } + + /** + * 重写判断对象相等的方法 + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * 重写 hashCode 方法 + * @return + */ + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbUtil.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbUtil.java new file mode 100644 index 0000000..16b62ac --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbUtil.java @@ -0,0 +1,313 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.icloud.softswitch.sdk.IBsSuitServiceCloud; +import cn.estsh.i3plus.platform.common.exception.ImppExceptionEnum; +import cn.estsh.i3plus.pojo.base.bean.BaseResultBean; +import cn.estsh.i3plus.pojo.base.enumutil.BlockSoftSwitchEnumUtil; +import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil; +import cn.estsh.i3plus.pojo.softswitch.bean.BsSuitCase; +import cn.estsh.i3plus.pojo.softswitch.bean.BsSuitCaseParam; +import cn.estsh.i3plus.pojo.softswitch.bean.BsSuitDataDetail; +import cn.estsh.impp.framework.boot.exception.ImppExceptionBuilder; +import cn.estsh.impp.framework.boot.util.ResultBean; +import lombok.NoArgsConstructor; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@NoArgsConstructor +public class WmsSAPDbUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbUtil.class); + + // 软适配微服调用代理对象 + @Autowired + IBsSuitServiceCloud bsSuitServiceCloud; + + + /** + * 从 XML 格式的报文中抽取数据 + * @param xmlString XML 报文 + * @param tableList XML 报文中的数据对象结构,要按从大到小排序 + * @return 抽取到的数据,扁平化的 List> 格式 + * @throws Exception XML 解析异常 + */ + public List> convertXmlToList(String xmlString, List tableList) + throws Exception { + Document document = DocumentHelper.parseText(xmlString); + Element rootElement = document.getRootElement(); + List elements = rootElement.selectNodes("//*"); + + List> rows = new ArrayList<>(); + + for (Element element : elements) { + if (tableList.get(0).equals(element.getName())) { + Map row = new HashMap<>(); + parseElement(element, row, rows, tableList); + } + } + + return rows; + } + + /** + * 从 XML 格式的报文中抽取数据 + * @param xmlString XML 报文 + * @param tables 数据结构的层级,用半角逗号分隔 + * @return 抽取到的数据,扁平化的 List> 格式 + * @throws Exception XML 解析异常 + */ + public List> convertXmlToList(String xmlString, String tables) + throws Exception { + List tableList = this.getListByString(tables); + + if (tableList == null || tableList.size() == 0) { + return null; + } + + return convertXmlToList(xmlString, tableList); + } + + /** + * 把 List 格式的数据转换成 XML 字符串 + * @param listData 原始数据 + * @param rootNodeName 根节点名称 + * @param nodeName 叶子节点名称 + * @param rootClassName 根节点的类名 + * @param nodeClassName 叶子节点的类名 + * @return XML 字符串 + */ + public String convertListToXml(List> listData, String rootNodeName, String nodeName, + String rootClassName, String nodeClassName) { + if (listData == null || listData.size() == 0) { + throw new RuntimeException("listData is null ! "); + } + + StringBuilder sb = new StringBuilder(); + + if (rootNodeName != null && !rootNodeName.isEmpty()) { + sb.append(String.format("<%s class=\"%s\">\n", rootNodeName, rootClassName)); + } + + for(Map row : listData) { + sb.append(String.format("<%s class=\"%s\">\n", nodeName, nodeClassName)); + + for (Map.Entry entry : row.entrySet()) { + sb.append(String.format("<%s>%s\n", entry.getKey(), entry.getValue(), entry.getKey())); + } + + sb.append(String.format("\n", nodeName)); + } + + if (rootNodeName != null && !rootNodeName.isEmpty()) { + sb.append(String.format("\n", rootNodeName)); + } + + return sb.toString(); + } + + /** + * 把报文的 ID 加入到数据集中(增加一列 SID) + * @param listData 原始数据集 + * @param sid 报文 ID + */ + public void packSID(List> listData, Long sid) { + if (listData == null || listData.size() == 0) { + return; + } + + for (Map row : listData) { + row.put("SID", sid); + } + } + + /** + * 把半角逗号分隔的字符串转成 List 格式 + * @param strList 半角逗号分隔的字符串 + * @return List + */ + public List getListByString(String strList) { + if (strList == null || strList.isEmpty()) { + return null; + } + + String[] arrList = strList.split(","); + + return Arrays.asList(arrList); + } + + /** + * 递归函数,用户解析 XML 报文并抽取数据 + * @param element XML 元素 + * @param row 当前数据行 + * @param rows 数据行列表 + * @param tableList 数据对象的结构 + */ + private void parseElement(Element element, Map row, + List> rows, List tableList) { + + List childElements = element.elements(); + + if (childElements != null) { + // 先遍历没有子节点的属性,因为有属性值在子节点之后,所以必须先遍历一遍 + for (Element eleData : childElements) { + if (eleData.elements() == null || eleData.elements().size() == 0) { + row.put(element.getName() + "_" + eleData.getName(), eleData.getStringValue().trim()); + } + } + + // 再遍历包含子节点的属性 + for (Element ele : childElements) { + if (tableList.contains(ele.getName()) || ele.getName().startsWith("List")) { + parseElement(ele, row, rows, tableList); + } + } + + // 末尾元素,开始新增行 + if (element.getName().equals(tableList.get(tableList.size() - 1))) { + HashMap newRow = new HashMap<>(); + newRow.putAll(row); + rows.add(newRow); + } + } + } + + /** + * 从软适配中按照适配编号获取报文(微服务调用) + * @param suitCaseCode 适配编号 + * @param packageCount 获取报文的数量 + * @return 报文对象 + */ + public ResultBean readSoftSwitchPackage(String suitCaseCode, Integer packageCount) { + BaseResultBean resultBean = + bsSuitServiceCloud.findLastUnProcessBsSuitDataDetailBySuitCaseCode(suitCaseCode, packageCount); + + if (resultBean.isSuccess()) { + List bsSuitDataDetailList = resultBean.getResultList(); + + return new ResultBean(true).setResultObject(bsSuitDataDetailList); + } else { + throw ImppExceptionBuilder.newInstance() + .setSystemID(CommonEnumUtil.SOFT_TYPE.WMS.getCode()) + .setErrorCode(ImppExceptionEnum.CLOUD_CALL_EXCEPTION.getCode()) + .setErrorDetail("微服调用失败: " + resultBean.getErrorMsg()) + .build(); + } + } + + /** + * 按照报文编号调用软适配,把报文标记为已处理 + * @param packageIds 报文编号 + * @return 标记结果 + */ + public boolean ackSoftSwitchPackage(Long[] packageIds) { + BaseResultBean resultBean = + bsSuitServiceCloud.updateBsSuitDataDetailProcessStateByIds( + packageIds, BlockSoftSwitchEnumUtil.SUIT_PROCESS_STATUS.PROCESS.getValue()); + + if (resultBean.isSuccess()) { + return true; + } else { + throw ImppExceptionBuilder.newInstance() + .setSystemID(CommonEnumUtil.SOFT_TYPE.WMS.getCode()) + .setErrorCode(ImppExceptionEnum.CLOUD_CALL_EXCEPTION.getCode()) + .setErrorDetail("微服调用失败: " + resultBean.getErrorMsg()) + .build(); + } + } + + /** + * 调用微服务接口发送报文 + * @param suitCaseCode 适配编号 + * @param xmlString XML 报文 + */ + public void sendSoftSwitchPackage(String suitCaseCode, String xmlString) { + LOGGER.info("WmsSAPDbUtil.sendSoftSwitchPackage input:suitCaseCode:{} ;xmlString:{}",suitCaseCode,xmlString); + BsSuitCase bsSuitCase = new BsSuitCase(); + // 设置适配器代码 + bsSuitCase.setSuitCaseCode(suitCaseCode); + bsSuitCase.setBsSuitCaseParamList(new ArrayList<>()); + bsSuitCase.setParamXml(xmlString); + + BaseResultBean resultBean = + bsSuitServiceCloud.executeBsSuitCase(bsSuitCase); + LOGGER.info("bsSuitServiceCloud.executeBsSuitCase result:{}", resultBean); + if (!resultBean.isSuccess()) { + throw ImppExceptionBuilder.newInstance() + .setSystemID(CommonEnumUtil.SOFT_TYPE.WMS.getCode()) + .setErrorCode(ImppExceptionEnum.CLOUD_CALL_EXCEPTION.getCode()) + .setErrorDetail("bsSuitServiceCloud.executeBsSuitCase failed: %s; suitCaseCode:%s; xmlString:%s", resultBean.getErrorMsg(), suitCaseCode, xmlString) + .build(); + } + } + + /** + * 以带参数名的方式调用微服务接口发送报文 + * + * @param suitCaseCode 适配编号 + * @param xmlString XML 报文 + */ + public void sendSoftSwitchPackageWithParams(String suitCaseCode, String xmlString) { + BsSuitCase bsSuitCase = new BsSuitCase(); + // 设置适配器代码 + bsSuitCase.setSuitCaseCode(suitCaseCode); + + List bsSuitCaseParamList = new ArrayList<>(); + BsSuitCaseParam bsSuitCaseParam = new BsSuitCaseParam(); + bsSuitCaseParam.setParamName("params"); + bsSuitCaseParam.setParamValue(xmlString); + bsSuitCaseParamList.add(bsSuitCaseParam); + bsSuitCase.setBsSuitCaseParamList(bsSuitCaseParamList); // 多个数据 + + BaseResultBean resultBean = + bsSuitServiceCloud.executeBsSuitCase(bsSuitCase); + LOGGER.info("bsSuitServiceCloud.executeBsSuitCase result:{}",resultBean); + if (!resultBean.isSuccess()) { + throw ImppExceptionBuilder.newInstance() + .setSystemID(CommonEnumUtil.SOFT_TYPE.WMS.getCode()) + .setErrorCode(ImppExceptionEnum.CLOUD_CALL_EXCEPTION.getCode()) + .setErrorDetail("bsSuitServiceCloud.executeBsSuitCase failed: %s; suitCaseCode:%s; xmlString:%s",resultBean.getErrorMsg(), suitCaseCode, xmlString) + .build(); + } + } + + /** + * 以带参数名的方式调用微服务接口发送报文 + * + * @param suitCaseCode 适配编号 + * @param resultMap 发送的数据 + */ + public void sendSoftSwitchPackageWithMap(String suitCaseCode, Map resultMap) { + BsSuitCase bsSuitCase = new BsSuitCase(); + // 设置适配器代码 + bsSuitCase.setSuitCaseCode(suitCaseCode); + + List bsSuitCaseParamList = new ArrayList<>(); + Iterator iterator = resultMap.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + BsSuitCaseParam bsSuitCaseParam = new BsSuitCaseParam(); + bsSuitCaseParam.setParamName(key); + bsSuitCaseParam.setParamValue(resultMap.get(key)); + bsSuitCaseParamList.add(bsSuitCaseParam); + } + bsSuitCase.setBsSuitCaseParamList(bsSuitCaseParamList); // 多个数据 + BaseResultBean resultBean = + bsSuitServiceCloud.executeBsSuitCase(bsSuitCase); + LOGGER.info("bsSuitServiceCloud.executeBsSuitCase result:{}",resultBean); + if (!resultBean.isSuccess()) { + throw ImppExceptionBuilder.newInstance() + .setSystemID(CommonEnumUtil.SOFT_TYPE.WMS.getCode()) + .setErrorCode(ImppExceptionEnum.CLOUD_CALL_EXCEPTION.getCode()) + .setErrorDetail("bsSuitServiceCloud.executeBsSuitCase failed: %s; suitCaseCode:%s, data:%s", resultBean.getErrorMsg(), suitCaseCode, resultMap.toString()) + .build(); + } + } + +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbWriter.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbWriter.java new file mode 100644 index 0000000..137a467 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/dbinterface/WmsSAPDbWriter.java @@ -0,0 +1,533 @@ +package cn.estsh.i3plus.ext.mes.apiservice.dbinterface; + +import cn.estsh.i3plus.mes.apiservice.serviceimpl.engine.script.EngineScriptManager; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataRow; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataSet; +import cn.estsh.i3plus.mes.apiservice.util.datatable.DataTable; +import cn.estsh.i3plus.platform.plugin.datasource.DynamicDataSourceProxy; +import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil; +import cn.estsh.i3plus.pojo.base.util.StringUtil; +import cn.estsh.i3plus.pojo.wms.dbinterface.UpdateSyncItem; +import cn.estsh.i3plus.pojo.wms.dbinterface.WmsInterfaceEnumUtil; +import cn.estsh.impp.framework.boot.util.ImppRedis; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description : 业务数据写入 + * 按照配置的规则把业务数据写入业务库 + * @Reference : + * @Author : rock.yu + * @CreateDate : 2019-06-11 19:34 + * @Modify: 1. 2019-10-14 19:13 增加了调用脚本引擎处理数据的逻辑 + **/ +@Component +@NoArgsConstructor +public class WmsSAPDbWriter extends WmsSAPDbSyncBase { + + public static final Logger LOGGER = LoggerFactory.getLogger(WmsSAPDbWriter.class); + + public static final int REQ_COUNT = 20; + + @Getter + private DynamicDataSourceProxy srcDataSourceProxy; + @Getter + private DynamicDataSourceProxy destDataSourceProxy; + + // 脚本引擎 + @Autowired + private EngineScriptManager engineScriptManager; + + @Resource(name = "redisMes") + private ImppRedis redisMes; + + @Getter + @Setter + private WmsSAPDbExpression wmsSAPDbExpression; + + // 数据库读写用的数据源 + @Getter + @Setter + private Connection srcConn; + @Getter + @Setter + private Connection destConn; + + public WmsSAPDbWriter(DynamicDataSourceProxy srcDataSourceProxy, DynamicDataSourceProxy destDataSourceProxy) { + this.srcDataSourceProxy = srcDataSourceProxy; + this.destDataSourceProxy = destDataSourceProxy; + } + + /** + * 保存来源和目标数据 + * 如果 originDataList 不为空,则表示来源数据集被脚本过滤过,需要使用原始查询到的数据集回写来源表的更新标识 + */ + public void saveData(List> srcDataList, DataSet destDataSet, List> originDataList) throws Exception { + // 先保存目标数据 + // 配置了使用脚本引擎保存数据 + try { + if (this.dataMapper.getUseScriptPush() == CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValue()) { + callScript(destDataSet, WmsSAPDbConstWords.SCRIPT_PUSH_FUN_NAME); + } else { + saveDestData(destDataSet); + } + } catch (Exception e) { + //记录sap过来的数据 超过一定的请求次数放弃数据 + if (WmsInterfaceEnumUtil.DIRECTION_TYPE.SAP2WMS.getName().equals(this.dataMapper.getGroupName())) { + if (!CollectionUtils.isEmpty(srcDataList)) { + String key = this.dataMapper.getSoftAdaptorCode() + ":" + srcDataList.get(0).get("SID"); + if (redisMes.hasKey(key)) { + int reqCount = Integer.parseInt(String.valueOf(redisMes.getObject(key))); + if (reqCount + 1 > REQ_COUNT) { + if (this.dataMapper.getUseScriptMark() == CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValue()) { + callScriptMark(srcDataList, originDataList); + } else { + callSqlMark(srcDataList, originDataList); + } + LOGGER.error("超过请求次数放弃数据 WmsSAPDbWriter.saveData SoftAdaptorCode:{}, SID:{}", this.dataMapper.getSoftAdaptorCode(), srcDataList.get(0).get("SID")); + return; + } else { + redisMes.incr(key, 1); + } + } else { + redisMes.incr(key, 1); + } + } + } + throw e; + } + + // 再更新来源数据的同步标志 + // 配置了使用脚本引擎标记来源数据 + if (this.dataMapper.getUseScriptMark() == CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValue()) { + callScriptMark(srcDataList, originDataList); + } else { + callSqlMark(srcDataList, originDataList); + } + } + + /** + * 执行SQL回写来源表的更新标记 + * @param srcDataList 来源数据集 + * @param originDataList 原始查询到的数据集 + * @throws Exception + */ + private void callSqlMark(List> srcDataList, List> originDataList) + throws Exception { + if (originDataList != null) { + saveSrcData(originDataList); + } else { + saveSrcData(srcDataList); + } + } + + /** + * 执行脚本回写来源表的更新标记 + * @param srcDataList 来源数据集 + * @param originDataList 原始查询到的数据集 + * @throws Exception + */ + private void callScriptMark(List> srcDataList, List> originDataList) + throws Exception { + if (originDataList != null) { + callScript(originDataList, WmsSAPDbConstWords.SCRIPT_MARK_FUN_NAME); + } else { + callScript(srcDataList, WmsSAPDbConstWords.SCRIPT_MARK_FUN_NAME); + } + } + + /** + * 调用脚本引擎处理数据 + * @param data 数据集 + * @param funName 调用脚本中的方法名称 + * @throws Exception 脚本执行异常 + */ + private void callScript(Object data, String funName) throws Exception { + // 调用脚本引擎写入目标数据 + String scriptNo = this.dataMapper.getScriptNo(); + + // 如果脚本编号配置的是 groovy 文件,就直接调用文件,便于运行中打断点调试 + if (scriptNo.indexOf(".groovy") > 0) { + engineScriptManager.invokeGroovyScript( + scriptNo, + funName, + this.dataMapper, + data); + } else { + engineScriptManager.invokeFuncation( + this.dataMapper.getOrganizeCode(), + scriptNo, + funName, + this.dataMapper, + data); + } + } + + /** + * 保存目标数据 + * + * @param destDataSet + * @throws Exception + */ + public void saveDestData(DataSet destDataSet) throws Exception { + if (destDataSet == null || destDataSet.getDataTables().size() == 0) { + LOGGER.error("no data in dest dataset!"); + throw new IllegalArgumentException("no data in dest dataset!"); + } + + List sqlStringList = new ArrayList<>(); + // 保存主键查询的 SQL 语句,用于判断表内数据行是否重复 + Map sqlCacheMap = new HashMap<>(); + + // 循环处理每一行目标表数据 + for (DataTable table : destDataSet.getDataTables()) { + for (DataRow destRow : table.getRows()) { + // 生成 SQL 语句 + sqlStringList.add(packRowSQL(destRow, sqlCacheMap)); + } + } + + try { + // 作为一个事务批量执行 + destDataSourceProxy.executeAsBatch(sqlStringList, destConn); + for(String sql : sqlStringList) { + LOGGER.info(sql); + } +// LOGGER.info(sqlStringList.toString()); + } catch (Exception e) { + // 包装异常信息,把 SQL 加进去,便于调试 + throw new SQLException("执行SQL异常" + e.getMessage() + " " + + getSqlStrings(sqlStringList)); + } + } + + /** + * 包装 SQL 语句,供测试参考 + * + * @param sqlStringList + * @return + */ + private String getSqlStrings(List sqlStringList) { + if (sqlStringList == null) { + return null; + } + + StringBuffer sb = new StringBuffer(); + int index = 0; + + for (String sql : sqlStringList) { + sb.append(String.valueOf(index) + " < "); + sb.append(sql); + sb.append("> "); + } + + return sb.toString(); + } + + /** + * 更新来源数据 + * + * @param srcDataList + * @throws Exception + */ + private void saveSrcData(List> srcDataList) throws Exception { + if (srcDataList == null || srcDataList.size() == 0) { + LOGGER.error("no data in src datalist!"); + throw new IllegalArgumentException("no data in src datalist!"); + } + + List sqlStringList = new ArrayList<>(); + // 存放插入备份表的SQL语句列表 + List insertSqlStringList = new ArrayList<>(); + // 存放删除来源表的SQL语句列表 + List deleteSqlStringList = new ArrayList<>(); + + List saveAndRemoveList = new ArrayList<>(); + + String sqlString; + + HashMap srcPkMap = new HashMap<>(); + + // 循环处理每一行数据 + for (Map srcRow : srcDataList) { + // 先清空主键 Map + srcPkMap.clear(); + if (StringUtil.isEmpty(this.getSrcPkColumn())) { + LOGGER.error("WmsSAPDbWriter.saveSrcData params srcPkColumn is null!"); + throw new Exception("WmsSAPDbWriter.saveSrcData params srcPkColumn is null"); + } + // 构建主键 Map + for (String column : this.getSrcPkColumn()) { + srcPkMap.put(column, srcRow.get(column)); + } + + // 把来源表的数据行放到表达式中,确保表达式可以引用 + HashMap updateValueMap = getUpdateSyncMap(srcRow); + // 存在来源表不需要更新同步标志的情况 + if (updateValueMap.size() > 0) { + // 拼接更新来源表同步标志的 SQL LIST + sqlString = srcDataSourceProxy.packUpdateSQL(this.dataMapper.getSrcTableName(), srcPkMap, + updateValueMap); + // 如果设置了分组处理,在回写来源表时就要加上原始的 Where 条件,以防止多更新数据 + if (StringUtils.isNotBlank(this.dataMapper.getSrcGroupColumns())) { + // 先去掉原来拼接的分号 + sqlString = sqlString.replace(";"," ") + " and (" + this.dataMapper.getSrcWhere() + "); "; + } + + sqlStringList.add(sqlString); + } + + // 添加处理完成的数据及时剪切到备份表的逻辑 + if (!StringUtils.isBlank(this.dataMapper.getCutToTableName())) { + // 插入历史表的语句 + sqlString = srcDataSourceProxy.packInsertSQL(this.dataMapper.getCutToTableName(), (HashMap)srcRow); + insertSqlStringList.add(sqlString); + + // 删除来源表的语句 + sqlString = srcDataSourceProxy.packDeleteSQL(this.dataMapper.getSrcTableName(), srcPkMap); +// // 如果设置了分组处理,在回写来源表时就要加上原始的 Where 条件,以防止多更新数据 +// if (StringUtils.isNotBlank(this.dataMapper.getSrcGroupColumns())) { +// // 先去掉原来拼接的分号 +// sqlString = sqlString.replace(";"," ") + " and (" + this.dataMapper.getSrcWhere() + "); "; +// } + deleteSqlStringList.add(sqlString); + } + } + + try { + if (sqlStringList.size() > 0) { + // 去掉重复的 SQL + List uniqeSqlList = sqlStringList.stream().distinct().collect(Collectors.toList()); + // 批量更新来源表的同步标志 + srcDataSourceProxy.executeAsBatch(uniqeSqlList, srcConn); + } + } catch (Exception e) { + // 包装异常信息,把 SQL 加进去,便于调试 + throw new SQLException("执行SQL异常" + e.getMessage() + " " + + getSqlStrings(sqlStringList)); + } + try { + if (insertSqlStringList.size() > 0) { + saveAndRemoveList.addAll(insertSqlStringList); + } + if (deleteSqlStringList.size() > 0) { + saveAndRemoveList.addAll(deleteSqlStringList); + } + if (saveAndRemoveList.size() > 0) { + // 去掉重复的 SQL + LOGGER.info("执行迁移的sql脚本为:sqlString:"+getSqlStrings(saveAndRemoveList)); + List uniqeSqlList = saveAndRemoveList.stream().distinct().collect(Collectors.toList()); + //插入备份表 & 删除来源表数据 + srcDataSourceProxy.executeAsBatch(uniqeSqlList, srcConn); + } + } catch (Exception e) { + // 包装异常信息,把 SQL 加进去,便于调试 + throw new SQLException("执行SQL异常" + e.getMessage() + " " + + getSqlStrings(saveAndRemoveList)); + } + } + + /** + * 根据表达式获来源表取待更新的值Map + * + * @return + */ + private HashMap getUpdateSyncMap(Map srcRow) throws Exception { + HashMap updateSyncMap = new HashMap<>(); + + if (this.getSrcUpdateSync() == null) { + return updateSyncMap; + } + + for (UpdateSyncItem item : this.getSrcUpdateSync()) { + Object updateValue = item.getUpdateValue(); + + // 如果包含表达式,则计算后赋值 + if (item.getUpdateValue().indexOf("#") != -1) { + updateValue = wmsSAPDbExpression.parse(item.getUpdateValue(), srcRow); + } + + updateSyncMap.put(item.getColumnName(), updateValue); + } + + return updateSyncMap; + } + + + /** + * 生成保存一行数据到目标表的SQL + * + * @param row + * @throws Exception + */ + private String packRowSQL(DataRow row, Map sqlCacheMap) throws Exception { + if (row == null || row.getColumns().size() == 0) { + LOGGER.error("no column in row!"); + throw new IllegalArgumentException("no column in row!"); + } + + // 获取主键列表 + List destPks = getPkList(row); + + // 获取主键查询 SQL & KeyMap + SqlAndKeyMap sqlAndKeyMap = new SqlAndKeyMap(row, destPks).invoke(); + String sqlString = sqlAndKeyMap.getSqlString(); + Map pkMap = sqlAndKeyMap.getPkMap(); + + // 按照主键从业务表查询数据 + List> resutlMap = destDataSourceProxy.queryMapList(sqlString, destConn); + + // 判断按主键查询的 SQL 有没有出现过 + Boolean dataDupInBatch = sqlCacheMap.containsKey(sqlString); + + // 没出现过,加入缓存中 + if (!dataDupInBatch) { + sqlCacheMap.put(sqlString, 1); + } + + // 最终要生成的插入或更新SQL + String sqlResult; + + // 目标数据存在或者在本次处理的数据中重复则更新,不存在则新增 + if ((resutlMap != null && resutlMap.size() > 0) || dataDupInBatch) { + HashMap rowMap = (HashMap) row.getItemMap(); + + // id 不用更新 + if (rowMap.containsKey("id")) { + rowMap.remove("id"); + } + + // 过滤标记为 NotTrans 数据项,这些数据不更新到数据库 + Iterator> iterator = rowMap.entrySet().iterator(); + Object value; + while (iterator.hasNext()) { + value = iterator.next().getValue(); + if (value != null && value.toString().endsWith(WmsSAPDbExpression.NOT_TRANS)) { + iterator.remove(); + } + } + + sqlResult = destDataSourceProxy.packUpdateSQL(row.getTable().getTableName(), (HashMap) pkMap,rowMap); + } else { + HashMap rowMap = (HashMap) row.getItemMap(); + // 带 NotTrans 的数据先把 :NotTrans 或者 NotTrans 标记去掉 + for (Map.Entry entry : rowMap.entrySet()) { + if (entry.getValue() != null && entry.getValue().toString().endsWith(WmsSAPDbExpression.NOT_TRANS)) { + String transValue = entry.getValue().toString(); + transValue = transValue.replace(":" + WmsSAPDbExpression.NOT_TRANS, ""); + transValue = transValue.replace(WmsSAPDbExpression.NOT_TRANS, ""); + LOGGER.debug("NotTrans Item: {} Insert before: {} after: {}", entry.getKey(), entry.getValue(), transValue); + rowMap.put(entry.getKey(), transValue); + } + } + + sqlResult = destDataSourceProxy.packInsertSQL(row.getTable().getTableName(), + (HashMap) row.getItemMap()); + } + + return sqlResult; + } + + /** + * 获取主键列表 + * + * @param row + * @return + */ + private List getPkList(DataRow row) { + // 先查找外面配置的主键 + List destPks = this.getDestPkProperty(); + + // 一表对多表,主键设置在字段对照表里 + if (destPks == null || destPks.isEmpty()) { + destPks = this.getDestPkByBeanName(row.getTable().getTableName()); + } + + // 没有找到主键时抛出异常 + if (destPks == null || destPks.isEmpty()) { + LOGGER.error("no pk in table: " + row.getTable().getTableName()); + throw new IllegalArgumentException("no pk in table: " + row.getTable().getTableName()); + } + + return destPks; + } + + + /** + * 内部类,用于拼接主键查询 SQL & KeyMap + */ + private class SqlAndKeyMap { + private DataRow row; + private List destPks; + private String sqlString; + private Map pkMap; + + public SqlAndKeyMap(DataRow row, List destPks) { + this.row = row; + this.destPks = destPks; + } + + public String getSqlString() { + return sqlString; + } + + public Map getPkMap() { + return pkMap; + } + + public SqlAndKeyMap invoke() { + sqlString = "select 1 from " + row.getTable().getTableName() + + " where 1=1 "; + + // 目标表的主键列表 + pkMap = new HashMap<>(); + + for (String column : destPks) { + String value = WmsSAPDbWriter.this.getSafetyString(row.getValue(column)); + // 处理掉 NotTrans 字段 + if (value != null && value.contains(WmsSAPDbExpression.NOT_TRANS)) { + value = value.replace(":" + WmsSAPDbExpression.NOT_TRANS, ""); + value = value.replace(WmsSAPDbExpression.NOT_TRANS, ""); + } + sqlString += "and " + column + "='" + value + "' "; + pkMap.put(column, row.getValue(column)); + } + + return this; + } + } + + /** + * 重写判断对象相等的方法 + * + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * 重写 hashCode 方法 + * + * @return + */ + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/schedulejob/MesSAPDbTransByWorksJob.java b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/schedulejob/MesSAPDbTransByWorksJob.java new file mode 100644 index 0000000..f61e023 --- /dev/null +++ b/modules/i3plus-ext-mes-apiservice/src/main/java/cn/estsh/i3plus/ext/mes/apiservice/schedulejob/MesSAPDbTransByWorksJob.java @@ -0,0 +1,103 @@ +package cn.estsh.i3plus.ext.mes.apiservice.schedulejob; + +import cn.estsh.i3plus.ext.mes.apiservice.dbinterface.MesSAPDbAdapter; +import cn.estsh.i3plus.mes.apiservice.schedulejob.BaseMesScheduleJob; +import cn.estsh.i3plus.platform.common.tool.JsonUtilTool; +import cn.estsh.i3plus.pojo.mes.dbinterface.MesInterfaceEnumUtil; +import cn.estsh.i3plus.pojo.model.wms.WmsJobParamModel; +import cn.estsh.impp.framework.boot.init.ApplicationProperties; +import cn.estsh.impp.framework.boot.util.SpringContextsUtil; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * @Description : 按工厂划分:把 SAP 接口表的数据同步到 MES 业务表中(单据+基础数据) + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 11:03 + * @Modify: + **/ +// 禁止 JOB 并发执行 +@DisallowConcurrentExecution +@ApiOperation("SAP接口表数据同步作业按工厂划分") +public class MesSAPDbTransByWorksJob extends BaseMesScheduleJob { + + public static final Logger LOGGER = LoggerFactory.getLogger(MesSAPDbTransByWorksJob.class); + + private static final long serialVersionUID = 9072058154235836696L; + + @Value("${server.port}") + private String serverPort; + @Value("${impp.server.ip}") + private String ip; + + public MesSAPDbTransByWorksJob() { + super(MesSAPDbTransByWorksJob.class, "SAP接口表数据同步作业按工厂划分"); + this.setMultiInstance(true); + } + + @Override + public void executeMesJob(JobExecutionContext context, ApplicationProperties applicationProperties) { + try { + String jobParam = this.getJobParam(); + + if (StringUtils.isBlank(jobParam)) { + throw new IllegalArgumentException("jobc参数为空,请检查参数"); + } + + //参数: [{ "organizeCodeList" : [ "3610",2752","2751"],"groupName" : "SAP2MES"}] + //参数: [{ "organizeCodeList" : [ "3610",2752","2751"],"groupName" : "WMS2SAP"}] + List wmsJobParamModelList = JsonUtilTool.toList(jobParam, WmsJobParamModel.class); + + if (!CollectionUtils.isEmpty(wmsJobParamModelList)) { + + //正常应该一个工厂一个分组JOB。所以一个【jobParam】就有一个解析对象。 + WmsJobParamModel wmsJobParamModel = wmsJobParamModelList.get(0); + + //解析job参数 + List organizeCodeList = wmsJobParamModel.getOrganizeCodeList(); + List destBeanNameList = wmsJobParamModel.getDestBeanNameList(); + List destBeanNameNotList = wmsJobParamModel.getDestBeanNameNotList(); + String groupName = wmsJobParamModel.getGroupName(); + + // 判断作业参数是否支持 + if (MesInterfaceEnumUtil.DIRECTION_TYPE.nameOf(groupName) == -1) { + throw new IllegalArgumentException("不支持的作业参数:" + groupName); + } + + LOGGER.info("SAP接口表数据同步作业按工厂划分开始 -----start"); + + // 开始同步数据 + // 一定要 new 对象,不然会有并发问题 + MesSAPDbAdapter mesSAPDbAdapter; + for (String organizeCode : organizeCodeList) { + + mesSAPDbAdapter = new MesSAPDbAdapter(); + // new 的对象需要手工注入 bean + if (SpringContextsUtil.getApplicationContext() != null) { + SpringContextsUtil.getApplicationContext(). + getAutowireCapableBeanFactory().autowireBean(mesSAPDbAdapter); + } + mesSAPDbAdapter.syncDataByOrganizeCode(groupName, organizeCode,destBeanNameList,destBeanNameNotList); + } + +// LOGGER.info("SAP接口表数据同步作业按工厂划分开始结束 ----- projectName:{}, port:{}", +// applicationProperties.getApplicationName(), applicationProperties.getServerPort()); + LOGGER.info("SAP接口表数据同步作业按工厂划分开始结束 ----- end"); + } + + } catch (Exception e) { + LOGGER.error("SAP接口表数据同步作业任务结束e:{}",e.toString()); + //sendErrorMessage(e.toString()); + } + } + +} diff --git a/modules/i3plus-ext-mes-apiservice/src/main/resources/application-cus-71.properties b/modules/i3plus-ext-mes-apiservice/src/main/resources/application-cus-71.properties index 73417e4..9c79730 100644 --- a/modules/i3plus-ext-mes-apiservice/src/main/resources/application-cus-71.properties +++ b/modules/i3plus-ext-mes-apiservice/src/main/resources/application-cus-71.properties @@ -16,7 +16,23 @@ impp.mes.datasource.password=estsh123 #\u6570\u636E\u6E90\u7684\u522B\u540D impp.yfas.datasource.alias=yfasDataSource impp.yfas.datasource.driver-class-name=com.mysql.jdbc.Driver -impp.yfas.datasource.jdbc-url=jdbc:mysql://10.195.88.71:3306/impp_i3_mes?autoReconnect=true&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true +impp.yfas.datasource.jdbc-url=jdbc:mysql://10.195.88.71:3306/impp_i3_wms_sap_if?autoReconnect=true&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true #impp.yfas.datasource.jdbc-url=jdbc:sqlserver://139.224.200.147:20037;DatabaseName=impp_i3_mes; impp.yfas.datasource.username=root impp.yfas.datasource.password=estsh123 + +#\u6570\u636E\u6E90\u7684\u522B\u540D +#impp.wms.datasource.isopen=true +#impp.wms.datasource.alias=wmsDataSource +#impp.wms.datasource.driver-class-name=com.mysql.jdbc.Driver +#impp.wms.datasource.jdbc-url=jdbc:mysql://dbmaster:3306/impp_i3_wms?autoReconnect=true&useSSL=false&characterEncoding=utf-8 +#impp.wms.datasource.username=root +#impp.wms.datasource.password=estsh123 +# +##SWEB?????????? +#impp.sweb.datasource.isopen=true +#impp.sweb.datasource.alias=swebDataSource +#impp.sweb.datasource.driver-class-name=com.mysql.jdbc.Driver +#impp.sweb.datasource.jdbc-url=jdbc:mysql://dbmaster:3306/impp_i3_wms?autoReconnect=true&useSSL=false&characterEncoding=utf-8 +#impp.sweb.datasource.username=root +#impp.sweb.datasource.password=estsh123 \ No newline at end of file diff --git a/modules/i3plus-ext-mes-apiservice/src/main/resources/application.properties b/modules/i3plus-ext-mes-apiservice/src/main/resources/application.properties index a3e79ba..d417395 100644 --- a/modules/i3plus-ext-mes-apiservice/src/main/resources/application.properties +++ b/modules/i3plus-ext-mes-apiservice/src/main/resources/application.properties @@ -1,5 +1,5 @@ #\u9879\u76EE\u540D\u79F0 -spring.application.name=mes-zxw +spring.application.name=mes #\u4F7F\u7528\u914D\u7F6E spring.profiles.active=71,cus-71 ######### \u81EA\u5B9A\u4E49\u53C2\u6570 ######### diff --git a/modules/i3plus-ext-mes-pojo/pom.xml b/modules/i3plus-ext-mes-pojo/pom.xml index 76022ef..4b29488 100644 --- a/modules/i3plus-ext-mes-pojo/pom.xml +++ b/modules/i3plus-ext-mes-pojo/pom.xml @@ -19,7 +19,10 @@ 1.0.0-yfai - + + org.quartz-scheduler + quartz + diff --git a/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/CronUtil.java b/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/CronUtil.java new file mode 100644 index 0000000..3c88e51 --- /dev/null +++ b/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/CronUtil.java @@ -0,0 +1,42 @@ +package cn.estsh.i3plus.ext.mes.pojo.util; + + +import org.quartz.TriggerUtils; +import org.quartz.impl.triggers.CronTriggerImpl; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +/** + * @Description :CronUtil + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +public class CronUtil { + + + + public static String getRecentTriggerTime(String cron) { + String newTime = ""; + try { + CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); + cronTriggerImpl.setCronExpression(cron); + //下次执行时间 增加一秒 + Date cronTime = new Date(); + cronTime.setTime(new Date().getTime()+1000); + cronTriggerImpl.setStartTime(cronTime); + // 这个是重点,一行代码搞定 + List dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null,5 ); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + newTime = dateFormat.format(dates.get(0)); + + } catch (ParseException e) { + e.printStackTrace(); + } + return newTime; + } + +} \ No newline at end of file diff --git a/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/OverwriteStringJoin.java b/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/OverwriteStringJoin.java new file mode 100644 index 0000000..83adc39 --- /dev/null +++ b/modules/i3plus-ext-mes-pojo/src/main/java/cn/estsh/i3plus/ext/mes/pojo/util/OverwriteStringJoin.java @@ -0,0 +1,35 @@ +package cn.estsh.i3plus.ext.mes.pojo.util; + + +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; +/** + * @Description :OverwriteStringJoin + * @Reference : + * @Author : gsz + * @CreateDate : 2024-05-10 16:08 + * @Modify: + **/ +public class OverwriteStringJoin { + + public static String join(CharSequence delimiter, CharSequence... elements) { + Objects.requireNonNull(delimiter); + Objects.requireNonNull(elements); + StringJoiner joiner = new StringJoiner(delimiter); + for (CharSequence cs: elements) { + joiner.add("'" + cs + "'"); + } + return joiner.toString(); + } + public static String join(CharSequence delimiter, List elements) { + Objects.requireNonNull(delimiter); + Objects.requireNonNull(elements); + StringJoiner joiner = new StringJoiner(delimiter); + for (String cs: elements) { + joiner.add("'" + cs + "'"); + } + return joiner.toString(); + } + +}