This commit is contained in:
Leo
2025-11-14 00:02:40 +08:00
parent b8981ffc98
commit 8a23c4d3f7
3 changed files with 216 additions and 2 deletions

View File

@@ -103,7 +103,7 @@ public class ErpProductController extends BaseController
}
/**
* 从闲鱼ERP拉取商品列表并保存
* 从闲鱼ERP拉取商品列表并保存(单页,保留用于兼容)
*/
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:pull')")
@Log(title = "拉取闲鱼商品", businessType = BusinessType.INSERT)
@@ -124,13 +124,31 @@ public class ErpProductController extends BaseController
if (productStatus != null) {
message += "(筛选条件:状态=" + statusText + "";
}
message += "。建议:1.尝试不选择状态拉取全部商品2.尝试其他状态(下架、已售等)";
message += "。建议:使用全量同步功能自动遍历所有页码";
return success(message);
}
} catch (Exception e) {
return error("拉取商品列表失败: " + e.getMessage());
}
}
/**
* 全量同步商品(自动遍历所有页码,同步更新和删除)
*/
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:pull')")
@Log(title = "全量同步闲鱼商品", businessType = BusinessType.UPDATE)
@PostMapping("/syncAll")
public AjaxResult syncAllProducts(
@RequestParam(required = false) String appid,
@RequestParam(required = false) Integer productStatus)
{
try {
IErpProductService.SyncResult result = erpProductService.syncAllProducts(appid, productStatus);
return success(result.getMessage());
} catch (Exception e) {
return error("全量同步失败: " + e.getMessage());
}
}
/**
* 获取状态文本(用于提示信息)

View File

@@ -69,5 +69,39 @@ public interface IErpProductService
* @return 拉取结果
*/
public int pullAndSaveProductList(String appid, Integer pageNo, Integer pageSize, Integer productStatus);
/**
* 全量同步商品列表(自动遍历所有页码,同步更新和删除)
*
* @param appid ERP应用ID
* @param productStatus 商品状态null表示全部状态
* @return 同步结果
*/
public SyncResult syncAllProducts(String appid, Integer productStatus);
/**
* 同步结果
*/
public static class SyncResult {
private int totalPulled; // 拉取总数
private int added; // 新增数量
private int updated; // 更新数量
private int deleted; // 删除数量
private int failed; // 失败数量
private String message; // 结果消息
public int getTotalPulled() { return totalPulled; }
public void setTotalPulled(int totalPulled) { this.totalPulled = totalPulled; }
public int getAdded() { return added; }
public void setAdded(int added) { this.added = added; }
public int getUpdated() { return updated; }
public void setUpdated(int updated) { this.updated = updated; }
public int getDeleted() { return deleted; }
public void setDeleted(int deleted) { this.deleted = deleted; }
public int getFailed() { return failed; }
public void setFailed(int failed) { this.failed = failed; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
}

View File

@@ -9,10 +9,14 @@ import com.ruoyi.jarvis.domain.ErpProduct;
import com.ruoyi.jarvis.service.IErpProductService;
import com.ruoyi.erp.request.ERPAccount;
import com.ruoyi.erp.request.ProductListQueryRequest;
import com.ruoyi.erp.request.ProductDeleteRequest;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;
/**
* 闲鱼商品Service业务层处理
@@ -313,6 +317,164 @@ public class ErpProductServiceImpl implements IErpProductService
}
}
/**
* 全量同步商品列表(自动遍历所有页码,同步更新和删除)
*/
@Override
public IErpProductService.SyncResult syncAllProducts(String appid, Integer productStatus) {
IErpProductService.SyncResult result = new IErpProductService.SyncResult();
Set<Long> remoteProductIds = new HashSet<>(); // 远程商品ID集合
int pageNo = 1;
int pageSize = 50; // 每页50条尽量少请求次数
int totalPulled = 0;
int added = 0;
int updated = 0;
try {
ERPAccount account = resolveAccount(appid);
// 第一步:遍历所有页码,拉取并保存所有商品
log.info("开始全量同步商品,账号:{}", appid);
while (true) {
ProductListQueryRequest request = new ProductListQueryRequest(account);
request.setPage(pageNo, pageSize);
if (productStatus != null) {
Integer apiStatus = convertProductStatus(productStatus);
if (apiStatus != null) {
request.setProductStatus(apiStatus);
}
}
String responseBody = request.getResponseBody();
JSONObject response = JSONObject.parseObject(responseBody);
if (response == null || response.getInteger("code") == null || response.getInteger("code") != 0) {
String errorMsg = response != null ? response.getString("msg") : "未知错误";
log.error("拉取商品列表失败(页码:{}: {}", pageNo, errorMsg);
result.setFailed(result.getFailed() + 1);
break;
}
JSONObject data = response.getJSONObject("data");
if (data == null) {
log.warn("拉取商品列表返回数据为空(页码:{}", pageNo);
break;
}
JSONArray productList = data.getJSONArray("list");
if (productList == null || productList.isEmpty()) {
log.info("第 {} 页数据为空,同步完成", pageNo);
break;
}
// 处理当前页商品
for (int i = 0; i < productList.size(); i++) {
JSONObject productJson = productList.getJSONObject(i);
ErpProduct erpProduct = parseProductJson(productJson, appid);
if (erpProduct != null && erpProduct.getProductId() != null) {
remoteProductIds.add(erpProduct.getProductId());
// 保存或更新商品
ErpProduct existing = erpProductMapper.selectErpProductByProductIdAndAppid(
erpProduct.getProductId(), erpProduct.getAppid());
if (existing != null) {
erpProduct.setId(existing.getId());
erpProductMapper.updateErpProduct(erpProduct);
updated++;
} else {
erpProductMapper.insertErpProduct(erpProduct);
added++;
}
totalPulled++;
}
}
log.info("已同步第 {} 页,共 {} 条商品", pageNo, productList.size());
// 判断是否还有下一页
if (productList.size() < pageSize) {
log.info("已拉取完所有页码,共 {} 页", pageNo);
break;
}
pageNo++;
}
result.setTotalPulled(totalPulled);
result.setAdded(added);
result.setUpdated(updated);
// 第二步:对比本地和远程,删除本地有但远程没有的商品
log.info("开始同步删除,远程商品数:{}", remoteProductIds.size());
// 查询本地该账号下的所有商品
ErpProduct queryParam = new ErpProduct();
queryParam.setAppid(appid);
List<ErpProduct> localProducts = erpProductMapper.selectErpProductList(queryParam);
// 找出需要删除的商品(本地有但远程没有的)
List<ErpProduct> toDelete = localProducts.stream()
.filter(p -> !remoteProductIds.contains(p.getProductId()))
.collect(Collectors.toList());
if (!toDelete.isEmpty()) {
log.info("发现 {} 个本地商品在远程已不存在,开始删除", toDelete.size());
for (ErpProduct product : toDelete) {
try {
// 先调用远程删除接口
ProductDeleteRequest deleteRequest = new ProductDeleteRequest(account);
deleteRequest.setProductId(product.getProductId());
String deleteResponse = deleteRequest.getResponseBody();
JSONObject deleteResult = JSONObject.parseObject(deleteResponse);
if (deleteResult != null && deleteResult.getInteger("code") != null &&
deleteResult.getInteger("code") == 0) {
// 远程删除成功,删除本地记录
erpProductMapper.deleteErpProductById(product.getId());
result.setDeleted(result.getDeleted() + 1);
log.debug("成功删除商品:{}", product.getProductId());
} else {
// 远程删除失败,记录日志但不删除本地(可能是远程已经删除了)
String errorMsg = deleteResult != null ? deleteResult.getString("msg") : "未知错误";
log.warn("远程删除商品失败(可能已不存在):{},错误:{}", product.getProductId(), errorMsg);
// 如果远程返回商品不存在的错误,也删除本地记录
if (errorMsg != null && (errorMsg.contains("不存在") || errorMsg.contains("not found"))) {
erpProductMapper.deleteErpProductById(product.getId());
result.setDeleted(result.getDeleted() + 1);
} else {
result.setFailed(result.getFailed() + 1);
}
}
} catch (Exception e) {
log.error("删除商品异常:{}", product.getProductId(), e);
result.setFailed(result.getFailed() + 1);
}
}
}
// 构建结果消息
StringBuilder msg = new StringBuilder();
msg.append(String.format("同步完成!拉取:%d个新增%d个更新%d个删除%d个",
totalPulled, added, updated, result.getDeleted()));
if (result.getFailed() > 0) {
msg.append(String.format(",失败:%d个", result.getFailed()));
}
result.setMessage(msg.toString());
log.info(result.getMessage());
return result;
} catch (Exception e) {
log.error("全量同步商品异常", e);
result.setMessage("同步失败: " + e.getMessage());
result.setFailed(result.getFailed() + 1);
throw new RuntimeException("全量同步商品失败: " + e.getMessage(), e);
}
}
/**
* 解析ERP账号
*/