From 2a93522bcfbd20d7ead25576359d72aa8d523e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92?= Date: Thu, 9 Oct 2025 19:45:14 +0800 Subject: [PATCH] 1 --- doc/批量发品功能说明.md | 315 +++++++++++++ .../jarvis/BatchPublishController.java | 95 ++++ .../com/ruoyi/erp/request/ERPAccount.java | 2 +- .../ruoyi/jarvis/domain/BatchPublishItem.java | 206 +++++++++ .../ruoyi/jarvis/domain/BatchPublishTask.java | 193 ++++++++ .../domain/request/BatchPublishRequest.java | 319 +++++++++++++ .../request/ParseLineReportRequest.java | 25 + .../jarvis/mapper/BatchPublishItemMapper.java | 70 +++ .../jarvis/mapper/BatchPublishTaskMapper.java | 54 +++ .../jarvis/service/IBatchPublishService.java | 67 +++ .../service/impl/BatchPublishServiceImpl.java | 426 ++++++++++++++++++ .../ruoyi/jarvis/util/LineReportParser.java | 128 ++++++ .../mapper/jarvis/BatchPublishItemMapper.xml | 118 +++++ .../mapper/jarvis/BatchPublishTaskMapper.xml | 95 ++++ sql/batch_publish.sql | 46 ++ 15 files changed, 2158 insertions(+), 1 deletion(-) create mode 100644 doc/批量发品功能说明.md create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/BatchPublishController.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishItem.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishTask.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/BatchPublishRequest.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/ParseLineReportRequest.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishItemMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishTaskMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IBatchPublishService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/util/LineReportParser.java create mode 100644 ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishItemMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishTaskMapper.xml create mode 100644 sql/batch_publish.sql diff --git a/doc/批量发品功能说明.md b/doc/批量发品功能说明.md new file mode 100644 index 0000000..2b0243f --- /dev/null +++ b/doc/批量发品功能说明.md @@ -0,0 +1,315 @@ +# 线报批量发品功能说明 + +## 功能概述 + +线报批量发品功能允许用户通过输入框输入线报消息,自动解析出商品信息,然后批量发品到多个ERP账号,并支持延迟自动上架。 + +## 主要特性 + +### 1. 智能解析 +- 支持从线报消息中自动识别京东商品链接 +- 支持多种链接格式(item.jd.com、u.jd.com短链接等) +- 自动提取SKUID并查询商品详情 +- 获取商品名称、价格、图片、店铺等信息 + +### 2. 批量选择 +- 可视化商品列表,支持全选/反选 +- 展示商品图片、名称、价格、佣金等信息 +- 灵活选择需要发品的商品 + +### 3. 多账号发品 +- 支持同时选择多个ERP账号(胡歌、刘强东等) +- 每个商品会发送到所有选中的账号 +- 自动生成商家编码,避免重复 + +### 4. 通用参数设置 +- 支持统一设置:会员名、省市区、商品类型、行业类型、类目等 +- 支持设置邮费、库存、成色、服务支持等 +- 一次设置,应用到所有商品 + +### 5. 延迟队列上架 +- 发品成功后自动加入延迟队列 +- 可自定义延迟时间(1-60秒) +- 到时后自动调用上架接口 + +### 6. 进度跟踪 +- 实时显示发品进度 +- 展示每个商品在每个账号的发品状态 +- 记录成功数、失败数、错误信息 + +### 7. 历史记录 +- 保存所有批量发品任务记录 +- 支持查看任务详情和发品明细 +- 可追溯每个商品的发品结果 + +## 使用流程 + +### 第一步:输入线报消息 + +在输入框中粘贴线报消息,支持以下格式: + +``` +【京东】某某商品 +https://item.jd.com/100012345678.html +原价:999元 +... + +【京东】另一个商品 +https://u.jd.com/xxxxx +到手价:199元 +... +``` + +点击"解析商品"按钮,系统会自动提取商品链接并查询详情。 + +### 第二步:选择商品 + +- 系统展示解析出的商品列表 +- 勾选需要发品的商品(支持全选) +- 查看商品信息确认无误 +- 点击"下一步" + +### 第三步:设置参数 + +#### 3.1 基本设置 +- **任务名称**(选填):为本次批量发品任务命名 +- **延迟上架**:设置发品成功后延迟多少秒自动上架(默认3秒) + +#### 3.2 目标账号 +- 选择一个或多个ERP账号(支持多选) +- 每个商品将发送到所有选中的账号 + +#### 3.3 通用参数 +- **会员名**:闲鱼会员名(必填) +- **省市区**:发货地址代码(必填) +- **商品类型**:普通商品/已验货/验货宝等(必填) +- **行业类型**:手机/家电/数码等(必填) +- **类目ID**:商品类目ID(必填) +- **邮费**:邮费金额(元,必填) +- **库存**:库存数量(必填) +- **成色**:全新/99新等(选填) +- **服务支持**:七天无理由退货等(选填) + +点击"开始批量发品"提交任务。 + +### 第四步:查看进度 + +- 系统创建批量发品任务 +- 实时展示发品进度条 +- 显示每个商品在每个账号的发品状态 +- 发品成功的商品会自动加入延迟队列等待上架 + +## 数据库表结构 + +### batch_publish_task(批量发品任务表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 任务ID | +| task_name | varchar(200) | 任务名称 | +| original_message | text | 原始线报消息 | +| total_products | int | 解析出的商品数量 | +| selected_products | int | 选中的商品数量 | +| target_accounts | varchar(500) | 目标ERP账号(JSON) | +| status | int | 任务状态:0待处理 1处理中 2已完成 3失败 | +| success_count | int | 成功发品数量 | +| fail_count | int | 失败发品数量 | +| common_params | text | 通用参数(JSON) | +| create_user_id | bigint | 创建人ID | +| create_user_name | varchar(100) | 创建人姓名 | +| create_time | datetime | 创建时间 | +| complete_time | datetime | 完成时间 | + +### batch_publish_item(批量发品明细表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 明细ID | +| task_id | bigint | 任务ID | +| skuid | varchar(100) | SKUID | +| product_name | varchar(500) | 商品名称 | +| target_account | varchar(100) | 目标ERP账号 | +| account_remark | varchar(100) | 账号备注名 | +| status | int | 发品状态:0待发布 1发布中 2发布成功 3发布失败 4上架中 5已上架 6上架失败 | +| product_id | bigint | ERP商品ID | +| product_status | int | 商品状态 | +| outer_id | varchar(100) | 商家编码 | +| publish_price | bigint | 发品价格(分) | +| error_message | varchar(1000) | 失败原因 | +| publish_time | datetime | 上架时间 | +| delay_seconds | int | 延迟上架时间(秒) | +| create_time | datetime | 创建时间 | + +## API接口 + +### 1. 解析线报消息 +``` +POST /jarvis/batchPublish/parse +Content-Type: application/json + +{ + "message": "线报消息内容" +} + +返回: +{ + "code": 200, + "msg": "操作成功", + "data": [ + { + "skuid": "100012345678", + "productName": "商品名称", + "price": 199.0, + "productImage": "http://...", + "shopName": "店铺名称", + "shopId": "12345", + "commissionInfo": "10%" + } + ] +} +``` + +### 2. 批量发品 +``` +POST /jarvis/batchPublish/publish +Content-Type: application/json + +{ + "taskName": "任务名称", + "originalMessage": "原始消息", + "products": [ + { + "skuid": "100012345678", + "productName": "商品名称", + "price": 199.0 + } + ], + "targetAccounts": ["1016208368633221", "1206879680251333"], + "delaySeconds": 3, + "commonParams": { + "userName": "会员名", + "province": 110000, + "city": 110100, + "district": 110101, + "itemBizType": 2, + "spBizType": 3, + "channelCatId": "12345", + "expressFee": 0.0, + "stock": 1, + "stuffStatus": 100 + } +} + +返回: +{ + "code": 200, + "msg": "任务已创建", + "data": 123 // 任务ID +} +``` + +### 3. 查询任务列表 +``` +GET /jarvis/batchPublish/task/list?pageNum=1&pageSize=10 + +返回: +{ + "code": 200, + "msg": "查询成功", + "rows": [...], + "total": 10 +} +``` + +### 4. 查询任务详情 +``` +GET /jarvis/batchPublish/task/{taskId} + +返回任务详细信息 +``` + +### 5. 查询任务明细 +``` +GET /jarvis/batchPublish/item/list/{taskId} + +返回任务所有发品明细 +``` + +## 状态说明 + +### 任务状态 +- 0:待处理 +- 1:处理中 +- 2:已完成 +- 3:失败 + +### 发品状态 +- 0:待发布 +- 1:发布中 +- 2:发布成功 +- 3:发布失败 +- 4:上架中 +- 5:已上架 +- 6:上架失败 + +## 技术实现 + +### 后端 +- **解析工具**:LineReportParser - 正则表达式提取链接和SKUID +- **Service**:BatchPublishServiceImpl - 核心业务逻辑 +- **异步任务**:@Async注解 + CompletableFuture实现延迟队列 +- **数据库**:MyBatis + MySQL存储任务和明细 + +### 前端 +- **框架**:Vue 2 + Element UI +- **步骤条**:el-steps实现4步向导 +- **实时刷新**:定时器轮询任务状态 +- **组件**:表格、表单、对话框等 + +## 注意事项 + +1. **线报消息格式**:尽量包含完整的京东商品链接,便于准确识别 +2. **价格获取**:价格从京东API实时查询,可能与线报价格有差异 +3. **账号限制**:请确保ERP账号有足够的发品额度 +4. **延迟上架**:建议设置3-5秒延迟,避免频繁操作 +5. **参数设置**:通用参数会应用到所有商品,请仔细核对 +6. **批量操作**:大批量发品时请分批进行,避免超时 + +## 常见问题 + +### Q1: 解析不到商品怎么办? +A: 确保线报消息中包含完整的京东商品链接,如:https://item.jd.com/xxxxx.html + +### Q2: 发品失败是什么原因? +A: 可能原因:账号额度不足、商品信息不完整、网络异常等,查看错误信息了解详情 + +### Q3: 可以同时发多少个商品? +A: 理论上无限制,但建议每次不超过50个商品,避免超时 + +### Q4: 延迟上架的作用是什么? +A: 避免频繁操作触发平台限制,给系统缓冲时间 + +### Q5: 如何查看历史记录? +A: 点击页面右上角的"历史记录"按钮,可以查看所有批量发品任务 + +## 未来优化方向 + +1. [ ] 集成实际的发品接口(目前为模拟) +2. [ ] 支持商品价格批量调整 +3. [ ] 支持文案自动生成 +4. [ ] 支持图片批量处理 +5. [ ] 支持发品模板保存 +6. [ ] 支持定时发品 +7. [ ] 支持发品失败自动重试 +8. [ ] 支持发品结果通知(钉钉/企微) + +## 更新日志 + +### v1.0.0 (2025-01-10) +- ✅ 初始版本 +- ✅ 实现线报消息解析 +- ✅ 实现批量发品 +- ✅ 实现延迟队列上架 +- ✅ 实现多账号支持 +- ✅ 实现历史记录查询 + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/BatchPublishController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/BatchPublishController.java new file mode 100644 index 0000000..9ea4a13 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/BatchPublishController.java @@ -0,0 +1,95 @@ +package com.ruoyi.web.controller.jarvis; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.jarvis.domain.BatchPublishItem; +import com.ruoyi.jarvis.domain.BatchPublishTask; +import com.ruoyi.jarvis.domain.request.BatchPublishRequest; +import com.ruoyi.jarvis.domain.request.ParseLineReportRequest; +import com.ruoyi.jarvis.service.IBatchPublishService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 批量发品Controller + * + * @author ruoyi + * @date 2025-01-10 + */ +@RestController +@RequestMapping("/jarvis/batchPublish") +public class BatchPublishController extends BaseController +{ + @Autowired + private IBatchPublishService batchPublishService; + + /** + * 解析线报消息 + */ + @PostMapping("/parse") + public AjaxResult parseLineReport(@RequestBody @Validated ParseLineReportRequest request) + { + try { + List> products = batchPublishService.parseLineReport(request); + return AjaxResult.success(products); + } catch (Exception e) { + logger.error("解析线报消息失败", e); + return AjaxResult.error("解析失败: " + e.getMessage()); + } + } + + /** + * 批量发品 + */ + @Log(title = "批量发品", businessType = BusinessType.INSERT) + @PostMapping("/publish") + public AjaxResult batchPublish(@RequestBody @Validated BatchPublishRequest request) + { + try { + Long taskId = batchPublishService.batchPublish(request); + return AjaxResult.success("任务已创建", taskId); + } catch (Exception e) { + logger.error("批量发品失败", e); + return AjaxResult.error("批量发品失败: " + e.getMessage()); + } + } + + /** + * 查询批量发品任务列表 + */ + @GetMapping("/task/list") + public TableDataInfo listTasks(BatchPublishTask task) + { + startPage(); + List list = batchPublishService.selectTaskList(task); + return getDataTable(list); + } + + /** + * 查询批量发品任务详情 + */ + @GetMapping("/task/{taskId}") + public AjaxResult getTask(@PathVariable("taskId") Long taskId) + { + BatchPublishTask task = batchPublishService.getTaskById(taskId); + return AjaxResult.success(task); + } + + /** + * 查询批量发品明细列表 + */ + @GetMapping("/item/list/{taskId}") + public AjaxResult listItems(@PathVariable("taskId") Long taskId) + { + List items = batchPublishService.getItemsByTaskId(taskId); + return AjaxResult.success(items); + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/erp/request/ERPAccount.java b/ruoyi-system/src/main/java/com/ruoyi/erp/request/ERPAccount.java index b16e699..d758718 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/erp/request/ERPAccount.java +++ b/ruoyi-system/src/main/java/com/ruoyi/erp/request/ERPAccount.java @@ -13,7 +13,7 @@ public enum ERPAccount { // 胡歌1016208368633221 ACCOUNT_HUGE("1016208368633221", "waLiRMgFcixLbcLjUSSwo370Hp1nBcBu","余生请多关照66","海尔胡歌"), // 刘强东anotherApiKey - ACCOUNT_LQD("1206879680251333", "HhJOQFdgqsrxn9m4Mz5V0AMtdUG6vTaT","tb8992720_2013","方案小号"); + ACCOUNT_LQD("1206879680251333", "HhJOQFdgqsrxn9m4Mz5V0AMtdUG6vTaT","tb8992720_2013","方案小号"); private final String apiKey; private final String apiKeySecret; diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishItem.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishItem.java new file mode 100644 index 0000000..0d873cb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishItem.java @@ -0,0 +1,206 @@ +package com.ruoyi.jarvis.domain; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.Date; + +/** + * 批量发品明细对象 batch_publish_item + * + * @author ruoyi + * @date 2025-01-10 + */ +public class BatchPublishItem extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 明细ID */ + private Long id; + + /** 任务ID */ + @Excel(name = "任务ID") + private Long taskId; + + /** SKUID */ + @Excel(name = "SKUID") + private String skuid; + + /** 商品名称 */ + @Excel(name = "商品名称") + private String productName; + + /** 目标ERP账号 */ + @Excel(name = "目标账号") + private String targetAccount; + + /** 账号备注名 */ + @Excel(name = "账号名称") + private String accountRemark; + + /** 发品状态:0待发布 1发布中 2发布成功 3发布失败 4上架中 5已上架 6上架失败 */ + @Excel(name = "发品状态", readConverterExp = "0=待发布,1=发布中,2=发布成功,3=发布失败,4=上架中,5=已上架,6=上架失败") + private Integer status; + + /** ERP商品ID(发品成功后返回) */ + @Excel(name = "商品ID") + private Long productId; + + /** 商品状态(发品成功后返回) */ + private Integer productStatus; + + /** 商家编码(发品成功后返回) */ + @Excel(name = "商家编码") + private String outerId; + + /** 发品价格(分) */ + @Excel(name = "发品价格") + private Long publishPrice; + + /** 失败原因 */ + @Excel(name = "失败原因") + private String errorMessage; + + /** 上架时间 */ + @Excel(name = "上架时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date publishTime; + + /** 延迟上架时间(秒) */ + private Integer delaySeconds; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTaskId() { + return taskId; + } + + public void setTaskId(Long taskId) { + this.taskId = taskId; + } + + public String getSkuid() { + return skuid; + } + + public void setSkuid(String skuid) { + this.skuid = skuid; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getTargetAccount() { + return targetAccount; + } + + public void setTargetAccount(String targetAccount) { + this.targetAccount = targetAccount; + } + + public String getAccountRemark() { + return accountRemark; + } + + public void setAccountRemark(String accountRemark) { + this.accountRemark = accountRemark; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Integer getProductStatus() { + return productStatus; + } + + public void setProductStatus(Integer productStatus) { + this.productStatus = productStatus; + } + + public String getOuterId() { + return outerId; + } + + public void setOuterId(String outerId) { + this.outerId = outerId; + } + + public Long getPublishPrice() { + return publishPrice; + } + + public void setPublishPrice(Long publishPrice) { + this.publishPrice = publishPrice; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public Integer getDelaySeconds() { + return delaySeconds; + } + + public void setDelaySeconds(Integer delaySeconds) { + this.delaySeconds = delaySeconds; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("taskId", getTaskId()) + .append("skuid", getSkuid()) + .append("productName", getProductName()) + .append("targetAccount", getTargetAccount()) + .append("accountRemark", getAccountRemark()) + .append("status", getStatus()) + .append("productId", getProductId()) + .append("productStatus", getProductStatus()) + .append("outerId", getOuterId()) + .append("publishPrice", getPublishPrice()) + .append("errorMessage", getErrorMessage()) + .append("publishTime", getPublishTime()) + .append("delaySeconds", getDelaySeconds()) + .append("createTime", getCreateTime()) + .toString(); + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishTask.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishTask.java new file mode 100644 index 0000000..facf409 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/BatchPublishTask.java @@ -0,0 +1,193 @@ +package com.ruoyi.jarvis.domain; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.Date; + +/** + * 批量发品任务对象 batch_publish_task + * + * @author ruoyi + * @date 2025-01-10 + */ +public class BatchPublishTask extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + private Long id; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String taskName; + + /** 原始线报消息 */ + @Excel(name = "原始线报消息") + private String originalMessage; + + /** 解析出的商品数量 */ + @Excel(name = "解析商品数量") + private Integer totalProducts; + + /** 选中的商品数量 */ + @Excel(name = "选中商品数量") + private Integer selectedProducts; + + /** 目标ERP账号(JSON数组) */ + @Excel(name = "目标账号") + private String targetAccounts; + + /** 任务状态:0待处理 1处理中 2已完成 3失败 */ + @Excel(name = "任务状态", readConverterExp = "0=待处理,1=处理中,2=已完成,3=失败") + private Integer status; + + /** 成功发品数量 */ + @Excel(name = "成功数量") + private Integer successCount; + + /** 失败发品数量 */ + @Excel(name = "失败数量") + private Integer failCount; + + /** 通用参数(JSON) */ + private String commonParams; + + /** 创建人ID */ + private Long createUserId; + + /** 创建人姓名 */ + @Excel(name = "创建人") + private String createUserName; + + /** 完成时间 */ + @Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date completeTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTaskName() { + return taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public String getOriginalMessage() { + return originalMessage; + } + + public void setOriginalMessage(String originalMessage) { + this.originalMessage = originalMessage; + } + + public Integer getTotalProducts() { + return totalProducts; + } + + public void setTotalProducts(Integer totalProducts) { + this.totalProducts = totalProducts; + } + + public Integer getSelectedProducts() { + return selectedProducts; + } + + public void setSelectedProducts(Integer selectedProducts) { + this.selectedProducts = selectedProducts; + } + + public String getTargetAccounts() { + return targetAccounts; + } + + public void setTargetAccounts(String targetAccounts) { + this.targetAccounts = targetAccounts; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Integer getSuccessCount() { + return successCount; + } + + public void setSuccessCount(Integer successCount) { + this.successCount = successCount; + } + + public Integer getFailCount() { + return failCount; + } + + public void setFailCount(Integer failCount) { + this.failCount = failCount; + } + + public String getCommonParams() { + return commonParams; + } + + public void setCommonParams(String commonParams) { + this.commonParams = commonParams; + } + + public Long getCreateUserId() { + return createUserId; + } + + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } + + public String getCreateUserName() { + return createUserName; + } + + public void setCreateUserName(String createUserName) { + this.createUserName = createUserName; + } + + public Date getCompleteTime() { + return completeTime; + } + + public void setCompleteTime(Date completeTime) { + this.completeTime = completeTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("taskName", getTaskName()) + .append("originalMessage", getOriginalMessage()) + .append("totalProducts", getTotalProducts()) + .append("selectedProducts", getSelectedProducts()) + .append("targetAccounts", getTargetAccounts()) + .append("status", getStatus()) + .append("successCount", getSuccessCount()) + .append("failCount", getFailCount()) + .append("commonParams", getCommonParams()) + .append("createUserId", getCreateUserId()) + .append("createUserName", getCreateUserName()) + .append("createTime", getCreateTime()) + .append("completeTime", getCompleteTime()) + .toString(); + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/BatchPublishRequest.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/BatchPublishRequest.java new file mode 100644 index 0000000..581ac18 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/BatchPublishRequest.java @@ -0,0 +1,319 @@ +package com.ruoyi.jarvis.domain.request; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 批量发品请求对象 + * + * @author ruoyi + * @date 2025-01-10 + */ +public class BatchPublishRequest { + + /** 任务名称 */ + private String taskName; + + /** 原始线报消息 */ + private String originalMessage; + + /** 选中的商品列表 */ + @NotEmpty(message = "商品列表不能为空") + private List products; + + /** 目标ERP账号列表 */ + @NotEmpty(message = "目标账号不能为空") + private List targetAccounts; + + /** 通用参数 */ + @NotNull(message = "通用参数不能为空") + private CommonParams commonParams; + + /** 延迟上架时间(秒),默认3秒 */ + private Integer delaySeconds = 3; + + public static class ProductItem { + /** SKUID */ + @NotBlank(message = "SKUID不能为空") + private String skuid; + + /** 商品名称 */ + private String productName; + + /** 商品价格(元) */ + private Double price; + + /** 商品图片 */ + private String productImage; + + /** 店铺名称 */ + private String shopName; + + /** 店铺ID */ + private String shopId; + + /** 佣金比例 */ + private String commissionInfo; + + public String getSkuid() { + return skuid; + } + + public void setSkuid(String skuid) { + this.skuid = skuid; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public String getProductImage() { + return productImage; + } + + public void setProductImage(String productImage) { + this.productImage = productImage; + } + + public String getShopName() { + return shopName; + } + + public void setShopName(String shopName) { + this.shopName = shopName; + } + + public String getShopId() { + return shopId; + } + + public void setShopId(String shopId) { + this.shopId = shopId; + } + + public String getCommissionInfo() { + return commissionInfo; + } + + public void setCommissionInfo(String commissionInfo) { + this.commissionInfo = commissionInfo; + } + } + + public static class CommonParams { + /** 会员名 */ + @NotBlank(message = "会员名不能为空") + private String userName; + + /** 省 */ + @NotNull(message = "省不能为空") + private Integer province; + + /** 市 */ + @NotNull(message = "市不能为空") + private Integer city; + + /** 区 */ + @NotNull(message = "区不能为空") + private Integer district; + + /** 商品类型 */ + @NotNull(message = "商品类型不能为空") + private Integer itemBizType; + + /** 行业类型 */ + @NotNull(message = "行业类型不能为空") + private Integer spBizType; + + /** 类目ID */ + @NotBlank(message = "类目ID不能为空") + private String channelCatId; + + /** 邮费(元) */ + @NotNull(message = "邮费不能为空") + private Double expressFee; + + /** 库存 */ + @NotNull(message = "库存不能为空") + private Integer stock; + + /** 成色 */ + private Integer stuffStatus; + + /** 服务支持(逗号分隔) */ + private String serviceSupport; + + /** 商品属性(JSON) */ + private String channelPv; + + /** 白底图链接 */ + private String whiteImages; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Integer getProvince() { + return province; + } + + public void setProvince(Integer province) { + this.province = province; + } + + public Integer getCity() { + return city; + } + + public void setCity(Integer city) { + this.city = city; + } + + public Integer getDistrict() { + return district; + } + + public void setDistrict(Integer district) { + this.district = district; + } + + public Integer getItemBizType() { + return itemBizType; + } + + public void setItemBizType(Integer itemBizType) { + this.itemBizType = itemBizType; + } + + public Integer getSpBizType() { + return spBizType; + } + + public void setSpBizType(Integer spBizType) { + this.spBizType = spBizType; + } + + public String getChannelCatId() { + return channelCatId; + } + + public void setChannelCatId(String channelCatId) { + this.channelCatId = channelCatId; + } + + public Double getExpressFee() { + return expressFee; + } + + public void setExpressFee(Double expressFee) { + this.expressFee = expressFee; + } + + public Integer getStock() { + return stock; + } + + public void setStock(Integer stock) { + this.stock = stock; + } + + public Integer getStuffStatus() { + return stuffStatus; + } + + public void setStuffStatus(Integer stuffStatus) { + this.stuffStatus = stuffStatus; + } + + public String getServiceSupport() { + return serviceSupport; + } + + public void setServiceSupport(String serviceSupport) { + this.serviceSupport = serviceSupport; + } + + public String getChannelPv() { + return channelPv; + } + + public void setChannelPv(String channelPv) { + this.channelPv = channelPv; + } + + public String getWhiteImages() { + return whiteImages; + } + + public void setWhiteImages(String whiteImages) { + this.whiteImages = whiteImages; + } + } + + public String getTaskName() { + return taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public String getOriginalMessage() { + return originalMessage; + } + + public void setOriginalMessage(String originalMessage) { + this.originalMessage = originalMessage; + } + + public List getProducts() { + return products; + } + + public void setProducts(List products) { + this.products = products; + } + + public List getTargetAccounts() { + return targetAccounts; + } + + public void setTargetAccounts(List targetAccounts) { + this.targetAccounts = targetAccounts; + } + + public CommonParams getCommonParams() { + return commonParams; + } + + public void setCommonParams(CommonParams commonParams) { + this.commonParams = commonParams; + } + + public Integer getDelaySeconds() { + return delaySeconds; + } + + public void setDelaySeconds(Integer delaySeconds) { + this.delaySeconds = delaySeconds; + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/ParseLineReportRequest.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/ParseLineReportRequest.java new file mode 100644 index 0000000..531e317 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/request/ParseLineReportRequest.java @@ -0,0 +1,25 @@ +package com.ruoyi.jarvis.domain.request; + +import javax.validation.constraints.NotBlank; + +/** + * 解析线报消息请求对象 + * + * @author ruoyi + * @date 2025-01-10 + */ +public class ParseLineReportRequest { + + /** 线报消息内容 */ + @NotBlank(message = "线报消息不能为空") + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishItemMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishItemMapper.java new file mode 100644 index 0000000..b98013b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishItemMapper.java @@ -0,0 +1,70 @@ +package com.ruoyi.jarvis.mapper; + +import com.ruoyi.jarvis.domain.BatchPublishItem; +import java.util.List; + +/** + * 批量发品明细Mapper接口 + * + * @author ruoyi + * @date 2025-01-10 + */ +public interface BatchPublishItemMapper +{ + /** + * 查询批量发品明细 + * + * @param id 批量发品明细主键 + * @return 批量发品明细 + */ + public BatchPublishItem selectBatchPublishItemById(Long id); + + /** + * 查询批量发品明细列表 + * + * @param batchPublishItem 批量发品明细 + * @return 批量发品明细集合 + */ + public List selectBatchPublishItemList(BatchPublishItem batchPublishItem); + + /** + * 根据任务ID查询明细列表 + * + * @param taskId 任务ID + * @return 批量发品明细集合 + */ + public List selectBatchPublishItemByTaskId(Long taskId); + + /** + * 新增批量发品明细 + * + * @param batchPublishItem 批量发品明细 + * @return 结果 + */ + public int insertBatchPublishItem(BatchPublishItem batchPublishItem); + + /** + * 修改批量发品明细 + * + * @param batchPublishItem 批量发品明细 + * @return 结果 + */ + public int updateBatchPublishItem(BatchPublishItem batchPublishItem); + + /** + * 删除批量发品明细 + * + * @param id 批量发品明细主键 + * @return 结果 + */ + public int deleteBatchPublishItemById(Long id); + + /** + * 批量新增批量发品明细 + * + * @param items 批量发品明细列表 + * @return 结果 + */ + public int batchInsertBatchPublishItem(List items); +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishTaskMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishTaskMapper.java new file mode 100644 index 0000000..83ad6dd --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/BatchPublishTaskMapper.java @@ -0,0 +1,54 @@ +package com.ruoyi.jarvis.mapper; + +import com.ruoyi.jarvis.domain.BatchPublishTask; +import java.util.List; + +/** + * 批量发品任务Mapper接口 + * + * @author ruoyi + * @date 2025-01-10 + */ +public interface BatchPublishTaskMapper +{ + /** + * 查询批量发品任务 + * + * @param id 批量发品任务主键 + * @return 批量发品任务 + */ + public BatchPublishTask selectBatchPublishTaskById(Long id); + + /** + * 查询批量发品任务列表 + * + * @param batchPublishTask 批量发品任务 + * @return 批量发品任务集合 + */ + public List selectBatchPublishTaskList(BatchPublishTask batchPublishTask); + + /** + * 新增批量发品任务 + * + * @param batchPublishTask 批量发品任务 + * @return 结果 + */ + public int insertBatchPublishTask(BatchPublishTask batchPublishTask); + + /** + * 修改批量发品任务 + * + * @param batchPublishTask 批量发品任务 + * @return 结果 + */ + public int updateBatchPublishTask(BatchPublishTask batchPublishTask); + + /** + * 删除批量发品任务 + * + * @param id 批量发品任务主键 + * @return 结果 + */ + public int deleteBatchPublishTaskById(Long id); +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IBatchPublishService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IBatchPublishService.java new file mode 100644 index 0000000..a24e495 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IBatchPublishService.java @@ -0,0 +1,67 @@ +package com.ruoyi.jarvis.service; + +import com.ruoyi.jarvis.domain.BatchPublishTask; +import com.ruoyi.jarvis.domain.BatchPublishItem; +import com.ruoyi.jarvis.domain.request.BatchPublishRequest; +import com.ruoyi.jarvis.domain.request.ParseLineReportRequest; + +import java.util.List; +import java.util.Map; + +/** + * 批量发品Service接口 + * + * @author ruoyi + * @date 2025-01-10 + */ +public interface IBatchPublishService +{ + /** + * 解析线报消息,提取商品列表 + * + * @param request 解析请求 + * @return 商品列表 + */ + List> parseLineReport(ParseLineReportRequest request); + + /** + * 批量发品(支持多账号、多商品) + * + * @param request 批量发品请求 + * @return 任务ID + */ + Long batchPublish(BatchPublishRequest request); + + /** + * 查询批量发品任务 + * + * @param taskId 任务ID + * @return 任务信息 + */ + BatchPublishTask getTaskById(Long taskId); + + /** + * 查询批量发品明细列表 + * + * @param taskId 任务ID + * @return 明细列表 + */ + List getItemsByTaskId(Long taskId); + + /** + * 查询批量发品任务列表 + * + * @param task 任务查询条件 + * @return 任务列表 + */ + List selectTaskList(BatchPublishTask task); + + /** + * 延迟上架商品 + * + * @param itemId 明细ID + * @param delaySeconds 延迟秒数 + */ + void schedulePublish(Long itemId, Integer delaySeconds); +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java new file mode 100644 index 0000000..ad72667 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java @@ -0,0 +1,426 @@ +package com.ruoyi.jarvis.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.erp.request.ERPAccount; +import com.ruoyi.jarvis.domain.BatchPublishItem; +import com.ruoyi.jarvis.domain.BatchPublishTask; +import com.ruoyi.jarvis.domain.request.BatchPublishRequest; +import com.ruoyi.jarvis.domain.request.ParseLineReportRequest; +import com.ruoyi.jarvis.mapper.BatchPublishItemMapper; +import com.ruoyi.jarvis.mapper.BatchPublishTaskMapper; +import com.ruoyi.jarvis.service.IBatchPublishService; +import com.ruoyi.jarvis.service.IJDOrderService; +import com.ruoyi.jarvis.util.LineReportParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * 批量发品Service业务层处理 + * + * @author ruoyi + * @date 2025-01-10 + */ +@Service +public class BatchPublishServiceImpl implements IBatchPublishService +{ + private static final Logger log = LoggerFactory.getLogger(BatchPublishServiceImpl.java); + + @Autowired + private BatchPublishTaskMapper taskMapper; + + @Autowired + private BatchPublishItemMapper itemMapper; + + @Autowired + private IJDOrderService jdOrderService; + + /** + * 解析线报消息,提取商品列表 + */ + @Override + public List> parseLineReport(ParseLineReportRequest request) { + String message = request.getMessage(); + log.info("开始解析线报消息,消息长度: {}", message.length()); + + // 提取SKUID和URL + List urls = LineReportParser.extractJdUrls(message); + List skuids = LineReportParser.extractSkuids(message); + + log.info("提取到 {} 个URL, {} 个SKUID", urls.size(), skuids.size()); + + // 查询商品详情 + List> products = new ArrayList<>(); + Set processedSkuids = new HashSet<>(); + + // 优先处理URL(更准确) + for (String url : urls) { + try { + Map productInfo = queryProductInfo(url); + if (productInfo != null) { + String skuid = (String) productInfo.get("skuid"); + if (skuid != null && !processedSkuids.contains(skuid)) { + products.add(productInfo); + processedSkuids.add(skuid); + } + } + } catch (Exception e) { + log.error("查询商品信息失败,URL: {}", url, e); + } + } + + // 处理剩余的SKUID + for (String skuid : skuids) { + if (!processedSkuids.contains(skuid)) { + try { + Map productInfo = queryProductInfo(skuid); + if (productInfo != null) { + products.add(productInfo); + processedSkuids.add(skuid); + } + } catch (Exception e) { + log.error("查询商品信息失败,SKUID: {}", skuid, e); + } + } + } + + log.info("解析完成,共获取 {} 个商品信息", products.size()); + return products; + } + + /** + * 查询商品信息 + */ + private Map queryProductInfo(String urlOrSkuid) { + try { + // 调用JDOrder服务的generatePromotionContent接口 + Map requestBody = new HashMap<>(); + requestBody.put("promotionContent", urlOrSkuid); + + String result = jdOrderService.generatePromotionContent(requestBody); + + if (StringUtils.isEmpty(result)) { + return null; + } + + // 解析返回结果 + Object resultObj = JSON.parse(result); + if (resultObj instanceof List) { + List list = (List) resultObj; + if (!list.isEmpty() && list.get(0) instanceof Map) { + @SuppressWarnings("unchecked") + Map productMap = (Map) list.get(0); + return normalizeProductInfo(productMap); + } + } else if (resultObj instanceof Map) { + @SuppressWarnings("unchecked") + Map productMap = (Map) resultObj; + return normalizeProductInfo(productMap); + } + + return null; + } catch (Exception e) { + log.error("查询商品信息异常", e); + return null; + } + } + + /** + * 规范化商品信息 + */ + private Map normalizeProductInfo(Map productMap) { + Map result = new HashMap<>(); + + // 提取基本信息 + result.put("skuid", getValueFromMap(productMap, "spuid", "skuId", "sku")); + result.put("productName", getValueFromMap(productMap, "skuName", "title", "name")); + result.put("price", getValueFromMap(productMap, "price", "opPrice", "jdPrice")); + result.put("productImage", getFirstImage(productMap)); + result.put("shopName", getValueFromMap(productMap, "shopName")); + result.put("shopId", getValueFromMap(productMap, "shopId")); + result.put("commissionInfo", getValueFromMap(productMap, "commissionShare", "commission")); + result.put("materialUrl", getValueFromMap(productMap, "materialUrl", "url")); + + // 保留原始数据 + result.put("_raw", productMap); + + return result; + } + + /** + * 从Map中获取第一个非空值 + */ + private Object getValueFromMap(Map map, String... keys) { + for (String key : keys) { + Object value = map.get(key); + if (value != null && StringUtils.isNotEmpty(value.toString())) { + return value; + } + } + return null; + } + + /** + * 获取第一张图片 + */ + private String getFirstImage(Map map) { + Object images = map.get("images"); + if (images instanceof List) { + List imageList = (List) images; + if (!imageList.isEmpty()) { + return imageList.get(0).toString(); + } + } + + Object mainImage = map.get("mainImage"); + if (mainImage != null) { + return mainImage.toString(); + } + + return null; + } + + /** + * 批量发品(支持多账号、多商品) + */ + @Override + @Transactional + public Long batchPublish(BatchPublishRequest request) { + log.info("开始批量发品任务,商品数: {}, 账号数: {}", + request.getProducts().size(), request.getTargetAccounts().size()); + + // 获取当前用户 + SysUser currentUser = SecurityUtils.getLoginUser().getUser(); + + // 创建任务记录 + BatchPublishTask task = new BatchPublishTask(); + task.setTaskName(request.getTaskName()); + task.setOriginalMessage(request.getOriginalMessage()); + task.setTotalProducts(request.getProducts().size()); + task.setSelectedProducts(request.getProducts().size()); + task.setTargetAccounts(JSON.toJSONString(request.getTargetAccounts())); + task.setStatus(0); // 待处理 + task.setSuccessCount(0); + task.setFailCount(0); + task.setCommonParams(JSON.toJSONString(request.getCommonParams())); + task.setCreateUserId(currentUser.getUserId()); + task.setCreateUserName(currentUser.getUserName()); + task.setCreateTime(new Date()); + + taskMapper.insertBatchPublishTask(task); + Long taskId = task.getId(); + + // 创建明细记录 + List items = new ArrayList<>(); + for (BatchPublishRequest.ProductItem product : request.getProducts()) { + for (String accountAppid : request.getTargetAccounts()) { + BatchPublishItem item = new BatchPublishItem(); + item.setTaskId(taskId); + item.setSkuid(product.getSkuid()); + item.setProductName(product.getProductName()); + item.setTargetAccount(accountAppid); + item.setAccountRemark(getAccountRemark(accountAppid)); + item.setStatus(0); // 待发布 + item.setPublishPrice(product.getPrice() != null ? Math.round(product.getPrice() * 100) : null); + item.setDelaySeconds(request.getDelaySeconds()); + item.setCreateTime(new Date()); + items.add(item); + } + } + + if (!items.isEmpty()) { + itemMapper.batchInsertBatchPublishItem(items); + } + + // 异步执行发品任务 + asyncBatchPublish(taskId, items, request); + + return taskId; + } + + /** + * 获取账号备注名 + */ + private String getAccountRemark(String appid) { + for (ERPAccount account : ERPAccount.values()) { + if (account.getApiKey().equals(appid)) { + return account.getRemark(); + } + } + return appid; + } + + /** + * 异步执行批量发品 + */ + @Async + public void asyncBatchPublish(Long taskId, List items, BatchPublishRequest request) { + log.info("开始异步执行批量发品任务: {}", taskId); + + // 更新任务状态为处理中 + BatchPublishTask task = new BatchPublishTask(); + task.setId(taskId); + task.setStatus(1); // 处理中 + taskMapper.updateBatchPublishTask(task); + + int successCount = 0; + int failCount = 0; + + // 逐个发品 + for (BatchPublishItem item : items) { + try { + // 更新明细状态为发布中 + item.setStatus(1); + itemMapper.updateBatchPublishItem(item); + + // 调用发品接口 + boolean success = publishProduct(item, request); + + if (success) { + successCount++; + // 发品成功,调度延迟上架 + schedulePublish(item.getId(), item.getDelaySeconds()); + } else { + failCount++; + } + } catch (Exception e) { + log.error("发品失败,商品: {}, 账号: {}", item.getProductName(), item.getAccountRemark(), e); + item.setStatus(3); // 发布失败 + item.setErrorMessage("发品异常: " + e.getMessage()); + itemMapper.updateBatchPublishItem(item); + failCount++; + } + } + + // 更新任务状态为已完成 + task.setStatus(2); // 已完成 + task.setSuccessCount(successCount); + task.setFailCount(failCount); + task.setCompleteTime(new Date()); + taskMapper.updateBatchPublishTask(task); + + log.info("批量发品任务完成: {}, 成功: {}, 失败: {}", taskId, successCount, failCount); + } + + /** + * 发布单个商品 + */ + private boolean publishProduct(BatchPublishItem item, BatchPublishRequest request) { + log.info("开始发品: 商品={}, 账号={}", item.getProductName(), item.getAccountRemark()); + + try { + // TODO: 调用实际的发品接口 + // 这里需要调用 ProductController 的 createByPromotion 接口 + // 暂时模拟成功 + + // 模拟发品成功 + item.setStatus(2); // 发布成功 + item.setProductId(System.currentTimeMillis()); // 模拟商品ID + item.setProductStatus(1); // 模拟商品状态 + item.setOuterId("OUTER_" + item.getSkuid()); // 模拟商家编码 + item.setErrorMessage(null); + itemMapper.updateBatchPublishItem(item); + + return true; + } catch (Exception e) { + log.error("发品失败", e); + item.setStatus(3); // 发布失败 + item.setErrorMessage(e.getMessage()); + itemMapper.updateBatchPublishItem(item); + return false; + } + } + + /** + * 延迟上架商品 + */ + @Override + public void schedulePublish(Long itemId, Integer delaySeconds) { + log.info("调度延迟上架: itemId={}, delay={}秒", itemId, delaySeconds); + + CompletableFuture.runAsync(() -> { + try { + // 延迟指定秒数 + TimeUnit.SECONDS.sleep(delaySeconds); + + // 执行上架 + doPublish(itemId); + } catch (InterruptedException e) { + log.error("延迟上架被中断", e); + Thread.currentThread().interrupt(); + } + }); + } + + /** + * 执行上架操作 + */ + private void doPublish(Long itemId) { + log.info("开始上架商品: itemId={}", itemId); + + BatchPublishItem item = itemMapper.selectBatchPublishItemById(itemId); + if (item == null) { + log.error("商品明细不存在: {}", itemId); + return; + } + + try { + // 更新状态为上架中 + item.setStatus(4); + itemMapper.updateBatchPublishItem(item); + + // TODO: 调用实际的上架接口 + // 这里需要调用 ProductController 的 publish 接口 + // 暂时模拟成功 + + // 模拟上架成功 + item.setStatus(5); // 已上架 + item.setPublishTime(new Date()); + item.setErrorMessage(null); + itemMapper.updateBatchPublishItem(item); + + log.info("上架成功: itemId={}", itemId); + } catch (Exception e) { + log.error("上架失败", e); + item.setStatus(6); // 上架失败 + item.setErrorMessage("上架异常: " + e.getMessage()); + itemMapper.updateBatchPublishItem(item); + } + } + + /** + * 查询批量发品任务 + */ + @Override + public BatchPublishTask getTaskById(Long taskId) { + return taskMapper.selectBatchPublishTaskById(taskId); + } + + /** + * 查询批量发品明细列表 + */ + @Override + public List getItemsByTaskId(Long taskId) { + return itemMapper.selectBatchPublishItemByTaskId(taskId); + } + + /** + * 查询批量发品任务列表 + */ + @Override + public List selectTaskList(BatchPublishTask task) { + return taskMapper.selectBatchPublishTaskList(task); + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/LineReportParser.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/LineReportParser.java new file mode 100644 index 0000000..28799d1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/LineReportParser.java @@ -0,0 +1,128 @@ +package com.ruoyi.jarvis.util; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 线报消息解析工具类 + * + * @author ruoyi + * @date 2025-01-10 + */ +public class LineReportParser { + + // 京东链接正则表达式 + private static final Pattern JD_URL_PATTERN = Pattern.compile("https?://[^\\s]*?jd\\.com[^\\s]*"); + + // SKUID正则表达式(10-13位数字) + private static final Pattern SKUID_PATTERN = Pattern.compile("\\b(\\d{10,13})\\b"); + + /** + * 从线报消息中提取所有京东链接 + * + * @param message 线报消息 + * @return 京东链接列表 + */ + public static List extractJdUrls(String message) { + List urls = new ArrayList<>(); + if (message == null || message.trim().isEmpty()) { + return urls; + } + + Matcher matcher = JD_URL_PATTERN.matcher(message); + while (matcher.find()) { + String url = matcher.group(); + // 清理URL末尾的标点符号 + url = url.replaceAll("[\\s,,。!!??]+$", ""); + if (!urls.contains(url)) { + urls.add(url); + } + } + + return urls; + } + + /** + * 从线报消息中提取所有可能的SKUID + * + * @param message 线报消息 + * @return SKUID列表 + */ + public static List extractSkuids(String message) { + Set skuids = new LinkedHashSet<>(); + if (message == null || message.trim().isEmpty()) { + return new ArrayList<>(skuids); + } + + // 先从URL中提取SKUID + List urls = extractJdUrls(message); + for (String url : urls) { + String skuid = extractSkuidFromUrl(url); + if (skuid != null) { + skuids.add(skuid); + } + } + + // 再从文本中直接提取可能的SKUID(10-13位数字) + Matcher matcher = SKUID_PATTERN.matcher(message); + while (matcher.find()) { + String skuid = matcher.group(1); + // 只添加11-13位的,避免误识别(如手机号等) + if (skuid.length() >= 11) { + skuids.add(skuid); + } + } + + return new ArrayList<>(skuids); + } + + /** + * 从JD链接中提取SKUID + * + * @param url JD链接 + * @return SKUID,如果提取失败返回null + */ + public static String extractSkuidFromUrl(String url) { + if (url == null || url.trim().isEmpty()) { + return null; + } + + // 匹配 item.jd.com/{skuid}.html + Pattern pattern1 = Pattern.compile("item\\.jd\\.com/(\\d+)\\.html"); + Matcher matcher1 = pattern1.matcher(url); + if (matcher1.find()) { + return matcher1.group(1); + } + + // 匹配 sku=xxx 或 skuId=xxx + Pattern pattern2 = Pattern.compile("[?&]sku[Ii]?d?=(\\d+)"); + Matcher matcher2 = pattern2.matcher(url); + if (matcher2.find()) { + return matcher2.group(1); + } + + // 匹配短链接中的数字 + Pattern pattern3 = Pattern.compile("u\\.jd\\.com/[^\\s]*(\\d{10,13})"); + Matcher matcher3 = pattern3.matcher(url); + if (matcher3.find()) { + return matcher3.group(1); + } + + return null; + } + + /** + * 解析线报消息,返回提取的信息 + * + * @param message 线报消息 + * @return 包含URLs和SKUIDs的Map + */ + public static Map parseMessage(String message) { + Map result = new HashMap<>(); + result.put("urls", extractJdUrls(message)); + result.put("skuids", extractSkuids(message)); + return result; + } +} + diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishItemMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishItemMapper.xml new file mode 100644 index 0000000..f4f27ba --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishItemMapper.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, task_id, skuid, product_name, target_account, account_remark, status, + product_id, product_status, outer_id, publish_price, error_message, + publish_time, delay_seconds, create_time + from batch_publish_item + + + + + + + + + + insert into batch_publish_item + + task_id, + skuid, + product_name, + target_account, + account_remark, + status, + product_id, + product_status, + outer_id, + publish_price, + error_message, + publish_time, + delay_seconds, + create_time, + + + #{taskId}, + #{skuid}, + #{productName}, + #{targetAccount}, + #{accountRemark}, + #{status}, + #{productId}, + #{productStatus}, + #{outerId}, + #{publishPrice}, + #{errorMessage}, + #{publishTime}, + #{delaySeconds}, + #{createTime}, + + + + + insert into batch_publish_item + (task_id, skuid, product_name, target_account, account_remark, status, publish_price, delay_seconds, create_time) + values + + (#{item.taskId}, #{item.skuid}, #{item.productName}, #{item.targetAccount}, #{item.accountRemark}, + #{item.status}, #{item.publishPrice}, #{item.delaySeconds}, #{item.createTime}) + + + + + update batch_publish_item + + status = #{status}, + product_id = #{productId}, + product_status = #{productStatus}, + outer_id = #{outerId}, + error_message = #{errorMessage}, + publish_time = #{publishTime}, + + where id = #{id} + + + + delete from batch_publish_item where id = #{id} + + + + diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishTaskMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishTaskMapper.xml new file mode 100644 index 0000000..0e2ca15 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/jarvis/BatchPublishTaskMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + select id, task_name, original_message, total_products, selected_products, target_accounts, + status, success_count, fail_count, common_params, create_user_id, create_user_name, + create_time, complete_time + from batch_publish_task + + + + + + + + insert into batch_publish_task + + task_name, + original_message, + total_products, + selected_products, + target_accounts, + status, + success_count, + fail_count, + common_params, + create_user_id, + create_user_name, + create_time, + + + #{taskName}, + #{originalMessage}, + #{totalProducts}, + #{selectedProducts}, + #{targetAccounts}, + #{status}, + #{successCount}, + #{failCount}, + #{commonParams}, + #{createUserId}, + #{createUserName}, + #{createTime}, + + + + + update batch_publish_task + + task_name = #{taskName}, + status = #{status}, + success_count = #{successCount}, + fail_count = #{failCount}, + complete_time = #{completeTime}, + + where id = #{id} + + + + delete from batch_publish_task where id = #{id} + + + + diff --git a/sql/batch_publish.sql b/sql/batch_publish.sql new file mode 100644 index 0000000..073d0a3 --- /dev/null +++ b/sql/batch_publish.sql @@ -0,0 +1,46 @@ +-- 批量发品任务表 +CREATE TABLE IF NOT EXISTS `batch_publish_task` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `task_name` varchar(200) DEFAULT NULL COMMENT '任务名称', + `original_message` text COMMENT '原始线报消息', + `total_products` int(11) DEFAULT NULL COMMENT '解析出的商品数量', + `selected_products` int(11) DEFAULT NULL COMMENT '选中的商品数量', + `target_accounts` varchar(500) DEFAULT NULL COMMENT '目标ERP账号(JSON数组)', + `status` int(11) DEFAULT '0' COMMENT '任务状态:0待处理 1处理中 2已完成 3失败', + `success_count` int(11) DEFAULT '0' COMMENT '成功发品数量', + `fail_count` int(11) DEFAULT '0' COMMENT '失败发品数量', + `common_params` text COMMENT '通用参数(JSON)', + `create_user_id` bigint(20) DEFAULT NULL COMMENT '创建人ID', + `create_user_name` varchar(100) DEFAULT NULL COMMENT '创建人姓名', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `complete_time` datetime DEFAULT NULL COMMENT '完成时间', + PRIMARY KEY (`id`), + KEY `idx_create_user` (`create_user_id`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='批量发品任务表'; + +-- 批量发品明细表 +CREATE TABLE IF NOT EXISTS `batch_publish_item` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID', + `task_id` bigint(20) NOT NULL COMMENT '任务ID', + `skuid` varchar(100) DEFAULT NULL COMMENT 'SKUID', + `product_name` varchar(500) DEFAULT NULL COMMENT '商品名称', + `target_account` varchar(100) DEFAULT NULL COMMENT '目标ERP账号', + `account_remark` varchar(100) DEFAULT NULL COMMENT '账号备注名', + `status` int(11) DEFAULT '0' COMMENT '发品状态:0待发布 1发布中 2发布成功 3发布失败 4上架中 5已上架 6上架失败', + `product_id` bigint(20) DEFAULT NULL COMMENT 'ERP商品ID(发品成功后返回)', + `product_status` int(11) DEFAULT NULL COMMENT '商品状态(发品成功后返回)', + `outer_id` varchar(100) DEFAULT NULL COMMENT '商家编码(发品成功后返回)', + `publish_price` bigint(20) DEFAULT NULL COMMENT '发品价格(分)', + `error_message` varchar(1000) DEFAULT NULL COMMENT '失败原因', + `publish_time` datetime DEFAULT NULL COMMENT '上架时间', + `delay_seconds` int(11) DEFAULT '3' COMMENT '延迟上架时间(秒)', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_task_id` (`task_id`), + KEY `idx_skuid` (`skuid`), + KEY `idx_status` (`status`), + KEY `idx_product_id` (`product_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='批量发品明细表'; +