package com.ruoyi.productionPlan.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.framework.config.AliDingConfig; import com.ruoyi.productionPlan.dto.ProductionPlanDto; import com.ruoyi.productionPlan.mapper.ProductionPlanMapper; import com.ruoyi.productionPlan.pojo.ProductionPlan; import com.ruoyi.productionPlan.service.ProductionPlanService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; /** *
* 销售生产需求接口实现类 *
* * @author deslrey * @version 1.0 * @since 2026/03/10 10:00 */ @Slf4j @Service public class ProductionPlanServiceImpl extends ServiceImpl implements ProductionPlanService { @Autowired private AliDingConfig aliDingConfig; @Autowired private ProductionPlanMapper productionPlanMapper; /** * 同步锁,确保手动和定时任务不同时执行 */ private final ReentrantLock syncLock = new ReentrantLock(); @Override public IPage listPage(Page page, ProductionPlanDto productionPlanDto) { return productionPlanMapper.listPage(page, productionPlanDto); } /** * 页面手动同步 */ @Override public void loadProdData() { syncProdData(1); } /** * 定时任务同步 */ @Override public void syncProdDataJob() { syncProdData(2); } /** * 同步数据 */ @Transactional(rollbackFor = Exception.class) public void syncProdData(Integer dataSyncType) { if (!syncLock.tryLock()) { log.warn("同步正在进行中,本次 {} 同步请求被跳过", dataSyncType == 1 ? "手动" : "定时任务"); return; } try { // 获取 AccessToken String accessToken = getAccessToken(); if (StringUtils.isEmpty(accessToken)) { return; } // 获取本地最后同步时间 LocalDateTime lastSyncTime = getLastSyncTime(); log.info("开始增量同步,本地最后修改时间: {}", lastSyncTime); int pageNumber = 1; int pageSize = 50; boolean hasMore = true; int totalSynced = 0; while (hasMore) { // 查询参数 JSONObject searchParam = buildSearchParam(lastSyncTime, pageNumber, pageSize); // 调用宜搭接口拉取数据 String dataRes = HttpUtils.sendPostJson( aliDingConfig.getSearchFormDataUrl(), searchParam.toJSONString(), StandardCharsets.UTF_8.name(), null, accessToken ); if (StringUtils.isEmpty(dataRes)) { log.warn("第 {} 页拉取数据为空", pageNumber); break; } JSONObject resultObj = JSON.parseObject(dataRes); JSONArray dataArr = resultObj.getJSONArray("data"); Integer totalCount = resultObj.getInteger("totalCount"); if (dataArr == null || dataArr.isEmpty()) { log.info("没有更多新数据需要同步"); break; } // 解析并保存数据 List list = parseProductionPlans(dataArr, dataSyncType, totalCount); if (!list.isEmpty()) { // 处理更新或新增 processSaveOrUpdate(list); totalSynced += list.size(); } // 判断是否还有下一页 hasMore = (pageNumber * pageSize) < totalCount; pageNumber++; log.info("正在同步第 {} 页,当前已同步 {}/{}", pageNumber - 1, totalSynced, totalCount); } log.info("数据同步完成,共同步 {} 条数据", totalSynced); } catch (Exception e) { log.error("同步生产计划异常", e); } finally { // 释放锁 syncLock.unlock(); } } private String getAccessToken() { String params = "appkey=" + aliDingConfig.getAppKey() + "&appsecret=" + aliDingConfig.getAppSecret(); String tokenRes = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params); JSONObject tokenObj = JSON.parseObject(tokenRes); String accessToken = tokenObj.getString("access_token"); if (StringUtils.isEmpty(accessToken)) { log.error("获取钉钉AccessToken失败: {}", tokenRes); } return accessToken; } private LocalDateTime getLastSyncTime() { // 查询本地数据库中 formModifiedTime 最大的记录 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(ProductionPlan::getFormModifiedTime).last("LIMIT 1"); ProductionPlan lastRecord = this.getOne(queryWrapper); return lastRecord != null ? lastRecord.getFormModifiedTime() : null; } private JSONObject buildSearchParam(LocalDateTime lastSyncTime, int pageNumber, int pageSize) { JSONObject searchParam = new JSONObject(); searchParam.put("appType", aliDingConfig.getAppType()); searchParam.put("systemToken", aliDingConfig.getSystemToken()); searchParam.put("userId", aliDingConfig.getUserId()); searchParam.put("formUuid", aliDingConfig.getFormUuid()); searchParam.put("pageSize", pageSize); searchParam.put("pageNumber", pageNumber); // 默认按修改时间升序排序,确保分页拉取数据的连续性 // "+" 表示升序,"gmt_modified" 是官方内置字段 searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}"); // 设置修改时间筛选区间 (格式必须为yyyy-MM-dd HH:mm:ss) if (lastSyncTime != null) { // 起始时间:上次同步到的最后一条数据的修改时间 String startTime = lastSyncTime.plusSeconds(1).atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); searchParam.put("modifiedFromTimeGMT", startTime); } // 截止时间:当前时间,确保获取最新的已修改/已新增数据 String endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); searchParam.put("modifiedToTimeGMT", endTime); return searchParam; } private List parseProductionPlans(JSONArray dataArr, Integer dataSyncType, Integer totalCount) { List list = new ArrayList<>(); LocalDateTime now = LocalDateTime.now(); for (int i = 0; i < dataArr.size(); i++) { JSONObject item = dataArr.getJSONObject(i); String formInstanceId = item.getString("formInstanceId"); String serialNo = item.getString("serialNo"); JSONObject originator = item.getJSONObject("originator"); String originatorName = originator != null && originator.containsKey("userName") ? originator.getJSONObject("userName").getString("nameInChinese") : "未知"; JSONObject formData = item.getJSONObject("formData"); JSONArray tableArr = formData.getJSONArray("tableField_l7fytfcn"); if (tableArr == null || tableArr.isEmpty()) { continue; } for (int j = 0; j < tableArr.size(); j++) { JSONObject row = tableArr.getJSONObject(j); ProductionPlan plan = new ProductionPlan(); plan.setFormInstanceId(formInstanceId); plan.setSerialNo(serialNo); plan.setApplyNo(formData.getString("textField_l7fytfco")); plan.setCustomerName(formData.getString("textField_lbkozohg")); plan.setMaterialCode(row.getString("textField_l9xo62q5")); plan.setProductName(row.getString("textField_l9xo62q7")); plan.setProductSpec(row.getString("textField_l9xo62q8")); plan.setLength(row.getInteger("numberField_lb7lgatg_value")); plan.setWidth(row.getInteger("numberField_lb7lgath_value")); plan.setHeight(row.getInteger("numberField_lb7lgati_value")); plan.setQuantity(row.getInteger("numberField_lb7lgatj_value")); plan.setVolume(row.getBigDecimal("numberField_l7fytfd3_value")); plan.setStrength(row.getString("radioField_m9urarr2_id")); JSONArray dateArr = row.getJSONArray("cascadeDateField_lfxqqluw"); if (dateArr != null && dateArr.size() == 2) { try { long start = Long.parseLong(dateArr.getString(0)); long end = Long.parseLong(dateArr.getString(1)); plan.setStartDate(Instant.ofEpochMilli(start).atZone(ZoneId.systemDefault()).toLocalDateTime()); plan.setEndDate(Instant.ofEpochMilli(end).atZone(ZoneId.systemDefault()).toLocalDateTime()); } catch (Exception e) { log.warn("解析日期失败: {}", dateArr); } } plan.setSubmitter(originatorName); plan.setSubmitOrg("宁夏中创绿能实业集团有限公司"); plan.setRemarkOne(formData.getString("textareaField_l7fytfcy")); plan.setRemarkTwo(formData.getString("textField_l7fytfcx")); plan.setCreatorName(originatorName); JSONObject modifyUser = item.getJSONObject("modifyUser"); if (modifyUser != null && modifyUser.containsKey("userName")) { plan.setModifierName(modifyUser.getJSONObject("userName").getString("nameInChinese")); } plan.setFormCreatedTime(parseUtcTime(item.getString("createdTimeGMT"))); plan.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT"))); plan.setDataSyncType(dataSyncType); plan.setDataSourceType(1); plan.setCreateTime(now); plan.setUpdateTime(now); plan.setTotalCount(totalCount); list.add(plan); } } return list; } private void processSaveOrUpdate(List list) { for (ProductionPlan plan : list) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ProductionPlan::getFormInstanceId, plan.getFormInstanceId()) .eq(ProductionPlan::getMaterialCode, plan.getMaterialCode()); ProductionPlan existing = this.getOne(queryWrapper); if (existing != null) { plan.setId(existing.getId()); this.updateById(plan); } else { this.save(plan); } } } private LocalDateTime parseUtcTime(String utcString) { if (StringUtils.isEmpty(utcString)) { return null; } try { OffsetDateTime odt = OffsetDateTime.parse(utcString); return odt.toLocalDateTime(); } catch (DateTimeParseException ex) { log.warn("解析时间 {} 失败: {}", utcString, ex.getMessage()); return null; } } }