package com.ruoyi.production.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.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.production.enums.MaterialConfigTypeEnum; import com.ruoyi.production.mapper.ProductMaterialMapper; import com.ruoyi.production.pojo.ProductMaterial; import com.ruoyi.production.pojo.ProductMaterialConfig; import com.ruoyi.production.service.ProductMaterialConfigService; import com.ruoyi.production.service.ProductMaterialService; 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.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/11 16:36 */ @Slf4j @Service public class ProductMaterialServiceImpl extends ServiceImpl implements ProductMaterialService { @Autowired private AliDingConfig aliDingConfig; @Autowired private ProductMaterialConfigService productMaterialConfigService; /** * 同步锁,防止手动和定时任务同时执行 */ private final ReentrantLock syncLock = new ReentrantLock(); @Override public void loadProductMaterialData() { syncProductMaterialData(1); } @Override public void syncProductMaterialJob() { syncProductMaterialData(2); } /** * 同步数据 */ @Transactional(rollbackFor = Exception.class) public void syncProductMaterialData(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 = parseProductMaterials(dataArr, totalCount); if (!list.isEmpty()) { // 处理更新或新增 int affected = processSaveOrUpdate(list); totalSynced += affected; } // 判断是否还有下一页 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() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(ProductMaterial::getFormModifiedTime).last("LIMIT 1"); ProductMaterial 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.getMaterialCodeFormUuid()); searchParam.put("currentPage", pageNumber); searchParam.put("pageSize", pageSize); JSONArray searchConditions = new JSONArray(); JSONObject statusCondition = new JSONObject(); statusCondition.put("key", "processInstanceStatus"); JSONArray statusValueArray = new JSONArray(); statusValueArray.add("COMPLETED"); statusCondition.put("value", statusValueArray); statusCondition.put("type", "ARRAY"); statusCondition.put("operator", "in"); statusCondition.put("componentName", "SelectField"); searchConditions.add(statusCondition); JSONObject resultCondition = new JSONObject(); resultCondition.put("key", "processApprovedResult"); JSONArray resultValueArray = new JSONArray(); resultValueArray.add("agree"); resultCondition.put("value", resultValueArray); resultCondition.put("type", "ARRAY"); resultCondition.put("operator", "in"); resultCondition.put("componentName", "SelectField"); searchConditions.add(resultCondition); searchParam.put("searchFieldJson", searchConditions.toJSONString()); searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}"); 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 parseProductMaterials(JSONArray dataArr, 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"); JSONObject originator = item.getJSONObject("originator"); String originatorName = originator != null && originator.containsKey("userName") ? originator.getJSONObject("userName").getString("nameInChinese") : "未知"; JSONObject formData = item.getJSONObject("formData"); ProductMaterial material = new ProductMaterial(); material.setFormInstanceId(formInstanceId); material.setIdentifierCode(formData.getString("textField_l92h77ju")); material.setMaterialCode(formData.getString("textField_l92f36f2")); material.setMaterialName(formData.getString("textField_l92f36f5")); material.setSpecification(formData.getString("textField_l92f36f6")); material.setBaseUnit(formData.getString("textField_la147lnw")); material.setMaterialAttribute(formData.getString("selectField_la14k51j")); material.setFinishedProductName(formData.getString("radioField_lbkk2nn2")); material.setRemark(formData.getString("textareaField_l92f36f9")); // 处理物料类型和存货类别 String materialType = formData.getString("selectField_l92f36fb"); String inventoryCat = formData.getString("selectField_la154noy"); material.setMaterialTypeId(getOrCreateConfigId(materialType, MaterialConfigTypeEnum.MATERIAL_TYPE.name())); material.setInventoryCategoryId(getOrCreateConfigId(inventoryCat, MaterialConfigTypeEnum.INVENTORY_CAT.name())); material.setOriginatorName(originatorName); material.setOriginatorOrg("宁夏中创绿能实业集团有限公司"); material.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT"))); material.setCreateTime(now); material.setUpdateTime(now); list.add(material); } return list; } private Integer getOrCreateConfigId(String name, String type) { if (StringUtils.isEmpty(name)) { return null; } ProductMaterialConfig config = productMaterialConfigService.getOne(new LambdaQueryWrapper() .eq(ProductMaterialConfig::getConfigName, name) .eq(ProductMaterialConfig::getConfigType, type)); if (config == null) { config = new ProductMaterialConfig(); config.setConfigName(name); config.setConfigType(type); productMaterialConfigService.save(config); } return config.getId(); } private int processSaveOrUpdate(List list) { if (list == null || list.isEmpty()) { return 0; } int affected = 0; for (ProductMaterial material : list) { ProductMaterial exist = this.getOne(new LambdaQueryWrapper() .eq(ProductMaterial::getFormInstanceId, material.getFormInstanceId())); if (exist == null) { this.save(material); affected++; log.info("新增物料数据 formInstanceId={}", material.getFormInstanceId()); } else { if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(material.getFormModifiedTime())) { material.setId(exist.getId()); material.setCreateTime(exist.getCreateTime()); this.updateById(material); affected++; log.info("更新物料数据 formInstanceId={}", material.getFormInstanceId()); } } } return affected; } 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; } } }