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.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.production.dto.ProductMaterialDto;
import com.ruoyi.production.dto.ProductMaterialGroupDto;
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.pojo.ProductMaterialSku;
import com.ruoyi.production.service.ProductMaterialConfigService;
import com.ruoyi.production.service.ProductMaterialService;
import com.ruoyi.production.service.ProductMaterialSkuService;
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.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
*
* 产品物料信息接口实现类
*
*
* @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;
@Autowired
private ProductMaterialSkuService productMaterialSkuService;
/**
* 同步锁
*/
private final ReentrantLock syncLock = new ReentrantLock();
/**
* config缓存
*/
private final Map configCache = new HashMap<>();
@Override
@Transactional(rollbackFor = Exception.class)
public void loadProductMaterialData() {
syncProductMaterialData(1);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncProductMaterialJob() {
syncProductMaterialData(2);
}
/**
* 同步物料数据
*/
@Transactional(rollbackFor = Exception.class)
public void syncProductMaterialData(Integer syncType) {
if (!syncLock.tryLock()) {
log.warn("同步任务正在执行,本次请求跳过");
return;
}
try {
String accessToken = getAccessToken();
if (StringUtils.isEmpty(accessToken)) {
return;
}
LocalDateTime lastSyncTime = getLastSyncTime();
log.info("开始同步物料,本地最后时间 {}", lastSyncTime);
int page = 1;
int size = 50;
boolean hasMore = true;
int total = 0;
while (hasMore) {
JSONObject param = buildSearchParam(lastSyncTime, page, size);
String res = HttpUtils.sendPostJson(
aliDingConfig.getSearchFormDataUrl(),
param.toJSONString(),
StandardCharsets.UTF_8.name(),
null,
accessToken
);
JSONObject result = JSON.parseObject(res);
JSONArray dataArr = result.getJSONArray("data");
Integer totalCount = result.getInteger("totalCount");
if (CollectionUtils.isEmpty(dataArr)) {
break;
}
List list = parseProductMaterials(dataArr);
int affected = processSaveOrUpdate(list);
total += affected;
hasMore = page * size < totalCount;
page++;
log.info("同步进度 {} / {}", total, totalCount);
}
log.info("物料同步完成 共 {} 条", total);
} catch (Exception e) {
log.error("同步异常", e);
} finally {
syncLock.unlock();
}
}
private List parseProductMaterials(JSONArray arr) {
List list = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
for (int i = 0; i < arr.size(); i++) {
JSONObject item = arr.getJSONObject(i);
JSONObject formData = item.getJSONObject("formData");
String materialName = formData.getString("textField_l92f36f5");
ProductMaterial material = new ProductMaterial();
material.setMaterialName(materialName);
material.setBaseUnit(formData.getString("textField_la147lnw"));
String type = formData.getString("selectField_l92f36fb");
String inventory = formData.getString("selectField_la154noy");
material.setMaterialTypeId(getOrCreateConfigId(type, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
material.setInventoryCategoryId(getOrCreateConfigId(inventory, MaterialConfigTypeEnum.INVENTORY_CAT.name()));
Long materialId = getOrCreateMaterial(material);
ProductMaterialSku sku = new ProductMaterialSku();
sku.setMaterialId(materialId);
sku.setMaterialCode(formData.getString("textField_l92f36f2"));
sku.setSpecification(formData.getString("textField_l92f36f6"));
sku.setIdentifierCode(formData.getString("textField_l92h77ju"));
sku.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
sku.setCreateTime(now);
sku.setUpdateTime(now);
list.add(sku);
}
return list;
}
private Long getOrCreateMaterial(ProductMaterial material) {
ProductMaterial exist = this.getOne(
new LambdaQueryWrapper()
.eq(ProductMaterial::getMaterialName, material.getMaterialName())
);
if (exist == null) {
material.setCreateTime(LocalDateTime.now());
material.setUpdateTime(LocalDateTime.now());
this.save(material);
return material.getId();
}
return exist.getId();
}
private Integer getOrCreateConfigId(String name, String type) {
if (StringUtils.isEmpty(name)) {
return null;
}
String key = type + "_" + name;
if (configCache.containsKey(key)) {
return configCache.get(key);
}
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);
}
configCache.put(key, config.getId());
return config.getId();
}
private int processSaveOrUpdate(List list) {
if (CollectionUtils.isEmpty(list)) {
return 0;
}
int affected = 0;
for (ProductMaterialSku sku : list) {
ProductMaterialSku exist = productMaterialSkuService.getOne(new LambdaQueryWrapper()
.eq(ProductMaterialSku::getMaterialId, sku.getMaterialId())
.eq(ProductMaterialSku::getSpecification, sku.getSpecification()));
if (exist == null) {
productMaterialSkuService.save(sku);
affected++;
} else if (!Objects.equals(exist.getFormModifiedTime(), sku.getFormModifiedTime())) {
sku.setId(exist.getId());
sku.setCreateTime(exist.getCreateTime());
productMaterialSkuService.updateById(sku);
affected++;
}
}
return affected;
}
@Override
public List ProductMaterialList(Integer type) {
List configList = productMaterialConfigService.list(new LambdaQueryWrapper()
.eq(ProductMaterialConfig::getConfigType, MaterialConfigTypeEnum.MATERIAL_TYPE.name())
);
if (CollectionUtils.isEmpty(configList)) {
return new ArrayList<>();
}
List result = new ArrayList<>();
Map> materialMap = new HashMap<>();
if (type != null && type == 2) {
List materialList = this.list(new LambdaQueryWrapper()
.select(
ProductMaterial::getId,
ProductMaterial::getMaterialTypeId,
ProductMaterial::getInventoryCategoryId,
ProductMaterial::getMaterialName
)
);
materialMap = materialList.stream()
.map(this::convert)
.collect(Collectors.groupingBy(ProductMaterialDto::getMaterialTypeId));
}
for (ProductMaterialConfig config : configList) {
ProductMaterialGroupDto dto = new ProductMaterialGroupDto();
dto.setConfigId(config.getId());
dto.setConfigName(config.getConfigName());
if (type != null && type == 2) {
dto.setMaterialList(materialMap.getOrDefault(config.getId(), new ArrayList<>()));
}
result.add(dto);
}
return result;
}
@Override
public List productMaterialListByQuery(String materialName, Integer materialTypeId) {
if (StringUtils.isEmpty(materialName) && materialTypeId == null) {
return new ArrayList<>();
}
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
// 只查询需要的字段数据
wrapper.select(
ProductMaterial::getId,
ProductMaterial::getMaterialTypeId,
ProductMaterial::getInventoryCategoryId,
ProductMaterial::getMaterialName
);
if (StringUtils.isNotEmpty(materialName)) {
wrapper.like(ProductMaterial::getMaterialName, materialName);
}
if (materialTypeId != null) {
wrapper.eq(ProductMaterial::getMaterialTypeId, materialTypeId);
}
List materials = this.list(wrapper);
if (CollectionUtils.isEmpty(materials)) {
return new ArrayList<>();
}
Map> map = materials.stream()
.map(this::convert)
.collect(Collectors.groupingBy(ProductMaterialDto::getMaterialTypeId));
List configList = productMaterialConfigService.list(new LambdaQueryWrapper()
.eq(ProductMaterialConfig::getConfigType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
List result = new ArrayList<>();
for (ProductMaterialConfig config : configList) {
List dtoList = map.get(config.getId());
if (CollectionUtils.isEmpty(dtoList)) {
continue;
}
ProductMaterialGroupDto dto = new ProductMaterialGroupDto();
dto.setConfigId(config.getId());
dto.setConfigName(config.getConfigName());
dto.setMaterialList(dtoList);
result.add(dto);
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addProductMaterial(ProductMaterial productMaterial) {
validateProductMaterial(productMaterial, false);
if (existsMaterialName(productMaterial.getMaterialName(), null)) {
throw new ServiceException("物料名称已存在");
}
LocalDateTime now = LocalDateTime.now();
if (productMaterial.getCreateTime() == null) {
productMaterial.setCreateTime(now);
}
productMaterial.setUpdateTime(now);
if (!this.save(productMaterial)) {
throw new ServiceException("新增物料失败");
}
log.info("新增物料成功 materialName={}", productMaterial.getMaterialName());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProductMaterial(ProductMaterial productMaterial) {
validateProductMaterial(productMaterial, true);
ProductMaterial exist = this.getById(productMaterial.getId());
if (exist == null) {
throw new ServiceException("物料不存在");
}
if (existsMaterialName(productMaterial.getMaterialName(), productMaterial.getId())) {
throw new ServiceException("物料名称已存在");
}
productMaterial.setUpdateTime(LocalDateTime.now());
if (!this.updateById(productMaterial)) {
throw new ServiceException("修改物料失败");
}
log.info("修改物料成功 id={}", productMaterial.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteProductMaterial(List ids) {
if (ids == null || ids.isEmpty()) {
throw new ServiceException("请选择至少一条数据");
}
if (!this.removeByIds(ids)) {
throw new ServiceException("删除物料失败");
}
log.info("删除物料成功 ids={}", ids);
}
private void validateProductMaterial(ProductMaterial productMaterial, boolean requireId) {
if (productMaterial == null) {
throw new ServiceException("参数不能为空");
}
if (requireId && productMaterial.getId() == null) {
throw new ServiceException("主键ID不能为空");
}
if (StringUtils.isEmpty(productMaterial.getMaterialName())) {
throw new ServiceException("物料名称不能为空");
}
}
private boolean existsMaterialName(String materialName, Long excludeId) {
if (StringUtils.isEmpty(materialName)) {
return false;
}
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ProductMaterial::getMaterialName, materialName);
if (excludeId != null) {
queryWrapper.ne(ProductMaterial::getId, excludeId);
}
return this.count(queryWrapper) > 0;
}
private ProductMaterialDto convert(ProductMaterial m) {
ProductMaterialDto dto = new ProductMaterialDto();
dto.setId(m.getId());
dto.setMaterialName(m.getMaterialName());
dto.setMaterialTypeId(m.getMaterialTypeId());
dto.setInventoryCategoryId(m.getInventoryCategoryId());
return dto;
}
private LocalDateTime parseUtcTime(String utc) {
if (StringUtils.isEmpty(utc)) {
return null;
}
try {
return OffsetDateTime.parse(utc).toLocalDateTime();
} catch (DateTimeParseException e) {
log.warn("时间解析失败 {}", utc);
return null;
}
}
private String getAccessToken() {
String params = "appkey=" + aliDingConfig.getAppKey() + "&appsecret=" + aliDingConfig.getAppSecret();
String res = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params);
JSONObject obj = JSON.parseObject(res);
return obj.getString("access_token");
}
private LocalDateTime getLastSyncTime() {
ProductMaterialSku last = productMaterialSkuService.getOne(new LambdaQueryWrapper()
.orderByDesc(ProductMaterialSku::getFormModifiedTime)
.last("limit 1"));
return last == null ? null : last.getFormModifiedTime();
}
private JSONObject buildSearchParam(LocalDateTime lastTime, int page, int size) {
JSONObject obj = new JSONObject();
obj.put("appType", aliDingConfig.getAppType());
obj.put("systemToken", aliDingConfig.getSystemToken());
obj.put("formUuid", aliDingConfig.getMaterialCodeFormUuid());
obj.put("currentPage", page);
obj.put("pageSize", size);
return obj;
}
}