diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpProductController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpProductController.java index fc836f3..7a0617e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpProductController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpProductController.java @@ -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()); + } + } /** * 获取状态文本(用于提示信息) diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpProductService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpProductService.java index 383e645..8fc5cee 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpProductService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpProductService.java @@ -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; } + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpProductServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpProductServiceImpl.java index c153d9e..a3b241a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpProductServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpProductServiceImpl.java @@ -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 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 localProducts = erpProductMapper.selectErpProductList(queryParam); + + // 找出需要删除的商品(本地有但远程没有的) + List 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账号 */