Compare commits
70 Commits
4430351e69
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
632b9f7eb1 | ||
|
|
eb53915bcd | ||
|
|
4dd3e9dd70 | ||
|
|
9206824efb | ||
|
|
2524461ff4 | ||
|
|
7581cc02a9 | ||
|
|
1dc91a6bb0 | ||
|
|
6b3c2b17c8 | ||
|
|
e890b18e3e | ||
|
|
9b2b770e29 | ||
|
|
047575ea42 | ||
|
|
702463b856 | ||
|
|
3aa3da8ade | ||
|
|
20861d270a | ||
|
|
e7c991ed9c | ||
|
|
2ead103faa | ||
|
|
c541beb413 | ||
|
|
083bcca270 | ||
|
|
35dcb20e4a | ||
|
|
7648b934ed | ||
|
|
01f0be6198 | ||
|
|
276fb49354 | ||
|
|
4f917dce10 | ||
|
|
98b56ab11b | ||
|
|
b495431b7e | ||
|
|
7f4b0dd986 | ||
|
|
79c5bf266f | ||
|
|
04156492a6 | ||
|
|
f578b9b2c9 | ||
|
|
6b07fa1d75 | ||
|
|
978da7042d | ||
|
|
66ac54ca70 | ||
|
|
026c6bf2a3 | ||
|
|
2b0587d4e1 | ||
|
|
0880628c93 | ||
|
|
2e59f49677 | ||
|
|
a54c8cc0cd | ||
|
|
8a23c4d3f7 | ||
|
|
b8981ffc98 | ||
|
|
9e69230948 | ||
|
|
64ce923631 | ||
|
|
2cd3a0a798 | ||
|
|
8889791a83 | ||
|
|
e184c7926f | ||
|
|
d73c7b6560 | ||
|
|
9d8f2ded0c | ||
|
|
7294748ae9 | ||
|
|
142b395dbe | ||
|
|
c8b15275a4 | ||
|
|
a61003fb7c | ||
|
|
939d03e192 | ||
|
|
e2facc3099 | ||
|
|
af68b529b0 | ||
|
|
185483dace | ||
|
|
e79e7081ee | ||
|
|
3176e45057 | ||
|
|
72b3458ef9 | ||
|
|
00149dc198 | ||
|
|
10020e6d52 | ||
|
|
c0908690b4 | ||
|
|
70ea908c23 | ||
|
|
18d2fb8dee | ||
|
|
a8c948e958 | ||
|
|
654a496478 | ||
|
|
79082adf8c | ||
|
|
287bf75d77 | ||
| 8ba4c4e383 | |||
| 0b5f054286 | |||
| 652824b84a | |||
| d1a1100064 |
@@ -16,6 +16,7 @@ import com.ruoyi.erp.request.ProductCategoryListQueryRequest;
|
|||||||
import com.ruoyi.erp.request.ProductPropertyListQueryRequest;
|
import com.ruoyi.erp.request.ProductPropertyListQueryRequest;
|
||||||
import com.ruoyi.erp.request.AuthorizeListQueryRequest;
|
import com.ruoyi.erp.request.AuthorizeListQueryRequest;
|
||||||
import com.ruoyi.erp.request.ProductPublishRequest;
|
import com.ruoyi.erp.request.ProductPublishRequest;
|
||||||
|
import com.ruoyi.erp.request.ProductDownShelfRequest;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -550,6 +551,217 @@ public class ProductController extends BaseController {
|
|||||||
public String getAppid() { return appid; }
|
public String getAppid() { return appid; }
|
||||||
public void setAppid(String appid) { this.appid = appid; }
|
public void setAppid(String appid) { this.appid = appid; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下架商品(单个)
|
||||||
|
*/
|
||||||
|
@PostMapping("/downShelf")
|
||||||
|
public R<?> downShelf(@RequestBody @Validated DownShelfRequest req) {
|
||||||
|
try {
|
||||||
|
ERPAccount account = resolveAccount(req.getAppid());
|
||||||
|
ProductDownShelfRequest downShelfRequest = new ProductDownShelfRequest(account);
|
||||||
|
downShelfRequest.setProductId(req.getProductId());
|
||||||
|
String resp = downShelfRequest.getResponseBody();
|
||||||
|
JSONObject jo = JSONObject.parseObject(resp);
|
||||||
|
return R.ok(jo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下架商品失败: productId={}", req.getProductId(), e);
|
||||||
|
return R.fail("下架失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量上架商品
|
||||||
|
*/
|
||||||
|
@PostMapping("/batchPublish")
|
||||||
|
public R<?> batchPublish(@RequestBody @Validated BatchPublishRequest req) {
|
||||||
|
try {
|
||||||
|
ERPAccount account = resolveAccount(req.getAppid());
|
||||||
|
List<Long> productIds = req.getProductIds();
|
||||||
|
|
||||||
|
if (productIds == null || productIds.isEmpty()) {
|
||||||
|
return R.fail("商品ID列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.getUserName() == null || req.getUserName().isEmpty()) {
|
||||||
|
return R.fail("闲鱼会员名不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HashMap<String, Object>> results = new ArrayList<>();
|
||||||
|
int successCount = 0;
|
||||||
|
int failCount = 0;
|
||||||
|
|
||||||
|
for (Long productId : productIds) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
result.put("productId", productId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProductPublishRequest publishRequest = new ProductPublishRequest(account);
|
||||||
|
publishRequest.setProductId(productId);
|
||||||
|
publishRequest.setUserName(req.getUserName());
|
||||||
|
if (req.getSpecifyPublishTime() != null) {
|
||||||
|
publishRequest.setSpecifyPublishTime(req.getSpecifyPublishTime());
|
||||||
|
}
|
||||||
|
String resp = publishRequest.getResponseBody();
|
||||||
|
JSONObject jo = JSONObject.parseObject(resp);
|
||||||
|
|
||||||
|
if (jo != null && jo.getInteger("code") != null && jo.getInteger("code") == 0) {
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("msg", "上架成功");
|
||||||
|
result.put("response", jo);
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("msg", jo != null ? jo.getString("msg") : "上架失败");
|
||||||
|
result.put("response", jo);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量上架商品失败: productId={}", productId, e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("msg", "上架异常: " + e.getMessage());
|
||||||
|
result.put("response", null);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, Object> summary = new HashMap<>();
|
||||||
|
summary.put("total", productIds.size());
|
||||||
|
summary.put("success", successCount);
|
||||||
|
summary.put("fail", failCount);
|
||||||
|
summary.put("results", results);
|
||||||
|
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
response.put("code", failCount == 0 ? 0 : 500);
|
||||||
|
response.put("msg", failCount == 0 ? "全部上架成功" : String.format("成功: %d, 失败: %d", successCount, failCount));
|
||||||
|
response.put("data", summary);
|
||||||
|
|
||||||
|
return R.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量上架商品异常", e);
|
||||||
|
return R.fail("批量上架失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下架商品
|
||||||
|
*/
|
||||||
|
@PostMapping("/batchDownShelf")
|
||||||
|
public R<?> batchDownShelf(@RequestBody @Validated BatchDownShelfRequest req) {
|
||||||
|
try {
|
||||||
|
ERPAccount account = resolveAccount(req.getAppid());
|
||||||
|
List<Long> productIds = req.getProductIds();
|
||||||
|
|
||||||
|
if (productIds == null || productIds.isEmpty()) {
|
||||||
|
return R.fail("商品ID列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HashMap<String, Object>> results = new ArrayList<>();
|
||||||
|
int successCount = 0;
|
||||||
|
int failCount = 0;
|
||||||
|
|
||||||
|
for (Long productId : productIds) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
result.put("productId", productId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProductDownShelfRequest downShelfRequest = new ProductDownShelfRequest(account);
|
||||||
|
downShelfRequest.setProductId(productId);
|
||||||
|
String resp = downShelfRequest.getResponseBody();
|
||||||
|
JSONObject jo = JSONObject.parseObject(resp);
|
||||||
|
|
||||||
|
if (jo != null && jo.getInteger("code") != null && jo.getInteger("code") == 0) {
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("msg", "下架成功");
|
||||||
|
result.put("response", jo);
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("msg", jo != null ? jo.getString("msg") : "下架失败");
|
||||||
|
result.put("response", jo);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量下架商品失败: productId={}", productId, e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("msg", "下架异常: " + e.getMessage());
|
||||||
|
result.put("response", null);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, Object> summary = new HashMap<>();
|
||||||
|
summary.put("total", productIds.size());
|
||||||
|
summary.put("success", successCount);
|
||||||
|
summary.put("fail", failCount);
|
||||||
|
summary.put("results", results);
|
||||||
|
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
response.put("code", failCount == 0 ? 0 : 500);
|
||||||
|
response.put("msg", failCount == 0 ? "全部下架成功" : String.format("成功: %d, 失败: %d", successCount, failCount));
|
||||||
|
response.put("data", summary);
|
||||||
|
|
||||||
|
return R.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量下架商品异常", e);
|
||||||
|
return R.fail("批量下架失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下架请求体
|
||||||
|
*/
|
||||||
|
public static class DownShelfRequest {
|
||||||
|
@NotNull
|
||||||
|
private Long productId;
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
public Long getProductId() { return productId; }
|
||||||
|
public void setProductId(Long productId) { this.productId = productId; }
|
||||||
|
public String getAppid() { return appid; }
|
||||||
|
public void setAppid(String appid) { this.appid = appid; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量上架请求体
|
||||||
|
*/
|
||||||
|
public static class BatchPublishRequest {
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 1, message = "商品ID列表不能为空")
|
||||||
|
private List<Long> productIds;
|
||||||
|
@NotBlank(message = "闲鱼会员名不能为空")
|
||||||
|
private String userName;
|
||||||
|
private String specifyPublishTime;
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
public List<Long> getProductIds() { return productIds; }
|
||||||
|
public void setProductIds(List<Long> productIds) { this.productIds = productIds; }
|
||||||
|
public String getUserName() { return userName; }
|
||||||
|
public void setUserName(String userName) { this.userName = userName; }
|
||||||
|
public String getSpecifyPublishTime() { return specifyPublishTime; }
|
||||||
|
public void setSpecifyPublishTime(String specifyPublishTime) { this.specifyPublishTime = specifyPublishTime; }
|
||||||
|
public String getAppid() { return appid; }
|
||||||
|
public void setAppid(String appid) { this.appid = appid; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下架请求体
|
||||||
|
*/
|
||||||
|
public static class BatchDownShelfRequest {
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 1, message = "商品ID列表不能为空")
|
||||||
|
private List<Long> productIds;
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
public List<Long> getProductIds() { return productIds; }
|
||||||
|
public void setProductIds(List<Long> productIds) { this.productIds = productIds; }
|
||||||
|
public String getAppid() { return appid; }
|
||||||
|
public void setAppid(String appid) { this.appid = appid; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpProduct;
|
||||||
|
import com.ruoyi.jarvis.service.IErpProductService;
|
||||||
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼商品Controller
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2024-01-01
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/erpProduct")
|
||||||
|
public class ErpProductController extends BaseController
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private IErpProductService erpProductService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品列表
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
startPage();
|
||||||
|
List<ErpProduct> list = erpProductService.selectErpProductList(erpProduct);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出闲鱼商品列表
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:export')")
|
||||||
|
@Log(title = "闲鱼商品", businessType = BusinessType.EXPORT)
|
||||||
|
@GetMapping("/export")
|
||||||
|
public AjaxResult export(ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
List<ErpProduct> list = erpProductService.selectErpProductList(erpProduct);
|
||||||
|
ExcelUtil<ErpProduct> util = new ExcelUtil<ErpProduct>(ErpProduct.class);
|
||||||
|
return util.exportExcel(list, "闲鱼商品数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取闲鱼商品详细信息
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:query')")
|
||||||
|
@GetMapping(value = "/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable("id") Long id)
|
||||||
|
{
|
||||||
|
return success(erpProductService.selectErpProductById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增闲鱼商品
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:add')")
|
||||||
|
@Log(title = "闲鱼商品", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult add(@RequestBody ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
return toAjax(erpProductService.insertErpProduct(erpProduct));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改闲鱼商品
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:edit')")
|
||||||
|
@Log(title = "闲鱼商品", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping
|
||||||
|
public AjaxResult edit(@RequestBody ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
return toAjax(erpProductService.updateErpProduct(erpProduct));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除闲鱼商品
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:remove')")
|
||||||
|
@Log(title = "闲鱼商品", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public AjaxResult remove(@PathVariable Long[] ids)
|
||||||
|
{
|
||||||
|
return toAjax(erpProductService.deleteErpProductByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从闲鱼ERP拉取商品列表并保存(单页,保留用于兼容)
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:pull')")
|
||||||
|
@Log(title = "拉取闲鱼商品", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping("/pull")
|
||||||
|
public AjaxResult pullProductList(
|
||||||
|
@RequestParam(required = false) String appid,
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||||
|
@RequestParam(defaultValue = "50") Integer pageSize,
|
||||||
|
@RequestParam(required = false) Integer productStatus)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
int count = erpProductService.pullAndSaveProductList(appid, pageNo, pageSize, productStatus);
|
||||||
|
if (count > 0) {
|
||||||
|
return success("成功拉取并保存 " + count + " 个商品");
|
||||||
|
} else {
|
||||||
|
String statusText = getStatusText(productStatus);
|
||||||
|
String message = "拉取完成,但没有获取到商品数据";
|
||||||
|
if (productStatus != null) {
|
||||||
|
message += "(筛选条件:状态=" + statusText + ")";
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态文本(用于提示信息)
|
||||||
|
*/
|
||||||
|
private String getStatusText(Integer status) {
|
||||||
|
if (status == null) {
|
||||||
|
return "全部";
|
||||||
|
}
|
||||||
|
switch (status) {
|
||||||
|
case -1:
|
||||||
|
return "删除";
|
||||||
|
case 21:
|
||||||
|
return "待发布";
|
||||||
|
case 22:
|
||||||
|
return "销售中";
|
||||||
|
case 23:
|
||||||
|
return "已售罄";
|
||||||
|
case 31:
|
||||||
|
return "手动下架";
|
||||||
|
case 33:
|
||||||
|
return "售出下架";
|
||||||
|
case 36:
|
||||||
|
return "自动下架";
|
||||||
|
default:
|
||||||
|
return String.valueOf(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量上架商品
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:publish')")
|
||||||
|
@Log(title = "批量上架商品", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/batchPublish")
|
||||||
|
public AjaxResult batchPublish(@RequestBody BatchOperationRequest request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return success("批量上架功能请调用 /erp/product/batchPublish 接口");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return error("批量上架失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下架商品
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:downShelf')")
|
||||||
|
@Log(title = "批量下架商品", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/batchDownShelf")
|
||||||
|
public AjaxResult batchDownShelf(@RequestBody BatchOperationRequest request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return success("批量下架功能请调用 /erp/product/batchDownShelf 接口");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return error("批量下架失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作请求体
|
||||||
|
*/
|
||||||
|
public static class BatchOperationRequest {
|
||||||
|
private java.util.List<Long> productIds;
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
public java.util.List<Long> getProductIds() {
|
||||||
|
return productIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductIds(java.util.List<Long> productIds) {
|
||||||
|
this.productIds = productIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppid() {
|
||||||
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppid(String appid) {
|
||||||
|
this.appid = appid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -21,13 +21,15 @@ public class InstructionController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行文本指令
|
* 执行文本指令(控制台入口,需要权限)
|
||||||
* body: { command: "京今日统计" }
|
* body: { command: "京今日统计", forceGenerate: false }
|
||||||
*/
|
*/
|
||||||
@PostMapping("/execute")
|
@PostMapping("/execute")
|
||||||
public AjaxResult execute(@RequestBody Map<String, String> body) {
|
public AjaxResult execute(@RequestBody Map<String, Object> body) {
|
||||||
String cmd = body != null ? body.get("command") : null;
|
String cmd = body != null ? (body.get("command") != null ? String.valueOf(body.get("command")) : null) : null;
|
||||||
java.util.List<String> result = instructionService.execute(cmd);
|
boolean forceGenerate = body != null && body.get("forceGenerate") != null && Boolean.parseBoolean(String.valueOf(body.get("forceGenerate")));
|
||||||
|
// 控制台入口,传递 isFromConsole=true,跳过订单查询校验
|
||||||
|
java.util.List<String> result = instructionService.execute(cmd, forceGenerate, true);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import com.ruoyi.jarvis.service.IJDOrderService;
|
|||||||
import com.ruoyi.jarvis.service.IOrderRowsService;
|
import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||||
import com.ruoyi.jarvis.service.IGiftCouponService;
|
import com.ruoyi.jarvis.service.IGiftCouponService;
|
||||||
import com.ruoyi.jarvis.domain.GiftCoupon;
|
import com.ruoyi.jarvis.domain.GiftCoupon;
|
||||||
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -37,11 +39,24 @@ public class JDOrderController extends BaseController {
|
|||||||
private final IJDOrderService jdOrderService;
|
private final IJDOrderService jdOrderService;
|
||||||
private final IOrderRowsService orderRowsService;
|
private final IOrderRowsService orderRowsService;
|
||||||
private final IGiftCouponService giftCouponService;
|
private final IGiftCouponService giftCouponService;
|
||||||
|
private final ISysConfigService sysConfigService;
|
||||||
|
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
||||||
|
private static final java.util.regex.Pattern URL_DETECT_PATTERN = java.util.regex.Pattern.compile(
|
||||||
|
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
||||||
|
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||||
|
private static final java.util.regex.Pattern UJD_LINK_PATTERN = java.util.regex.Pattern.compile(
|
||||||
|
"^https?://u\\.jd\\.com/[A-Za-z0-9]+[A-Za-z0-9_-]*$",
|
||||||
|
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||||
|
private static final java.util.regex.Pattern JINGFEN_LINK_PATTERN = java.util.regex.Pattern.compile(
|
||||||
|
"^https?://jingfen\\.jd\\.com/detail/[A-Za-z0-9]+\\.html$",
|
||||||
|
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService, IGiftCouponService giftCouponService) {
|
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
||||||
|
IGiftCouponService giftCouponService, ISysConfigService sysConfigService) {
|
||||||
this.jdOrderService = jdOrderService;
|
this.jdOrderService = jdOrderService;
|
||||||
this.orderRowsService = orderRowsService;
|
this.orderRowsService = orderRowsService;
|
||||||
this.giftCouponService = giftCouponService;
|
this.giftCouponService = giftCouponService;
|
||||||
|
this.sysConfigService = sysConfigService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static String skey = "2192057370ef8140c201079969c956a3";
|
private final static String skey = "2192057370ef8140c201079969c956a3";
|
||||||
@@ -134,10 +149,16 @@ public class JDOrderController extends BaseController {
|
|||||||
if (w instanceof JSONObject) {
|
if (w instanceof JSONObject) {
|
||||||
JSONObject wj = (JSONObject) w;
|
JSONObject wj = (JSONObject) w;
|
||||||
Object content = wj.get("content");
|
Object content = wj.get("content");
|
||||||
if (content instanceof String) {
|
if (!(content instanceof String)) {
|
||||||
String cleaned = stripUrls((String) content);
|
continue;
|
||||||
wj.put("content", cleaned);
|
|
||||||
}
|
}
|
||||||
|
String type = wj.getString("type");
|
||||||
|
if ("通用文案".equals(type)) {
|
||||||
|
// 通用文案需要保留链接,用于后续替换/复制
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String cleaned = stripUrls((String) content);
|
||||||
|
wj.put("content", cleaned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -543,8 +564,8 @@ public class JDOrderController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文本替换:根据选中的商品创建礼金并替换文案中的URL
|
* 文本替换:为每个URL单独查询商品并创建礼金券,然后替换
|
||||||
* 入参:{ content, selectedProducts: [{skuId/materialUrl, amount, quantity, owner, skuName, originalUrl}], ... }
|
* 入参:{ content, amount, quantity, owner }
|
||||||
* 返回:替换后的文本和礼金信息
|
* 返回:替换后的文本和礼金信息
|
||||||
*/
|
*/
|
||||||
@PostMapping("/replaceUrlsWithGiftCoupons")
|
@PostMapping("/replaceUrlsWithGiftCoupons")
|
||||||
@@ -555,115 +576,309 @@ public class JDOrderController extends BaseController {
|
|||||||
return AjaxResult.error("content is required");
|
return AjaxResult.error("content is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("文本URL替换请求 - content长度={}, 参数: materialUrl={}, skuId={}, amount={}, quantity={}, owner={}",
|
Object amountObj = body.get("amount");
|
||||||
content.length(), body.get("materialUrl"), body.get("skuId"), body.get("amount"),
|
Object quantityObj = body.get("quantity");
|
||||||
body.get("quantity"), body.get("owner"));
|
String owner = body.get("owner") != null ? String.valueOf(body.get("owner")) : "g";
|
||||||
|
|
||||||
|
double amount = amountObj != null ? Double.parseDouble(String.valueOf(amountObj)) : 1.8;
|
||||||
|
int quantity = quantityObj != null ? Integer.parseInt(String.valueOf(quantityObj)) : 12;
|
||||||
|
|
||||||
|
logger.info("文本URL替换请求 - content长度={}, amount={}, quantity={}, owner={}",
|
||||||
|
content.length(), amount, quantity, owner);
|
||||||
|
|
||||||
// 提取文本中的所有URL(京东链接)
|
// 提取文本中的所有URL(京东链接)
|
||||||
java.util.List<String> urls = new java.util.ArrayList<>();
|
java.util.List<UrlSegment> urlSegments = new java.util.ArrayList<>();
|
||||||
java.util.regex.Pattern urlPattern = java.util.regex.Pattern.compile(
|
java.util.regex.Matcher matcher = URL_DETECT_PATTERN.matcher(content);
|
||||||
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
|
||||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
|
||||||
java.util.regex.Matcher matcher = urlPattern.matcher(content);
|
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String url = matcher.group(0);
|
String segment = matcher.group(0);
|
||||||
if (url != null && !url.trim().isEmpty()) {
|
UrlSegment urlInfo = parseUrlSegment(segment);
|
||||||
urls.add(url.trim());
|
if (urlInfo != null) {
|
||||||
|
urlSegments.add(urlInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("文本URL替换 - 提取到{}个URL", urls.size());
|
logger.info("文本URL替换 - 提取到{}个URL", urlSegments.size());
|
||||||
|
|
||||||
if (urls.isEmpty()) {
|
if (urlSegments.isEmpty()) {
|
||||||
return AjaxResult.success(new JSONObject().fluentPut("replacedContent", content)
|
return AjaxResult.success(new JSONObject()
|
||||||
|
.fluentPut("replacedContent", content)
|
||||||
|
.fluentPut("originalContent", content)
|
||||||
|
.fluentPut("replacements", new JSONArray())
|
||||||
|
.fluentPut("totalUrls", 0)
|
||||||
|
.fluentPut("replacedCount", 0)
|
||||||
.fluentPut("message", "未找到URL,无需替换"));
|
.fluentPut("message", "未找到URL,无需替换"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量创建礼金券
|
// 为每个URL单独处理:查询商品 → 创建礼金 → 转链 → 替换
|
||||||
Map<String, Object> batchParams = new java.util.HashMap<>();
|
|
||||||
batchParams.put("materialUrl", body.get("materialUrl"));
|
|
||||||
batchParams.put("skuId", body.get("skuId"));
|
|
||||||
batchParams.put("amount", body.get("amount"));
|
|
||||||
batchParams.put("quantity", body.get("quantity"));
|
|
||||||
batchParams.put("batchSize", urls.size());
|
|
||||||
batchParams.put("owner", body.get("owner"));
|
|
||||||
batchParams.put("skuName", body.get("skuName"));
|
|
||||||
|
|
||||||
AjaxResult batchResult = batchCreateGiftCoupons(batchParams);
|
|
||||||
|
|
||||||
Integer code = (Integer) batchResult.get(AjaxResult.CODE_TAG);
|
|
||||||
if (code == null || code != 200) {
|
|
||||||
String msg = (String) batchResult.get(AjaxResult.MSG_TAG);
|
|
||||||
return AjaxResult.error("批量创建礼金失败: " + (msg != null ? msg : "未知错误"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析批量创建结果
|
|
||||||
Object data = batchResult.get(AjaxResult.DATA_TAG);
|
|
||||||
JSONObject batchData = null;
|
|
||||||
if (data instanceof JSONObject) {
|
|
||||||
batchData = (JSONObject) data;
|
|
||||||
} else if (data instanceof String) {
|
|
||||||
try {
|
|
||||||
batchData = JSON.parseObject((String) data);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("解析批量创建结果失败", e);
|
|
||||||
return AjaxResult.error("解析批量创建结果失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batchData == null || !batchData.containsKey("results")) {
|
|
||||||
return AjaxResult.error("批量创建结果格式错误");
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONArray results = batchData.getJSONArray("results");
|
|
||||||
if (results == null || results.size() != urls.size()) {
|
|
||||||
logger.warn("批量创建礼金数量不匹配 - 期望={}, 实际={}", urls.size(), results != null ? results.size() : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results == null) {
|
|
||||||
return AjaxResult.error("批量创建结果为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 替换文本中的URL
|
|
||||||
String replacedContent = content;
|
String replacedContent = content;
|
||||||
JSONArray replacementInfo = new JSONArray();
|
JSONArray replacementInfo = new JSONArray();
|
||||||
|
int successCount = 0;
|
||||||
int minSize = Math.min(urls.size(), results.size());
|
|
||||||
for (int i = 0; i < minSize; i++) {
|
|
||||||
String originalUrl = urls.get(i);
|
|
||||||
JSONObject result = results.getJSONObject(i);
|
|
||||||
|
|
||||||
String newUrl = null;
|
|
||||||
if (Boolean.TRUE.equals(result.getBoolean("success"))) {
|
|
||||||
newUrl = result.getString("shortURL");
|
|
||||||
if (newUrl == null || newUrl.trim().isEmpty()) {
|
|
||||||
newUrl = originalUrl; // 如果转链失败,保持原URL
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newUrl = originalUrl; // 创建失败,保持原URL
|
|
||||||
}
|
|
||||||
|
|
||||||
// 替换文本中的URL(支持多种格式)
|
for (int i = 0; i < urlSegments.size(); i++) {
|
||||||
replacedContent = replacedContent.replace(originalUrl, newUrl != null ? newUrl : originalUrl);
|
UrlSegment urlSegment = urlSegments.get(i);
|
||||||
|
logger.info("处理第{}/{}个URL: {}", i + 1, urlSegments.size(), urlSegment.urlPart);
|
||||||
JSONObject info = new JSONObject();
|
|
||||||
info.put("index", i + 1);
|
JSONObject product = null; // 在try外部声明,以便在catch中使用
|
||||||
info.put("originalUrl", originalUrl);
|
try {
|
||||||
info.put("newUrl", newUrl);
|
if (urlSegment.normalizedJdUrl == null) {
|
||||||
info.put("success", Boolean.TRUE.equals(result.getBoolean("success")));
|
logger.warn("URL{}不是京东推广链接,跳过: {}", i + 1, urlSegment.original);
|
||||||
info.put("giftCouponKey", result.getString("giftCouponKey"));
|
JSONObject info = new JSONObject();
|
||||||
replacementInfo.add(info);
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", "非京东推广链接或格式不支持");
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询该URL的商品信息
|
||||||
|
String queryUrl = requestUrl + "generatePromotionContent";
|
||||||
|
JSONObject queryParam = new JSONObject();
|
||||||
|
queryParam.put("skey", skey);
|
||||||
|
queryParam.put("promotionContent", urlSegment.normalizedJdUrl);
|
||||||
|
|
||||||
|
String queryResult = HttpUtils.sendJsonPost(queryUrl, queryParam.toJSONString());
|
||||||
|
logger.debug("商品查询响应: {}", queryResult);
|
||||||
|
|
||||||
|
Object parsed = JSON.parse(queryResult);
|
||||||
|
|
||||||
|
// 解析商品信息
|
||||||
|
if (parsed instanceof JSONArray) {
|
||||||
|
JSONArray arr = (JSONArray) parsed;
|
||||||
|
if (arr.size() > 0 && arr.get(0) instanceof JSONObject) {
|
||||||
|
product = arr.getJSONObject(0);
|
||||||
|
}
|
||||||
|
} else if (parsed instanceof JSONObject) {
|
||||||
|
JSONObject obj = (JSONObject) parsed;
|
||||||
|
if (obj.get("list") instanceof JSONArray) {
|
||||||
|
JSONArray list = obj.getJSONArray("list");
|
||||||
|
if (list.size() > 0) product = list.getJSONObject(0);
|
||||||
|
} else if (obj.get("data") instanceof JSONArray) {
|
||||||
|
JSONArray data = obj.getJSONArray("data");
|
||||||
|
if (data.size() > 0) product = data.getJSONObject(0);
|
||||||
|
} else if (obj.containsKey("materialUrl") || obj.containsKey("skuName")) {
|
||||||
|
product = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product == null || product.containsKey("error")) {
|
||||||
|
String errorMsg = "商品信息查询失败";
|
||||||
|
if (product != null) {
|
||||||
|
String apiError = product.getString("error");
|
||||||
|
String apiMessage = product.getString("message");
|
||||||
|
if (apiMessage != null && !apiMessage.trim().isEmpty()) {
|
||||||
|
errorMsg = apiMessage;
|
||||||
|
} else if (apiError != null && !apiError.trim().isEmpty()) {
|
||||||
|
errorMsg = apiError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.warn("URL{}商品信息查询失败: {}", urlSegment.urlPart, errorMsg);
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", errorMsg);
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 为该商品创建礼金券
|
||||||
|
String createUrl = requestUrl + "createGiftCoupon";
|
||||||
|
JSONObject createParam = new JSONObject();
|
||||||
|
createParam.put("skey", skey);
|
||||||
|
createParam.put("amount", amount);
|
||||||
|
createParam.put("quantity", quantity);
|
||||||
|
createParam.put("skuName", product.getString("skuName"));
|
||||||
|
|
||||||
|
// 处理owner字段:p -> pop, g -> g
|
||||||
|
String productOwner = product.getString("owner");
|
||||||
|
if (productOwner == null || productOwner.trim().isEmpty()) {
|
||||||
|
productOwner = owner; // 使用用户选择的
|
||||||
|
} else if ("p".equalsIgnoreCase(productOwner)) {
|
||||||
|
productOwner = "pop";
|
||||||
|
}
|
||||||
|
createParam.put("owner", productOwner);
|
||||||
|
|
||||||
|
// 设置skuId或materialUrl(注意:POP商品优先使用materialUrl或oriItemId)
|
||||||
|
String skuId = product.getString("skuId");
|
||||||
|
String materialUrl = product.getString("materialUrl");
|
||||||
|
String oriItemId = product.getString("oriItemId");
|
||||||
|
|
||||||
|
// 判断skuId是否有效(排除字符串"null")
|
||||||
|
boolean hasValidSkuId = skuId != null && !skuId.trim().isEmpty() &&
|
||||||
|
!"null".equalsIgnoreCase(skuId) &&
|
||||||
|
!"undefined".equalsIgnoreCase(skuId);
|
||||||
|
|
||||||
|
// 判断materialUrl是否有效
|
||||||
|
boolean hasValidMaterialUrl = materialUrl != null && !materialUrl.trim().isEmpty() &&
|
||||||
|
!"null".equalsIgnoreCase(materialUrl);
|
||||||
|
|
||||||
|
// 判断oriItemId是否有效
|
||||||
|
boolean hasValidOriItemId = oriItemId != null && !oriItemId.trim().isEmpty() &&
|
||||||
|
!"null".equalsIgnoreCase(oriItemId);
|
||||||
|
|
||||||
|
if ("pop".equalsIgnoreCase(productOwner)) {
|
||||||
|
// POP商品优先使用oriItemId或materialUrl
|
||||||
|
if (hasValidOriItemId) {
|
||||||
|
createParam.put("materialUrl", oriItemId);
|
||||||
|
logger.info("POP商品使用oriItemId: {}", oriItemId);
|
||||||
|
} else if (hasValidMaterialUrl) {
|
||||||
|
createParam.put("materialUrl", materialUrl);
|
||||||
|
logger.info("POP商品使用materialUrl: {}", materialUrl);
|
||||||
|
} else if (hasValidSkuId) {
|
||||||
|
createParam.put("skuId", skuId);
|
||||||
|
logger.info("POP商品使用skuId: {}", skuId);
|
||||||
|
} else {
|
||||||
|
logger.warn("POP商品{}缺少有效的oriItemId/materialUrl/skuId", product.getString("skuName"));
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", "POP商品信息不完整");
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 自营商品优先使用skuId
|
||||||
|
if (hasValidSkuId) {
|
||||||
|
createParam.put("skuId", skuId);
|
||||||
|
logger.info("自营商品使用skuId: {}", skuId);
|
||||||
|
} else if (hasValidMaterialUrl) {
|
||||||
|
createParam.put("materialUrl", materialUrl);
|
||||||
|
logger.info("自营商品使用materialUrl: {}", materialUrl);
|
||||||
|
} else {
|
||||||
|
logger.warn("自营商品{}缺少有效的skuId/materialUrl", product.getString("skuName"));
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", "自营商品信息不完整");
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String createResult = HttpUtils.sendJsonPost(createUrl, createParam.toJSONString());
|
||||||
|
logger.debug("礼金创建响应: {}", createResult);
|
||||||
|
|
||||||
|
JSONObject createData = JSON.parseObject(createResult);
|
||||||
|
if (createData == null || createData.containsKey("error") ||
|
||||||
|
createData.getString("giftCouponKey") == null ||
|
||||||
|
createData.getString("giftCouponKey").trim().isEmpty()) {
|
||||||
|
String errorMsg = "礼金创建失败";
|
||||||
|
if (createData != null) {
|
||||||
|
String apiError = createData.getString("error");
|
||||||
|
String apiMessage = createData.getString("message");
|
||||||
|
if (apiMessage != null && !apiMessage.trim().isEmpty()) {
|
||||||
|
errorMsg = apiMessage;
|
||||||
|
} else if (apiError != null && !apiError.trim().isEmpty()) {
|
||||||
|
errorMsg = apiError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.warn("URL{}礼金创建失败: {}", urlSegment.urlPart, errorMsg);
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", errorMsg);
|
||||||
|
if (product != null && product.getString("skuName") != null) {
|
||||||
|
info.put("skuName", product.getString("skuName"));
|
||||||
|
}
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String giftCouponKey = createData.getString("giftCouponKey");
|
||||||
|
|
||||||
|
// 3. 转链(带礼金)
|
||||||
|
String transferUrl = requestUrl + "transfer";
|
||||||
|
JSONObject transferParam = new JSONObject();
|
||||||
|
transferParam.put("skey", skey);
|
||||||
|
transferParam.put("materialUrl", urlSegment.normalizedJdUrl);
|
||||||
|
transferParam.put("giftCouponKey", giftCouponKey);
|
||||||
|
|
||||||
|
String transferResult = HttpUtils.sendJsonPost(transferUrl, transferParam.toJSONString());
|
||||||
|
logger.debug("转链响应: {}", transferResult);
|
||||||
|
|
||||||
|
JSONObject transferData = JSON.parseObject(transferResult);
|
||||||
|
if (transferData == null || transferData.containsKey("error") ||
|
||||||
|
transferData.getString("shortURL") == null ||
|
||||||
|
transferData.getString("shortURL").trim().isEmpty()) {
|
||||||
|
String errorMsg = "转链失败";
|
||||||
|
if (transferData != null) {
|
||||||
|
String apiError = transferData.getString("error");
|
||||||
|
String apiMessage = transferData.getString("message");
|
||||||
|
if (apiMessage != null && !apiMessage.trim().isEmpty()) {
|
||||||
|
errorMsg = apiMessage;
|
||||||
|
} else if (apiError != null && !apiError.trim().isEmpty()) {
|
||||||
|
errorMsg = apiError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.warn("URL{}转链失败: {}", urlSegment.urlPart, errorMsg);
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("giftCouponKey", giftCouponKey);
|
||||||
|
info.put("error", errorMsg);
|
||||||
|
if (product != null && product.getString("skuName") != null) {
|
||||||
|
info.put("skuName", product.getString("skuName"));
|
||||||
|
}
|
||||||
|
replacementInfo.add(info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String shortURL = transferData.getString("shortURL");
|
||||||
|
|
||||||
|
// 4. 替换文本中的URL
|
||||||
|
replacedContent = replaceUrlInContent(replacedContent, urlSegment, shortURL);
|
||||||
|
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", shortURL);
|
||||||
|
info.put("success", true);
|
||||||
|
info.put("giftCouponKey", giftCouponKey);
|
||||||
|
info.put("skuName", product.getString("skuName"));
|
||||||
|
replacementInfo.add(info);
|
||||||
|
|
||||||
|
successCount++;
|
||||||
|
logger.info("URL{}处理成功: {} -> {}", i + 1, urlSegment.urlPart, shortURL);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("处理URL{}失败: {}", urlSegment.urlPart, e.getMessage(), e);
|
||||||
|
String errorMsg = e.getMessage();
|
||||||
|
if (errorMsg == null || errorMsg.trim().isEmpty()) {
|
||||||
|
errorMsg = "处理失败: " + e.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("index", i + 1);
|
||||||
|
info.put("originalUrl", urlSegment.urlPart);
|
||||||
|
info.put("newUrl", urlSegment.urlPart);
|
||||||
|
info.put("success", false);
|
||||||
|
info.put("error", errorMsg);
|
||||||
|
if (product != null && product.getString("skuName") != null) {
|
||||||
|
info.put("skuName", product.getString("skuName"));
|
||||||
|
}
|
||||||
|
replacementInfo.add(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("文本URL替换完成 - 替换了{}个URL", minSize);
|
logger.info("文本URL替换完成 - 成功{}/{}", successCount, urlSegments.size());
|
||||||
|
|
||||||
JSONObject response = new JSONObject();
|
JSONObject response = new JSONObject();
|
||||||
response.put("replacedContent", replacedContent);
|
response.put("replacedContent", replacedContent);
|
||||||
response.put("originalContent", content);
|
response.put("originalContent", content);
|
||||||
response.put("replacements", replacementInfo);
|
response.put("replacements", replacementInfo);
|
||||||
response.put("totalUrls", urls.size());
|
response.put("totalUrls", urlSegments.size());
|
||||||
response.put("replacedCount", minSize);
|
response.put("replacedCount", successCount);
|
||||||
|
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -702,9 +917,7 @@ public class JDOrderController extends BaseController {
|
|||||||
|
|
||||||
// 检查分销标识
|
// 检查分销标识
|
||||||
String distributionMark = order.getDistributionMark();
|
String distributionMark = order.getDistributionMark();
|
||||||
if (distributionMark == null || (!distributionMark.equals("F") && !distributionMark.equals("PDD"))) {
|
|
||||||
return AjaxResult.error("该订单的分销标识不是F或PDD,无需处理。当前分销标识: " + distributionMark);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查物流链接
|
// 检查物流链接
|
||||||
String logisticsLink = order.getLogisticsLink();
|
String logisticsLink = order.getLogisticsLink();
|
||||||
@@ -810,6 +1023,43 @@ public class JDOrderController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分销标识获取接收人列表
|
||||||
|
* 从系统配置中读取,配置键名格式:logistics.push.touser.{分销标识}
|
||||||
|
* 配置值格式:接收人1,接收人2,接收人3(逗号分隔)
|
||||||
|
*
|
||||||
|
* @param distributionMark 分销标识
|
||||||
|
* @return 接收人列表(逗号分隔),如果未配置则返回null
|
||||||
|
*/
|
||||||
|
private String getTouserByDistributionMark(String distributionMark) {
|
||||||
|
if (!StringUtils.hasText(distributionMark)) {
|
||||||
|
logger.warn("分销标识为空,无法获取接收人配置");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建配置键名
|
||||||
|
String configKey = CONFIG_KEY_PREFIX + distributionMark.trim();
|
||||||
|
|
||||||
|
// 从系统配置中获取接收人列表
|
||||||
|
String configValue = sysConfigService.selectConfigByKey(configKey);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(configValue)) {
|
||||||
|
// 清理配置值(去除空格)
|
||||||
|
String touser = configValue.trim().replaceAll(",\\s+", ",");
|
||||||
|
logger.info("从配置获取接收人列表 - 分销标识: {}, 配置键: {}, 接收人: {}",
|
||||||
|
distributionMark, configKey, touser);
|
||||||
|
return touser;
|
||||||
|
} else {
|
||||||
|
logger.debug("未找到接收人配置 - 分销标识: {}, 配置键: {}", distributionMark, configKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取接收人配置失败 - 分销标识: {}, 错误: {}", distributionMark, e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用企业应用推送逻辑
|
* 调用企业应用推送逻辑
|
||||||
* @param order 订单信息
|
* @param order 订单信息
|
||||||
@@ -838,7 +1088,7 @@ public class JDOrderController extends BaseController {
|
|||||||
// 收货地址
|
// 收货地址
|
||||||
pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n");
|
pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n");
|
||||||
// 运单号
|
// 运单号
|
||||||
pushContent.append("运单号:").append(waybillNo).append("\n");
|
pushContent.append("运单号:").append("\n").append("\n").append("\n").append("\n").append(waybillNo).append("\n");
|
||||||
|
|
||||||
// 调用企业微信推送接口(参考WxtsUtil的实现)
|
// 调用企业微信推送接口(参考WxtsUtil的实现)
|
||||||
String pushUrl = "https://wxts.van333.cn/wx/send/pdd";
|
String pushUrl = "https://wxts.van333.cn/wx/send/pdd";
|
||||||
@@ -848,8 +1098,23 @@ public class JDOrderController extends BaseController {
|
|||||||
String content = pushContent.toString();
|
String content = pushContent.toString();
|
||||||
pushParam.put("text", content);
|
pushParam.put("text", content);
|
||||||
|
|
||||||
|
// 根据分销标识获取接收人列表
|
||||||
|
String touser = getTouserByDistributionMark(distributionMark);
|
||||||
|
if (StringUtils.hasText(touser)) {
|
||||||
|
pushParam.put("touser", touser);
|
||||||
|
logger.info("企业微信推送设置接收人 - 订单ID: {}, 分销标识: {}, 接收人: {}",
|
||||||
|
order.getId(), distributionMark, touser);
|
||||||
|
} else {
|
||||||
|
logger.warn("未找到分销标识对应的接收人配置 - 订单ID: {}, 分销标识: {}",
|
||||||
|
order.getId(), distributionMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录完整的推送参数(用于调试)
|
||||||
|
String jsonBody = pushParam.toJSONString();
|
||||||
|
logger.info("企业微信推送完整参数 - 订单ID: {}, JSON: {}", order.getId(), jsonBody);
|
||||||
|
|
||||||
// 使用支持自定义header的HTTP请求
|
// 使用支持自定义header的HTTP请求
|
||||||
String pushResult = sendPostWithHeaders(pushUrl, pushParam.toJSONString(), token);
|
String pushResult = sendPostWithHeaders(pushUrl, jsonBody, token);
|
||||||
logger.info("企业应用推送调用结果 - 订单ID: {}, waybill_no: {}, 推送结果: {}",
|
logger.info("企业应用推送调用结果 - 订单ID: {}, waybill_no: {}, 推送结果: {}",
|
||||||
order.getId(), waybillNo, pushResult);
|
order.getId(), waybillNo, pushResult);
|
||||||
|
|
||||||
@@ -926,4 +1191,80 @@ public class JDOrderController extends BaseController {
|
|||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class UrlSegment {
|
||||||
|
private final String original;
|
||||||
|
private final String urlPart;
|
||||||
|
private final String suffix;
|
||||||
|
private final String normalizedJdUrl;
|
||||||
|
|
||||||
|
private UrlSegment(String original, String urlPart, String suffix, String normalizedJdUrl) {
|
||||||
|
this.original = original;
|
||||||
|
this.urlPart = urlPart;
|
||||||
|
this.suffix = suffix;
|
||||||
|
this.normalizedJdUrl = normalizedJdUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UrlSegment parseUrlSegment(String segment) {
|
||||||
|
if (segment == null || segment.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String original = segment.trim();
|
||||||
|
int urlLength = calculateUrlLength(original);
|
||||||
|
if (urlLength <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String urlPart = original.substring(0, urlLength);
|
||||||
|
String suffix = original.substring(urlLength);
|
||||||
|
String normalized = normalizeJdUrl(urlPart);
|
||||||
|
return new UrlSegment(original, urlPart, suffix, normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateUrlLength(String text) {
|
||||||
|
int length = 0;
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
char c = text.charAt(i);
|
||||||
|
if (isAllowedUrlChar(c)) {
|
||||||
|
length++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAllowedUrlChar(char c) {
|
||||||
|
if (c <= 0x7F) {
|
||||||
|
return Character.isLetterOrDigit(c) || "-._~:/?#[]@!$&'()*+,;=%".indexOf(c) >= 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeJdUrl(String urlPart) {
|
||||||
|
if (urlPart == null || urlPart.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = urlPart;
|
||||||
|
if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) {
|
||||||
|
normalized = "https://" + normalized;
|
||||||
|
}
|
||||||
|
if (UJD_LINK_PATTERN.matcher(normalized).matches() || JINGFEN_LINK_PATTERN.matcher(normalized).matches()) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String replaceUrlInContent(String content, UrlSegment segment, String newUrl) {
|
||||||
|
if (content == null || segment == null || newUrl == null || newUrl.trim().isEmpty()) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
String suffix = segment.suffix != null ? segment.suffix : "";
|
||||||
|
String replacementWithSuffix = newUrl + suffix;
|
||||||
|
String result = content.replace(segment.original, replacementWithSuffix);
|
||||||
|
if (result.equals(content) && !segment.original.equals(segment.urlPart)) {
|
||||||
|
result = result.replace(segment.urlPart, newUrl);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
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.enums.BusinessType;
|
||||||
|
import com.ruoyi.jarvis.service.ISocialMediaService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小红书/抖音内容生成Controller
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-XX
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/social-media")
|
||||||
|
public class SocialMediaController extends BaseController
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private ISocialMediaService socialMediaService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取关键词
|
||||||
|
*/
|
||||||
|
@PostMapping("/extract-keywords")
|
||||||
|
public AjaxResult extractKeywords(@RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String productName = (String) request.get("productName");
|
||||||
|
if (productName == null || productName.trim().isEmpty()) {
|
||||||
|
return AjaxResult.error("商品名称不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = socialMediaService.extractKeywords(productName);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("提取关键词失败", e);
|
||||||
|
return AjaxResult.error("提取关键词失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成文案
|
||||||
|
*/
|
||||||
|
@PostMapping("/generate-content")
|
||||||
|
public AjaxResult generateContent(@RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String productName = (String) request.get("productName");
|
||||||
|
if (productName == null || productName.trim().isEmpty()) {
|
||||||
|
return AjaxResult.error("商品名称不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object originalPriceObj = request.get("originalPrice");
|
||||||
|
Object finalPriceObj = request.get("finalPrice");
|
||||||
|
String keywords = (String) request.get("keywords");
|
||||||
|
String style = (String) request.getOrDefault("style", "both");
|
||||||
|
|
||||||
|
Map<String, Object> result = socialMediaService.generateContent(
|
||||||
|
productName, originalPriceObj, finalPriceObj, keywords, style
|
||||||
|
);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("生成文案失败", e);
|
||||||
|
return AjaxResult.error("生成文案失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键生成完整内容(关键词 + 文案 + 图片)
|
||||||
|
*/
|
||||||
|
@Log(title = "小红书/抖音内容生成", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/generate-complete")
|
||||||
|
public AjaxResult generateComplete(@RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String productImageUrl = (String) request.get("productImageUrl");
|
||||||
|
String productName = (String) request.get("productName");
|
||||||
|
if (productName == null || productName.trim().isEmpty()) {
|
||||||
|
return AjaxResult.error("商品名称不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object originalPriceObj = request.get("originalPrice");
|
||||||
|
Object finalPriceObj = request.get("finalPrice");
|
||||||
|
String style = (String) request.getOrDefault("style", "both");
|
||||||
|
|
||||||
|
Map<String, Object> result = socialMediaService.generateCompleteContent(
|
||||||
|
productImageUrl, productName, originalPriceObj, finalPriceObj, style
|
||||||
|
);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("生成完整内容失败", e);
|
||||||
|
return AjaxResult.error("生成完整内容失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取提示词模板列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/prompt/list")
|
||||||
|
public AjaxResult listPromptTemplates()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.listPromptTemplates();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取提示词模板列表失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个提示词模板
|
||||||
|
*/
|
||||||
|
@GetMapping("/prompt/{key}")
|
||||||
|
public AjaxResult getPromptTemplate(@PathVariable String key)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.getPromptTemplate(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取提示词模板失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存提示词模板
|
||||||
|
*/
|
||||||
|
@Log(title = "保存提示词模板", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/prompt/save")
|
||||||
|
public AjaxResult savePromptTemplate(@RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.savePromptTemplate(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("保存提示词模板失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除提示词模板(恢复默认)
|
||||||
|
*/
|
||||||
|
@Log(title = "删除提示词模板", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/prompt/{key}")
|
||||||
|
public AjaxResult deletePromptTemplate(@PathVariable String key)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.deletePromptTemplate(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除提示词模板失败", e);
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -485,7 +485,7 @@ public class TencentDocController extends BaseController {
|
|||||||
// 2. 检查订单是否已推送(防止重复推送)
|
// 2. 检查订单是否已推送(防止重复推送)
|
||||||
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(thirdPartyOrderNo);
|
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(thirdPartyOrderNo);
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
logOperation(null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "订单不存在");
|
"FAILED", "订单不存在");
|
||||||
return AjaxResult.error("订单不存在:" + thirdPartyOrderNo);
|
return AjaxResult.error("订单不存在:" + thirdPartyOrderNo);
|
||||||
}
|
}
|
||||||
@@ -494,7 +494,7 @@ public class TencentDocController extends BaseController {
|
|||||||
if (order.getTencentDocPushed() != null && order.getTencentDocPushed() == 1 && !forceRePush) {
|
if (order.getTencentDocPushed() != null && order.getTencentDocPushed() == 1 && !forceRePush) {
|
||||||
String pushTimeStr = order.getTencentDocPushTime() != null ?
|
String pushTimeStr = order.getTencentDocPushTime() != null ?
|
||||||
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getTencentDocPushTime()) : "未知";
|
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getTencentDocPushTime()) : "未知";
|
||||||
logOperation(null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"SKIPPED", String.format("订单已推送,推送时间: %s", pushTimeStr));
|
"SKIPPED", String.format("订单已推送,推送时间: %s", pushTimeStr));
|
||||||
return AjaxResult.error(String.format("该订单已推送到腾讯文档(推送时间:%s),请勿重复操作!如需重新推送,请使用强制推送功能。", pushTimeStr));
|
return AjaxResult.error(String.format("该订单已推送到腾讯文档(推送时间:%s),请勿重复操作!如需重新推送,请使用强制推送功能。", pushTimeStr));
|
||||||
}
|
}
|
||||||
@@ -504,7 +504,7 @@ public class TencentDocController extends BaseController {
|
|||||||
try {
|
try {
|
||||||
accessToken = tencentDocTokenService.getValidAccessToken();
|
accessToken = tencentDocTokenService.getValidAccessToken();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logOperation(null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "访问令牌无效: " + e.getMessage());
|
"FAILED", "访问令牌无效: " + e.getMessage());
|
||||||
return AjaxResult.error("访问令牌无效,请先完成授权");
|
return AjaxResult.error("访问令牌无效,请先完成授权");
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileId == null || fileId.isEmpty() || sheetId == null || sheetId.isEmpty()) {
|
if (fileId == null || fileId.isEmpty() || sheetId == null || sheetId.isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "文档配置不完整");
|
"FAILED", "文档配置不完整");
|
||||||
return AjaxResult.error("文档配置不完整,请先完成配置");
|
return AjaxResult.error("文档配置不完整,请先完成配置");
|
||||||
}
|
}
|
||||||
@@ -544,7 +544,7 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
if (!lockAcquired) {
|
if (!lockAcquired) {
|
||||||
log.warn("获取锁失败,该订单正在被其他请求处理:{}", thirdPartyOrderNo);
|
log.warn("获取锁失败,该订单正在被其他请求处理:{}", thirdPartyOrderNo);
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "获取分布式锁失败,该订单正在处理中");
|
"FAILED", "获取分布式锁失败,该订单正在处理中");
|
||||||
return AjaxResult.error("该订单正在处理中,请稍后再试");
|
return AjaxResult.error("该订单正在处理中,请稍后再试");
|
||||||
}
|
}
|
||||||
@@ -558,7 +558,7 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange);
|
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange);
|
||||||
if (headerData == null || !headerData.containsKey("gridData")) {
|
if (headerData == null || !headerData.containsKey("gridData")) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "无法读取表头数据");
|
"FAILED", "无法读取表头数据");
|
||||||
return AjaxResult.error("无法读取表头数据");
|
return AjaxResult.error("无法读取表头数据");
|
||||||
}
|
}
|
||||||
@@ -567,7 +567,7 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONObject gridData = headerData.getJSONObject("gridData");
|
JSONObject gridData = headerData.getJSONObject("gridData");
|
||||||
JSONArray rows = gridData.getJSONArray("rows");
|
JSONArray rows = gridData.getJSONArray("rows");
|
||||||
if (rows == null || rows.isEmpty()) {
|
if (rows == null || rows.isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "表头数据为空");
|
"FAILED", "表头数据为空");
|
||||||
return AjaxResult.error("表头数据为空");
|
return AjaxResult.error("表头数据为空");
|
||||||
}
|
}
|
||||||
@@ -593,7 +593,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (orderNoColumn == -1 || logisticsColumn == -1) {
|
if (orderNoColumn == -1 || logisticsColumn == -1) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "未找到'单号'或'物流'列");
|
"FAILED", "未找到'单号'或'物流'列");
|
||||||
return AjaxResult.error("未找到'单号'或'物流'列,请检查表头配置");
|
return AjaxResult.error("未找到'单号'或'物流'列,请检查表头配置");
|
||||||
}
|
}
|
||||||
@@ -605,7 +605,7 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONObject data = tencentDocService.readSheetData(accessToken, fileId, sheetId, dataRange);
|
JSONObject data = tencentDocService.readSheetData(accessToken, fileId, sheetId, dataRange);
|
||||||
|
|
||||||
if (data == null || !data.containsKey("gridData")) {
|
if (data == null || !data.containsKey("gridData")) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "无法读取数据区域");
|
"FAILED", "无法读取数据区域");
|
||||||
return AjaxResult.error("无法读取数据区域");
|
return AjaxResult.error("无法读取数据区域");
|
||||||
}
|
}
|
||||||
@@ -614,7 +614,7 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONArray dataRows = dataGridData.getJSONArray("rows");
|
JSONArray dataRows = dataGridData.getJSONArray("rows");
|
||||||
|
|
||||||
if (dataRows == null || dataRows.isEmpty()) {
|
if (dataRows == null || dataRows.isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "数据区域为空");
|
"FAILED", "数据区域为空");
|
||||||
return AjaxResult.error("数据区域为空");
|
return AjaxResult.error("数据区域为空");
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetRow == -1) {
|
if (targetRow == -1) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "未找到单号");
|
"FAILED", "未找到单号");
|
||||||
return AjaxResult.error("未找到单号: " + thirdPartyOrderNo);
|
return AjaxResult.error("未找到单号: " + thirdPartyOrderNo);
|
||||||
}
|
}
|
||||||
@@ -650,7 +650,7 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONObject verifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, verifyRange);
|
JSONObject verifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, verifyRange);
|
||||||
|
|
||||||
if (verifyData == null || !verifyData.containsKey("gridData")) {
|
if (verifyData == null || !verifyData.containsKey("gridData")) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"FAILED", "验证读取失败");
|
"FAILED", "验证读取失败");
|
||||||
return AjaxResult.error("验证读取失败");
|
return AjaxResult.error("验证读取失败");
|
||||||
}
|
}
|
||||||
@@ -659,7 +659,7 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONArray verifyRows = verifyGridData.getJSONArray("rows");
|
JSONArray verifyRows = verifyGridData.getJSONArray("rows");
|
||||||
|
|
||||||
if (verifyRows == null || verifyRows.isEmpty()) {
|
if (verifyRows == null || verifyRows.isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"FAILED", "验证行数据为空");
|
"FAILED", "验证行数据为空");
|
||||||
return AjaxResult.error("验证行数据为空");
|
return AjaxResult.error("验证行数据为空");
|
||||||
}
|
}
|
||||||
@@ -669,7 +669,7 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
// 验证单号是否仍然匹配
|
// 验证单号是否仍然匹配
|
||||||
if (verifyCells == null || verifyCells.size() <= orderNoColumn) {
|
if (verifyCells == null || verifyCells.size() <= orderNoColumn) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"SKIPPED", "验证时单号列为空,行已变化");
|
"SKIPPED", "验证时单号列为空,行已变化");
|
||||||
return AjaxResult.error("验证失败:行数据已变化,单号列为空");
|
return AjaxResult.error("验证失败:行数据已变化,单号列为空");
|
||||||
}
|
}
|
||||||
@@ -681,7 +681,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!thirdPartyOrderNo.equals(verifyOrderNo)) {
|
if (!thirdPartyOrderNo.equals(verifyOrderNo)) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"SKIPPED", String.format("验证失败:单号不匹配,期望 %s,实际 %s", thirdPartyOrderNo, verifyOrderNo));
|
"SKIPPED", String.format("验证失败:单号不匹配,期望 %s,实际 %s", thirdPartyOrderNo, verifyOrderNo));
|
||||||
return AjaxResult.error("验证失败:单号不匹配,行数据已变化");
|
return AjaxResult.error("验证失败:单号不匹配,行数据已变化");
|
||||||
}
|
}
|
||||||
@@ -692,7 +692,7 @@ public class TencentDocController extends BaseController {
|
|||||||
if (logisticsCell.containsKey("cellValue")) {
|
if (logisticsCell.containsKey("cellValue")) {
|
||||||
String existingLogistics = logisticsCell.getJSONObject("cellValue").getString("text");
|
String existingLogistics = logisticsCell.getJSONObject("cellValue").getString("text");
|
||||||
if (existingLogistics != null && !existingLogistics.trim().isEmpty()) {
|
if (existingLogistics != null && !existingLogistics.trim().isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"SKIPPED", String.format("物流链接列已有值:%s", existingLogistics));
|
"SKIPPED", String.format("物流链接列已有值:%s", existingLogistics));
|
||||||
return AjaxResult.error(String.format("该订单物流链接已存在:%s", existingLogistics));
|
return AjaxResult.error(String.format("该订单物流链接已存在:%s", existingLogistics));
|
||||||
}
|
}
|
||||||
@@ -705,13 +705,13 @@ public class TencentDocController extends BaseController {
|
|||||||
JSONObject userInfo = com.ruoyi.jarvis.util.TencentDocApiUtil.getUserInfo(accessToken);
|
JSONObject userInfo = com.ruoyi.jarvis.util.TencentDocApiUtil.getUserInfo(accessToken);
|
||||||
JSONObject userData = userInfo.getJSONObject("data");
|
JSONObject userData = userInfo.getJSONObject("data");
|
||||||
if (userData == null) {
|
if (userData == null) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"FAILED", "无法获取用户数据");
|
"FAILED", "无法获取用户数据");
|
||||||
return AjaxResult.error("无法获取用户数据");
|
return AjaxResult.error("无法获取用户数据");
|
||||||
}
|
}
|
||||||
String openId = userData.getString("openID");
|
String openId = userData.getString("openID");
|
||||||
if (openId == null || openId.isEmpty()) {
|
if (openId == null || openId.isEmpty()) {
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"FAILED", "无法获取Open-Id");
|
"FAILED", "无法获取Open-Id");
|
||||||
return AjaxResult.error("无法获取Open-Id");
|
return AjaxResult.error("无法获取Open-Id");
|
||||||
}
|
}
|
||||||
@@ -729,7 +729,7 @@ public class TencentDocController extends BaseController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 12. 记录成功日志
|
// 12. 记录成功日志
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, targetRow, logisticsLink,
|
||||||
"SUCCESS", null);
|
"SUCCESS", null);
|
||||||
|
|
||||||
log.info("✓ 物流链接写入成功 - 单号: {}, 行: {}, 链接: {}", thirdPartyOrderNo, targetRow, logisticsLink);
|
log.info("✓ 物流链接写入成功 - 单号: {}, 行: {}, 链接: {}", thirdPartyOrderNo, targetRow, logisticsLink);
|
||||||
@@ -757,7 +757,7 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("填充物流链接失败", e);
|
log.error("填充物流链接失败", e);
|
||||||
logOperation(fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, null,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, null,
|
||||||
"FAILED", "异常: " + e.getMessage());
|
"FAILED", "异常: " + e.getMessage());
|
||||||
return AjaxResult.error("填充物流链接失败: " + e.getMessage());
|
return AjaxResult.error("填充物流链接失败: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
@@ -776,11 +776,12 @@ public class TencentDocController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 记录操作日志到数据库
|
* 记录操作日志到数据库
|
||||||
*/
|
*/
|
||||||
private void logOperation(String fileId, String sheetId, String operationType,
|
private void logOperation(String batchId, String fileId, String sheetId, String operationType,
|
||||||
String orderNo, Integer targetRow, String logisticsLink,
|
String orderNo, Integer targetRow, String logisticsLink,
|
||||||
String status, String errorMessage) {
|
String status, String errorMessage) {
|
||||||
try {
|
try {
|
||||||
com.ruoyi.jarvis.domain.TencentDocOperationLog log = new com.ruoyi.jarvis.domain.TencentDocOperationLog();
|
com.ruoyi.jarvis.domain.TencentDocOperationLog log = new com.ruoyi.jarvis.domain.TencentDocOperationLog();
|
||||||
|
log.setBatchId(batchId);
|
||||||
log.setFileId(fileId);
|
log.setFileId(fileId);
|
||||||
log.setSheetId(sheetId);
|
log.setSheetId(sheetId);
|
||||||
log.setOperationType(operationType);
|
log.setOperationType(operationType);
|
||||||
@@ -882,6 +883,9 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从参数获取批次ID(如果是批量调用会传入)
|
||||||
|
String batchId = params.get("batchId") != null ? String.valueOf(params.get("batchId")) : null;
|
||||||
|
|
||||||
// 从参数或配置中获取文档信息
|
// 从参数或配置中获取文档信息
|
||||||
String fileId = (String) params.get("fileId");
|
String fileId = (String) params.get("fileId");
|
||||||
String sheetId = (String) params.get("sheetId");
|
String sheetId = (String) params.get("sheetId");
|
||||||
@@ -1194,7 +1198,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录同步日志
|
// 记录同步日志
|
||||||
logOperation(fileId, sheetId, "BATCH_SYNC", orderNo, excelRow, existingLogisticsLink,
|
logOperation(batchId, fileId, sheetId, "BATCH_SYNC", orderNo, excelRow, existingLogisticsLink,
|
||||||
"SKIPPED", "文档中已有物流链接,已同步订单状态");
|
"SKIPPED", "文档中已有物流链接,已同步订单状态");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -1397,7 +1401,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录操作日志
|
// 记录操作日志
|
||||||
logOperation(fileId, sheetId, "BATCH_SYNC", expectedOrderNo, row, logisticsLink,
|
logOperation(batchId, fileId, sheetId, "BATCH_SYNC", expectedOrderNo, row, logisticsLink,
|
||||||
"SUCCESS", null);
|
"SUCCESS", null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("写入数据失败 - 行: {}", entry.getKey(), e);
|
log.error("写入数据失败 - 行: {}", entry.getKey(), e);
|
||||||
@@ -1406,7 +1410,7 @@ public class TencentDocController extends BaseController {
|
|||||||
// 记录失败日志
|
// 记录失败日志
|
||||||
String orderNo = entry.getValue().getString("orderNo");
|
String orderNo = entry.getValue().getString("orderNo");
|
||||||
String logisticsLink = entry.getValue().getString("logisticsLink");
|
String logisticsLink = entry.getValue().getString("logisticsLink");
|
||||||
logOperation(fileId, sheetId, "BATCH_SYNC", orderNo, entry.getKey(), logisticsLink,
|
logOperation(batchId, fileId, sheetId, "BATCH_SYNC", orderNo, entry.getKey(), logisticsLink,
|
||||||
"FAILED", "写入异常: " + e.getMessage());
|
"FAILED", "写入异常: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1676,5 +1680,352 @@ public class TencentDocController extends BaseController {
|
|||||||
return AjaxResult.error("取消待推送任务失败: " + e.getMessage());
|
return AjaxResult.error("取消待推送任务失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反向同步第三方单号
|
||||||
|
* 从腾讯文档的物流单号列读取链接,通过链接匹配本地订单,将腾讯文档的单号列值写入到订单的第三方单号字段
|
||||||
|
*
|
||||||
|
* @param params 包含 fileId, sheetId, startRow(起始行,默认850)
|
||||||
|
* @return 同步结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/reverseSyncThirdPartyOrderNo")
|
||||||
|
public AjaxResult reverseSyncThirdPartyOrderNo(@RequestBody Map<String, Object> params) {
|
||||||
|
String batchId = java.util.UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取访问令牌
|
||||||
|
String accessToken;
|
||||||
|
try {
|
||||||
|
accessToken = tencentDocTokenService.refreshAccessToken();
|
||||||
|
log.info("成功刷新访问令牌");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新访问令牌失败", e);
|
||||||
|
try {
|
||||||
|
accessToken = tencentDocTokenService.getValidAccessToken();
|
||||||
|
} catch (Exception e2) {
|
||||||
|
return AjaxResult.error("访问令牌无效,请先完成授权。获取授权URL: GET /jarvis/tendoc/authUrl");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从参数或配置中获取文档信息
|
||||||
|
String fileId = (String) params.get("fileId");
|
||||||
|
String sheetId = (String) params.get("sheetId");
|
||||||
|
|
||||||
|
final String CONFIG_KEY_PREFIX = "tencent:doc:auto:config:";
|
||||||
|
if (fileId == null || fileId.isEmpty()) {
|
||||||
|
fileId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "fileId");
|
||||||
|
if (fileId == null || fileId.isEmpty()) {
|
||||||
|
fileId = tencentDocConfig.getFileId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sheetId == null || sheetId.isEmpty()) {
|
||||||
|
sheetId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "sheetId");
|
||||||
|
if (sheetId == null || sheetId.isEmpty()) {
|
||||||
|
sheetId = tencentDocConfig.getSheetId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从配置中读取表头行
|
||||||
|
Integer headerRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "headerRow");
|
||||||
|
if (headerRow == null) {
|
||||||
|
headerRow = tencentDocConfig.getHeaderRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 起始行,默认850
|
||||||
|
Integer startRow = params.get("startRow") != null ?
|
||||||
|
Integer.valueOf(params.get("startRow").toString()) : 850;
|
||||||
|
|
||||||
|
// 结束行,默认到2500行
|
||||||
|
Integer endRow = params.get("endRow") != null ?
|
||||||
|
Integer.valueOf(params.get("endRow").toString()) : 2500;
|
||||||
|
|
||||||
|
// 记录接收到的参数
|
||||||
|
log.info("接收到参数 - startRow: {}, endRow: {}, params: {}", startRow, endRow, params);
|
||||||
|
|
||||||
|
// 如果 endRow 小于 startRow + 1000,可能是前端传错了,强制设置为 2500
|
||||||
|
if (endRow < startRow + 1000) {
|
||||||
|
log.warn("检测到 endRow ({}) 可能不正确,强制设置为 2500", endRow);
|
||||||
|
endRow = 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessToken == null || fileId == null || sheetId == null) {
|
||||||
|
return AjaxResult.error("文档配置不完整,请先配置 fileId 和 sheetId");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("反向同步第三方单号开始 - fileId: {}, sheetId: {}, 起始行: {}, 结束行: {}",
|
||||||
|
fileId, sheetId, startRow, endRow);
|
||||||
|
|
||||||
|
// 读取表头,识别列位置
|
||||||
|
String headerRange = String.format("A%d:Z%d", headerRow, headerRow);
|
||||||
|
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange);
|
||||||
|
|
||||||
|
if (headerData == null || !headerData.containsKey("values")) {
|
||||||
|
return AjaxResult.error("读取表头失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray headerValues = headerData.getJSONArray("values");
|
||||||
|
if (headerValues == null || headerValues.isEmpty()) {
|
||||||
|
return AjaxResult.error("表头数据为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray headerRowData = headerValues.getJSONArray(0);
|
||||||
|
if (headerRowData == null || headerRowData.isEmpty()) {
|
||||||
|
return AjaxResult.error("无法识别表头");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 识别列位置
|
||||||
|
Integer orderNoColumn = null; // "单号"列
|
||||||
|
Integer logisticsLinkColumn = null; // "物流单号"列
|
||||||
|
|
||||||
|
for (int i = 0; i < headerRowData.size(); i++) {
|
||||||
|
String cellValue = headerRowData.getString(i);
|
||||||
|
if (cellValue != null) {
|
||||||
|
String cellValueTrim = cellValue.trim();
|
||||||
|
|
||||||
|
if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) {
|
||||||
|
orderNoColumn = i;
|
||||||
|
log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
|
log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderNoColumn == null || logisticsLinkColumn == null) {
|
||||||
|
return AjaxResult.error("无法识别表头列,请确保表头包含'单号'和'物流单号'列");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计结果
|
||||||
|
int successCount = 0;
|
||||||
|
int skippedCount = 0;
|
||||||
|
int errorCount = 0;
|
||||||
|
|
||||||
|
// 分批读取数据,每批200行(避免单次读取过多数据导致API限制)
|
||||||
|
final int BATCH_SIZE = 200;
|
||||||
|
int currentStartRow = startRow;
|
||||||
|
int totalBatches = (int) Math.ceil((double)(endRow - startRow + 1) / BATCH_SIZE);
|
||||||
|
int currentBatch = 0;
|
||||||
|
|
||||||
|
log.info("开始分批处理,共 {} 批,每批 {} 行", totalBatches, BATCH_SIZE);
|
||||||
|
|
||||||
|
while (currentStartRow <= endRow) {
|
||||||
|
currentBatch++;
|
||||||
|
int currentEndRow = Math.min(currentStartRow + BATCH_SIZE - 1, endRow);
|
||||||
|
|
||||||
|
log.info("正在处理第 {}/{} 批:第 {} 行到第 {} 行", currentBatch, totalBatches, currentStartRow, currentEndRow);
|
||||||
|
|
||||||
|
// 读取当前批次的数据行
|
||||||
|
String dataRange = String.format("A%d:Z%d", currentStartRow, currentEndRow);
|
||||||
|
log.info("读取数据范围: {}", dataRange);
|
||||||
|
|
||||||
|
JSONObject dataResponse = null;
|
||||||
|
try {
|
||||||
|
dataResponse = tencentDocService.readSheetData(accessToken, fileId, sheetId, dataRange);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("读取第 {} 批数据失败({} - {} 行)", currentBatch, currentStartRow, currentEndRow, e);
|
||||||
|
errorCount += (currentEndRow - currentStartRow + 1);
|
||||||
|
// 继续处理下一批
|
||||||
|
currentStartRow = currentEndRow + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataResponse == null || !dataResponse.containsKey("values")) {
|
||||||
|
log.warn("第 {} 批数据读取返回空({} - {} 行),跳过", currentBatch, currentStartRow, currentEndRow);
|
||||||
|
currentStartRow = currentEndRow + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray rows = dataResponse.getJSONArray("values");
|
||||||
|
if (rows == null || rows.isEmpty()) {
|
||||||
|
log.info("第 {} 批数据为空({} - {} 行),跳过", currentBatch, currentStartRow, currentEndRow);
|
||||||
|
currentStartRow = currentEndRow + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("第 {} 批读取到 {} 行数据", currentBatch, rows.size());
|
||||||
|
|
||||||
|
// 处理当前批次的每一行
|
||||||
|
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
|
||||||
|
JSONArray row = rows.getJSONArray(rowIndex);
|
||||||
|
if (row == null || row.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) {
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int actualRow = currentStartRow + rowIndex;
|
||||||
|
// 确保不超过结束行
|
||||||
|
if (actualRow > endRow) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String logisticsLink = row.getString(logisticsLinkColumn);
|
||||||
|
String orderNoFromDoc = row.getString(orderNoColumn);
|
||||||
|
|
||||||
|
// 跳过物流链接为空的行
|
||||||
|
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||||
|
log.debug("跳过第 {} 行:物流链接为空", actualRow);
|
||||||
|
skippedCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, null,
|
||||||
|
"SKIPPED", "物流链接为空");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过单号为空的行
|
||||||
|
if (orderNoFromDoc == null || orderNoFromDoc.trim().isEmpty()) {
|
||||||
|
log.debug("跳过第 {} 行:单号为空", actualRow);
|
||||||
|
skippedCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink,
|
||||||
|
"SKIPPED", "单号为空");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理物流链接(去除空格、换行符、中文等)
|
||||||
|
logisticsLink = cleanLogisticsLink(logisticsLink);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 通过物流链接查找订单
|
||||||
|
JDOrder order = jdOrderService.selectJDOrderByLogisticsLink(logisticsLink);
|
||||||
|
|
||||||
|
if (order == null) {
|
||||||
|
log.warn("未找到匹配的订单 - 行: {}, 物流链接: {}", actualRow, logisticsLink);
|
||||||
|
errorCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink,
|
||||||
|
"FAILED", "未找到匹配的订单");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单是否已有第三方单号(如果已有且与文档中的不同,跳过)
|
||||||
|
if (order.getThirdPartyOrderNo() != null && !order.getThirdPartyOrderNo().trim().isEmpty()) {
|
||||||
|
if (!order.getThirdPartyOrderNo().trim().equals(orderNoFromDoc.trim())) {
|
||||||
|
log.info("跳过第 {} 行:订单已有第三方单号且不同 - 现有: {}, 文档: {}",
|
||||||
|
actualRow, order.getThirdPartyOrderNo(), orderNoFromDoc);
|
||||||
|
skippedCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink,
|
||||||
|
"SKIPPED", "订单已有第三方单号且不同");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 如果相同,继续执行(清除物流链接)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新订单的第三方单号
|
||||||
|
order.setThirdPartyOrderNo(orderNoFromDoc.trim());
|
||||||
|
int updateResult = jdOrderService.updateJDOrder(order);
|
||||||
|
|
||||||
|
if (updateResult <= 0) {
|
||||||
|
log.error("更新订单失败 - 行: {}, 订单ID: {}, 单号: {}",
|
||||||
|
actualRow, order.getId(), order.getRemark());
|
||||||
|
errorCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink,
|
||||||
|
"FAILED", "更新订单失败");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✓ 更新订单成功 - 行: {}, 订单: {}, 第三方单号: {}",
|
||||||
|
actualRow, order.getRemark(), orderNoFromDoc);
|
||||||
|
|
||||||
|
successCount++;
|
||||||
|
|
||||||
|
// 记录成功日志
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink,
|
||||||
|
"SUCCESS", String.format("已将单号 %s 写入订单的第三方单号字段", orderNoFromDoc));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理第 {} 行失败", actualRow, e);
|
||||||
|
errorCount++;
|
||||||
|
logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink,
|
||||||
|
"FAILED", "处理异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加延迟,避免API调用频率过高
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("第 {}/{} 批处理完成,当前统计 - 成功: {}, 跳过: {}, 错误: {}",
|
||||||
|
currentBatch, totalBatches, successCount, skippedCount, errorCount);
|
||||||
|
|
||||||
|
// 移动到下一批
|
||||||
|
currentStartRow = currentEndRow + 1;
|
||||||
|
|
||||||
|
// 批次之间的延迟,避免API调用频率过高
|
||||||
|
if (currentStartRow <= endRow) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(200); // 批次之间延迟200ms
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
result.put("batchId", batchId);
|
||||||
|
result.put("startRow", startRow);
|
||||||
|
result.put("endRow", endRow);
|
||||||
|
result.put("successCount", successCount);
|
||||||
|
result.put("skippedCount", skippedCount);
|
||||||
|
result.put("errorCount", errorCount);
|
||||||
|
|
||||||
|
String message = String.format(
|
||||||
|
"✓ 反向同步完成:成功 %d 条,跳过 %d 条,错误 %d 条\n" +
|
||||||
|
" 处理范围:第 %d-%d 行\n" +
|
||||||
|
" 批次ID:%s",
|
||||||
|
successCount, skippedCount, errorCount, startRow, endRow, batchId);
|
||||||
|
result.put("message", message);
|
||||||
|
|
||||||
|
log.info("反向同步第三方单号完成 - {}", message);
|
||||||
|
|
||||||
|
return AjaxResult.success("反向同步完成", result);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("反向同步第三方单号失败", e);
|
||||||
|
return AjaxResult.error("反向同步失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理物流链接
|
||||||
|
* 去除空格、换行符、制表符、中文等特殊字符,只保留URL有效字符
|
||||||
|
*
|
||||||
|
* @param logisticsLink 原始物流链接
|
||||||
|
* @return 清理后的物流链接
|
||||||
|
*/
|
||||||
|
private String cleanLogisticsLink(String logisticsLink) {
|
||||||
|
if (logisticsLink == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除首尾空格
|
||||||
|
String cleaned = logisticsLink.trim();
|
||||||
|
|
||||||
|
// 去除换行符、制表符等空白字符
|
||||||
|
cleaned = cleaned.replaceAll("[\\r\\n\\t]", "");
|
||||||
|
|
||||||
|
// 去除所有空格
|
||||||
|
cleaned = cleaned.replaceAll("\\s+", "");
|
||||||
|
|
||||||
|
// 提取URL(如果包含中文或其他非URL字符,尝试提取URL部分)
|
||||||
|
// 匹配 http:// 或 https:// 开头的URL
|
||||||
|
java.util.regex.Pattern urlPattern = java.util.regex.Pattern.compile("https?://[^\\s\\u4e00-\\u9fa5]+");
|
||||||
|
java.util.regex.Matcher matcher = urlPattern.matcher(cleaned);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
cleaned = matcher.group();
|
||||||
|
} else {
|
||||||
|
// 如果没有找到完整URL,尝试提取 3.cn 相关的链接
|
||||||
|
java.util.regex.Pattern shortUrlPattern = java.util.regex.Pattern.compile("https?://3\\.cn/[^\\s\\u4e00-\\u9fa5]+");
|
||||||
|
java.util.regex.Matcher shortMatcher = shortUrlPattern.matcher(cleaned);
|
||||||
|
if (shortMatcher.find()) {
|
||||||
|
cleaned = shortMatcher.group();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleaned.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,11 +111,14 @@ public class PublicOrderController extends BaseController {
|
|||||||
|
|
||||||
log.info("日期验证通过: 订单日期[{}]", orderDate);
|
log.info("日期验证通过: 订单日期[{}]", orderDate);
|
||||||
|
|
||||||
|
// 获取forceGenerate参数(默认为false)
|
||||||
|
boolean forceGenerate = body != null && body.get("forceGenerate") != null && Boolean.parseBoolean(String.valueOf(body.get("forceGenerate")));
|
||||||
|
|
||||||
// 执行指令
|
// 执行指令
|
||||||
List<String> result;
|
List<String> result;
|
||||||
try {
|
try {
|
||||||
log.info("开始执行订单指令...");
|
log.info("开始执行订单指令... forceGenerate={}", forceGenerate);
|
||||||
result = instructionService.execute(trimmedCmd);
|
result = instructionService.execute(trimmedCmd, forceGenerate);
|
||||||
log.info("订单指令执行完成");
|
log.info("订单指令执行完成");
|
||||||
|
|
||||||
// 记录执行结果
|
// 记录执行结果
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import com.ruoyi.jarvis.service.IOrderRowsService;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
|
import com.ruoyi.jarvis.service.IInstructionService;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
|
||||||
@@ -30,10 +33,12 @@ public class JDOrderListController extends BaseController
|
|||||||
|
|
||||||
private final IJDOrderService jdOrderService;
|
private final IJDOrderService jdOrderService;
|
||||||
private final IOrderRowsService orderRowsService;
|
private final IOrderRowsService orderRowsService;
|
||||||
|
private final IInstructionService instructionService;
|
||||||
|
|
||||||
public JDOrderListController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService) {
|
public JDOrderListController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService, IInstructionService instructionService) {
|
||||||
this.jdOrderService = jdOrderService;
|
this.jdOrderService = jdOrderService;
|
||||||
this.orderRowsService = orderRowsService;
|
this.orderRowsService = orderRowsService;
|
||||||
|
this.instructionService = instructionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +69,12 @@ public class JDOrderListController extends BaseController
|
|||||||
query.getParams().put("hasFinishTime", true);
|
query.getParams().put("hasFinishTime", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理混合搜索参数(订单号/第三方单号/分销标识)
|
||||||
|
String orderSearch = request.getParameter("orderSearch");
|
||||||
|
if (orderSearch != null && !orderSearch.trim().isEmpty()) {
|
||||||
|
query.getParams().put("orderSearch", orderSearch.trim());
|
||||||
|
}
|
||||||
|
|
||||||
java.util.List<JDOrder> list;
|
java.util.List<JDOrder> list;
|
||||||
if (orderBy != null && !orderBy.isEmpty()) {
|
if (orderBy != null && !orderBy.isEmpty()) {
|
||||||
// 设置排序参数
|
// 设置排序参数
|
||||||
@@ -85,9 +96,11 @@ public class JDOrderListController extends BaseController
|
|||||||
if (orderRows != null) {
|
if (orderRows != null) {
|
||||||
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
||||||
jdOrder.setFinishTime(orderRows.getFinishTime());
|
jdOrder.setFinishTime(orderRows.getFinishTime());
|
||||||
|
jdOrder.setOrderStatus(orderRows.getValidCode());
|
||||||
} else {
|
} else {
|
||||||
jdOrder.setProPriceAmount(0.0);
|
jdOrder.setProPriceAmount(0.0);
|
||||||
jdOrder.setFinishTime(null);
|
jdOrder.setFinishTime(null);
|
||||||
|
jdOrder.setOrderStatus(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 过滤掉完成时间为空的订单
|
// 过滤掉完成时间为空的订单
|
||||||
@@ -101,8 +114,10 @@ public class JDOrderListController extends BaseController
|
|||||||
if (orderRows != null) {
|
if (orderRows != null) {
|
||||||
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
||||||
jdOrder.setFinishTime(orderRows.getFinishTime());
|
jdOrder.setFinishTime(orderRows.getFinishTime());
|
||||||
|
jdOrder.setOrderStatus(orderRows.getValidCode());
|
||||||
} else {
|
} else {
|
||||||
jdOrder.setProPriceAmount(0.0);
|
jdOrder.setProPriceAmount(0.0);
|
||||||
|
jdOrder.setOrderStatus(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,4 +186,251 @@ public class JDOrderListController extends BaseController
|
|||||||
{
|
{
|
||||||
return toAjax(jdOrderService.deleteJDOrderByIds(ids));
|
return toAjax(jdOrderService.deleteJDOrderByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单搜索工具接口(返回简易字段)
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@GetMapping("/tools/search")
|
||||||
|
public TableDataInfo searchOrders(
|
||||||
|
@RequestParam(required = false) String orderSearch,
|
||||||
|
@RequestParam(required = false) String address,
|
||||||
|
HttpServletRequest request)
|
||||||
|
{
|
||||||
|
// startPage会从request中读取pageNum和pageSize参数
|
||||||
|
startPage();
|
||||||
|
|
||||||
|
JDOrder query = new JDOrder();
|
||||||
|
|
||||||
|
// 处理单号搜索(过滤TF、H、F、PDD等关键词)
|
||||||
|
if (orderSearch != null && !orderSearch.trim().isEmpty()) {
|
||||||
|
String searchKeyword = orderSearch.trim().toUpperCase();
|
||||||
|
// 过滤掉TF、H、F、PDD等关键词
|
||||||
|
if (searchKeyword.contains("TF") || searchKeyword.contains("H") ||
|
||||||
|
searchKeyword.contains("F") || searchKeyword.contains("PDD")) {
|
||||||
|
// 如果包含过滤关键词,返回空结果
|
||||||
|
return getDataTable(new java.util.ArrayList<>());
|
||||||
|
}
|
||||||
|
// 至少5个字符
|
||||||
|
if (searchKeyword.length() >= 5) {
|
||||||
|
query.getParams().put("orderSearch", orderSearch.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理地址搜索(至少3个字符)
|
||||||
|
if (address != null && !address.trim().isEmpty()) {
|
||||||
|
if (address.trim().length() >= 3) {
|
||||||
|
query.setAddress(address.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有有效的搜索条件,返回空结果
|
||||||
|
if ((orderSearch == null || orderSearch.trim().isEmpty() || orderSearch.trim().length() < 5) &&
|
||||||
|
(address == null || address.trim().isEmpty() || address.trim().length() < 3)) {
|
||||||
|
return getDataTable(new java.util.ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
java.util.List<JDOrder> list = jdOrderService.selectJDOrderList(query);
|
||||||
|
|
||||||
|
// 转换为简易DTO,只返回前端需要的字段,其他字段脱敏
|
||||||
|
java.util.List<JDOrderSimpleDTO> simpleList = new java.util.ArrayList<>();
|
||||||
|
for (JDOrder jdOrder : list) {
|
||||||
|
JDOrderSimpleDTO dto = new JDOrderSimpleDTO();
|
||||||
|
|
||||||
|
// 只设置前端需要的字段
|
||||||
|
dto.setRemark(jdOrder.getRemark());
|
||||||
|
dto.setOrderId(jdOrder.getOrderId());
|
||||||
|
dto.setThirdPartyOrderNo(jdOrder.getThirdPartyOrderNo());
|
||||||
|
dto.setModelNumber(jdOrder.getModelNumber());
|
||||||
|
dto.setAddress(jdOrder.getAddress());
|
||||||
|
dto.setIsRefunded(jdOrder.getIsRefunded() != null ? jdOrder.getIsRefunded() : 0);
|
||||||
|
dto.setIsRebateReceived(jdOrder.getIsRebateReceived() != null ? jdOrder.getIsRebateReceived() : 0);
|
||||||
|
dto.setStatus(jdOrder.getStatus());
|
||||||
|
dto.setCreateTime(jdOrder.getCreateTime());
|
||||||
|
|
||||||
|
// 关联查询订单状态和赔付金额
|
||||||
|
OrderRows orderRows = orderRowsService.selectOrderRowsByOrderId(jdOrder.getOrderId());
|
||||||
|
if (orderRows != null) {
|
||||||
|
dto.setProPriceAmount(orderRows.getProPriceAmount());
|
||||||
|
dto.setOrderStatus(orderRows.getValidCode());
|
||||||
|
} else {
|
||||||
|
dto.setProPriceAmount(0.0);
|
||||||
|
dto.setOrderStatus(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleList.add(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDataTable(simpleList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一次性批量更新历史订单:将赔付金额大于0的订单标记为后返到账
|
||||||
|
* 此方法只应执行一次,用于处理历史数据
|
||||||
|
*/
|
||||||
|
@Log(title = "批量标记后返到账", businessType = BusinessType.UPDATE)
|
||||||
|
@RequestMapping(value = "/tools/batch-mark-rebate-received", method = {RequestMethod.POST, RequestMethod.GET})
|
||||||
|
public AjaxResult batchMarkRebateReceivedForCompensation() {
|
||||||
|
try {
|
||||||
|
// 调用批量更新方法
|
||||||
|
if (instructionService instanceof com.ruoyi.jarvis.service.impl.InstructionServiceImpl) {
|
||||||
|
((com.ruoyi.jarvis.service.impl.InstructionServiceImpl) instructionService)
|
||||||
|
.batchMarkRebateReceivedForCompensation();
|
||||||
|
return AjaxResult.success("批量标记后返到账完成,请查看控制台日志");
|
||||||
|
} else {
|
||||||
|
return AjaxResult.error("无法执行批量更新操作");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("批量标记失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成录单格式文本(Excel可粘贴格式)
|
||||||
|
* 根据当前查询条件生成Tab分隔的文本,可以直接粘贴到Excel
|
||||||
|
*/
|
||||||
|
@GetMapping("/generateExcelText")
|
||||||
|
public AjaxResult generateExcelText(JDOrder query, HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
// 处理时间筛选参数
|
||||||
|
String beginTimeStr = request.getParameter("beginTime");
|
||||||
|
String endTimeStr = request.getParameter("endTime");
|
||||||
|
|
||||||
|
if (beginTimeStr != null && !beginTimeStr.isEmpty()) {
|
||||||
|
query.getParams().put("beginTime", beginTimeStr);
|
||||||
|
}
|
||||||
|
if (endTimeStr != null && !endTimeStr.isEmpty()) {
|
||||||
|
query.getParams().put("endTime", endTimeStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理混合搜索参数
|
||||||
|
String orderSearch = request.getParameter("orderSearch");
|
||||||
|
if (orderSearch != null && !orderSearch.trim().isEmpty()) {
|
||||||
|
query.getParams().put("orderSearch", orderSearch.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他查询参数
|
||||||
|
if (query.getRemark() != null && !query.getRemark().trim().isEmpty()) {
|
||||||
|
query.setRemark(query.getRemark().trim());
|
||||||
|
}
|
||||||
|
if (query.getDistributionMark() != null && !query.getDistributionMark().trim().isEmpty()) {
|
||||||
|
query.setDistributionMark(query.getDistributionMark().trim());
|
||||||
|
}
|
||||||
|
if (query.getModelNumber() != null && !query.getModelNumber().trim().isEmpty()) {
|
||||||
|
query.setModelNumber(query.getModelNumber().trim());
|
||||||
|
}
|
||||||
|
if (query.getBuyer() != null && !query.getBuyer().trim().isEmpty()) {
|
||||||
|
query.setBuyer(query.getBuyer().trim());
|
||||||
|
}
|
||||||
|
if (query.getAddress() != null && !query.getAddress().trim().isEmpty()) {
|
||||||
|
query.setAddress(query.getAddress().trim());
|
||||||
|
}
|
||||||
|
if (query.getStatus() != null && !query.getStatus().trim().isEmpty()) {
|
||||||
|
query.setStatus(query.getStatus().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订单列表(不分页,获取所有符合条件的订单)
|
||||||
|
List<JDOrder> list = jdOrderService.selectJDOrderList(query);
|
||||||
|
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return AjaxResult.success("暂无订单数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联查询订单状态和赔付金额
|
||||||
|
for (JDOrder jdOrder : list) {
|
||||||
|
OrderRows orderRows = orderRowsService.selectOrderRowsByOrderId(jdOrder.getOrderId());
|
||||||
|
if (orderRows != null) {
|
||||||
|
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
||||||
|
// estimateCosPrice 是京粉实际价格
|
||||||
|
if (orderRows.getEstimateCosPrice() != null) {
|
||||||
|
jdOrder.setJingfenActualPrice(orderRows.getEstimateCosPrice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 remark 排序
|
||||||
|
list.sort((o1, o2) -> {
|
||||||
|
String r1 = o1.getRemark() != null ? o1.getRemark() : "";
|
||||||
|
String r2 = o2.getRemark() != null ? o2.getRemark() : "";
|
||||||
|
return r1.compareTo(r2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成Excel格式文本(Tab分隔)
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (JDOrder o : list) {
|
||||||
|
// 日期(格式:yyyy/MM/dd)
|
||||||
|
String dateStr = "";
|
||||||
|
if (o.getOrderTime() != null) {
|
||||||
|
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd");
|
||||||
|
dateStr = sdf.format(o.getOrderTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多多单号(第三方单号,如果没有则使用内部单号)
|
||||||
|
String duoduoOrderNo = o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().trim().isEmpty()
|
||||||
|
? o.getThirdPartyOrderNo() : (o.getRemark() != null ? o.getRemark() : "");
|
||||||
|
|
||||||
|
// 型号
|
||||||
|
String modelNumber = o.getModelNumber() != null ? o.getModelNumber() : "";
|
||||||
|
|
||||||
|
// 数量(固定为1)
|
||||||
|
String quantity = "1";
|
||||||
|
|
||||||
|
// 地址
|
||||||
|
String address = o.getAddress() != null ? o.getAddress() : "";
|
||||||
|
|
||||||
|
// 姓名(从地址中提取,地址格式通常是"姓名 电话 详细地址")
|
||||||
|
String buyer = "";
|
||||||
|
if (address != null && !address.trim().isEmpty()) {
|
||||||
|
String[] addressParts = address.trim().split("\\s+");
|
||||||
|
if (addressParts.length > 0) {
|
||||||
|
buyer = addressParts[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 售价(固定为0)
|
||||||
|
String sellingPriceStr = "0";
|
||||||
|
|
||||||
|
// 成本(售价是0,成本也设为空)
|
||||||
|
String costStr = "";
|
||||||
|
|
||||||
|
// 利润(后返金额)
|
||||||
|
Double rebate = o.getRebateAmount() != null ? o.getRebateAmount() : 0.0;
|
||||||
|
String profitStr = rebate > 0
|
||||||
|
? String.format(java.util.Locale.ROOT, "%.2f", rebate) : "";
|
||||||
|
|
||||||
|
// 京东单号
|
||||||
|
String orderId = o.getOrderId() != null ? o.getOrderId() : "";
|
||||||
|
|
||||||
|
// 物流链接
|
||||||
|
String logisticsLink = o.getLogisticsLink() != null ? o.getLogisticsLink() : "";
|
||||||
|
|
||||||
|
// 下单付款
|
||||||
|
String paymentAmountStr = o.getPaymentAmount() != null
|
||||||
|
? String.format(java.util.Locale.ROOT, "%.2f", o.getPaymentAmount()) : "";
|
||||||
|
|
||||||
|
// 后返
|
||||||
|
String rebateAmountStr = o.getRebateAmount() != null
|
||||||
|
? String.format(java.util.Locale.ROOT, "%.2f", o.getRebateAmount()) : "";
|
||||||
|
|
||||||
|
// 按顺序拼接:日期、多多单号、型号、数量、姓名、地址、售价、成本、利润、京东单号、物流、下单付款、后返
|
||||||
|
sb.append(dateStr).append('\t')
|
||||||
|
.append(duoduoOrderNo).append('\t')
|
||||||
|
.append(modelNumber).append('\t')
|
||||||
|
.append(quantity).append('\t')
|
||||||
|
.append(buyer).append('\t')
|
||||||
|
.append(address).append('\t')
|
||||||
|
.append(sellingPriceStr).append('\t')
|
||||||
|
.append(costStr).append('\t')
|
||||||
|
.append(profitStr).append('\t')
|
||||||
|
.append(orderId).append('\t')
|
||||||
|
.append(logisticsLink).append('\t')
|
||||||
|
.append(paymentAmountStr).append('\t')
|
||||||
|
.append(rebateAmountStr).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return AjaxResult.success(sb.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("生成失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ tencent:
|
|||||||
delayed:
|
delayed:
|
||||||
push:
|
push:
|
||||||
# 延迟时间(分钟),默认10分钟
|
# 延迟时间(分钟),默认10分钟
|
||||||
minutes: 30
|
minutes: 10
|
||||||
|
|||||||
@@ -217,6 +217,79 @@ public class HttpUtils
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向指定 URL 发送DELETE方法的请求
|
||||||
|
*
|
||||||
|
* @param url 发送请求的 URL
|
||||||
|
* @return 所代表远程资源的响应结果
|
||||||
|
*/
|
||||||
|
public static String sendDelete(String url)
|
||||||
|
{
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
BufferedReader in = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.info("sendDelete - {}", url);
|
||||||
|
URL realUrl = new URL(url);
|
||||||
|
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) realUrl.openConnection();
|
||||||
|
conn.setRequestMethod("DELETE");
|
||||||
|
conn.setRequestProperty("accept", "*/*");
|
||||||
|
conn.setRequestProperty("connection", "Keep-Alive");
|
||||||
|
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||||
|
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(20000);
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
int responseCode = conn.getResponseCode();
|
||||||
|
InputStream inputStream = (responseCode >= 200 && responseCode < 300)
|
||||||
|
? conn.getInputStream()
|
||||||
|
: conn.getErrorStream();
|
||||||
|
|
||||||
|
if (inputStream != null)
|
||||||
|
{
|
||||||
|
in = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null)
|
||||||
|
{
|
||||||
|
result.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("recv - {}", result);
|
||||||
|
}
|
||||||
|
catch (ConnectException e)
|
||||||
|
{
|
||||||
|
log.error("调用HttpUtils.sendDelete ConnectException, url=" + url, e);
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException e)
|
||||||
|
{
|
||||||
|
log.error("调用HttpUtils.sendDelete SocketTimeoutException, url=" + url, e);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.error("调用HttpUtils.sendDelete IOException, url=" + url, e);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("调用HttpUtils.sendDelete Exception, url=" + url, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (in != null)
|
||||||
|
{
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log.error("调用in.close Exception, url=" + url, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String sendSSLPost(String url, String param)
|
public static String sendSSLPost(String url, String param)
|
||||||
{
|
{
|
||||||
return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼商品对象 erp_product
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2024-01-01
|
||||||
|
*/
|
||||||
|
public class ErpProduct extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 管家商品ID */
|
||||||
|
@Excel(name = "管家商品ID")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
/** 商品标题 */
|
||||||
|
@Excel(name = "商品标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/** 商品图片(主图) */
|
||||||
|
@Excel(name = "商品图片")
|
||||||
|
private String mainImage;
|
||||||
|
|
||||||
|
/** 商品价格(分) */
|
||||||
|
@Excel(name = "商品价格")
|
||||||
|
private Long price;
|
||||||
|
|
||||||
|
/** 商品库存 */
|
||||||
|
@Excel(name = "商品库存")
|
||||||
|
private Integer stock;
|
||||||
|
|
||||||
|
/** 商品状态 -1:删除 21:待发布 22:销售中 23:已售罄 31:手动下架 33:售出下架 36:自动下架 */
|
||||||
|
@Excel(name = "商品状态", readConverterExp = "-1=删除,21=待发布,22=销售中,23=已售罄,31=手动下架,33=售出下架,36=自动下架")
|
||||||
|
private Integer productStatus;
|
||||||
|
|
||||||
|
/** 销售状态 */
|
||||||
|
@Excel(name = "销售状态")
|
||||||
|
private Integer saleStatus;
|
||||||
|
|
||||||
|
/** 闲鱼会员名 */
|
||||||
|
@Excel(name = "闲鱼会员名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/** 上架时间 */
|
||||||
|
@Excel(name = "上架时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Long onlineTime;
|
||||||
|
|
||||||
|
/** 下架时间 */
|
||||||
|
@Excel(name = "下架时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Long offlineTime;
|
||||||
|
|
||||||
|
/** 售出时间 */
|
||||||
|
@Excel(name = "售出时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Long soldTime;
|
||||||
|
|
||||||
|
/** 创建时间(闲鱼) */
|
||||||
|
@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Long createTimeXy;
|
||||||
|
|
||||||
|
/** 更新时间(闲鱼) */
|
||||||
|
@Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Long updateTimeXy;
|
||||||
|
|
||||||
|
/** ERP应用ID */
|
||||||
|
@Excel(name = "ERP应用ID")
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
/** 商品链接 */
|
||||||
|
@Excel(name = "商品链接")
|
||||||
|
private String productUrl;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
@Excel(name = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
public void setId(Long id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId)
|
||||||
|
{
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId()
|
||||||
|
{
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle()
|
||||||
|
{
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainImage(String mainImage)
|
||||||
|
{
|
||||||
|
this.mainImage = mainImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainImage()
|
||||||
|
{
|
||||||
|
return mainImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(Long price)
|
||||||
|
{
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPrice()
|
||||||
|
{
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStock(Integer stock)
|
||||||
|
{
|
||||||
|
this.stock = stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStock()
|
||||||
|
{
|
||||||
|
return stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductStatus(Integer productStatus)
|
||||||
|
{
|
||||||
|
this.productStatus = productStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getProductStatus()
|
||||||
|
{
|
||||||
|
return productStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaleStatus(Integer saleStatus)
|
||||||
|
{
|
||||||
|
this.saleStatus = saleStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSaleStatus()
|
||||||
|
{
|
||||||
|
return saleStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName)
|
||||||
|
{
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName()
|
||||||
|
{
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnlineTime(Long onlineTime)
|
||||||
|
{
|
||||||
|
this.onlineTime = onlineTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOnlineTime()
|
||||||
|
{
|
||||||
|
return onlineTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOfflineTime(Long offlineTime)
|
||||||
|
{
|
||||||
|
this.offlineTime = offlineTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOfflineTime()
|
||||||
|
{
|
||||||
|
return offlineTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoldTime(Long soldTime)
|
||||||
|
{
|
||||||
|
this.soldTime = soldTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSoldTime()
|
||||||
|
{
|
||||||
|
return soldTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTimeXy(Long createTimeXy)
|
||||||
|
{
|
||||||
|
this.createTimeXy = createTimeXy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreateTimeXy()
|
||||||
|
{
|
||||||
|
return createTimeXy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTimeXy(Long updateTimeXy)
|
||||||
|
{
|
||||||
|
this.updateTimeXy = updateTimeXy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUpdateTimeXy()
|
||||||
|
{
|
||||||
|
return updateTimeXy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppid(String appid)
|
||||||
|
{
|
||||||
|
this.appid = appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppid()
|
||||||
|
{
|
||||||
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductUrl(String productUrl)
|
||||||
|
{
|
||||||
|
this.productUrl = productUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductUrl()
|
||||||
|
{
|
||||||
|
return productUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRemark(String remark)
|
||||||
|
{
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemark()
|
||||||
|
{
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||||
|
.append("id", getId())
|
||||||
|
.append("productId", getProductId())
|
||||||
|
.append("title", getTitle())
|
||||||
|
.append("mainImage", getMainImage())
|
||||||
|
.append("price", getPrice())
|
||||||
|
.append("stock", getStock())
|
||||||
|
.append("productStatus", getProductStatus())
|
||||||
|
.append("saleStatus", getSaleStatus())
|
||||||
|
.append("userName", getUserName())
|
||||||
|
.append("onlineTime", getOnlineTime())
|
||||||
|
.append("offlineTime", getOfflineTime())
|
||||||
|
.append("soldTime", getSoldTime())
|
||||||
|
.append("createTimeXy", getCreateTimeXy())
|
||||||
|
.append("updateTimeXy", getUpdateTimeXy())
|
||||||
|
.append("appid", getAppid())
|
||||||
|
.append("productUrl", getProductUrl())
|
||||||
|
.append("remark", getRemark())
|
||||||
|
.append("createTime", getCreateTime())
|
||||||
|
.append("updateTime", getUpdateTime())
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -80,6 +80,11 @@ public class JDOrder extends BaseEntity {
|
|||||||
@Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date finishTime;
|
private Date finishTime;
|
||||||
|
|
||||||
|
/** 订单状态(从order_rows表查询) */
|
||||||
|
@Transient
|
||||||
|
@Excel(name = "订单状态")
|
||||||
|
private Integer orderStatus;
|
||||||
|
|
||||||
/** 是否参与统计(0否 1是) */
|
/** 是否参与统计(0否 1是) */
|
||||||
@Excel(name = "参与统计")
|
@Excel(name = "参与统计")
|
||||||
private Integer isCountEnabled;
|
private Integer isCountEnabled;
|
||||||
@@ -92,6 +97,30 @@ public class JDOrder extends BaseEntity {
|
|||||||
@Excel(name = "京粉实际价格")
|
@Excel(name = "京粉实际价格")
|
||||||
private Double jingfenActualPrice;
|
private Double jingfenActualPrice;
|
||||||
|
|
||||||
|
/** 是否退款(0否 1是) */
|
||||||
|
@Excel(name = "是否退款")
|
||||||
|
private Integer isRefunded;
|
||||||
|
|
||||||
|
/** 退款日期 */
|
||||||
|
@Excel(name = "退款日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date refundDate;
|
||||||
|
|
||||||
|
/** 是否退款到账(0否 1是) */
|
||||||
|
@Excel(name = "是否退款到账")
|
||||||
|
private Integer isRefundReceived;
|
||||||
|
|
||||||
|
/** 退款到账日期 */
|
||||||
|
@Excel(name = "退款到账日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date refundReceivedDate;
|
||||||
|
|
||||||
|
/** 后返到账(0否 1是) */
|
||||||
|
@Excel(name = "后返到账")
|
||||||
|
private Integer isRebateReceived;
|
||||||
|
|
||||||
|
/** 后返到账日期 */
|
||||||
|
@Excel(name = "后返到账日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date rebateReceivedDate;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ public class SuperAdmin extends BaseEntity
|
|||||||
@Excel(name = "是否参与订单统计", readConverterExp = "0=否,1=是")
|
@Excel(name = "是否参与订单统计", readConverterExp = "0=否,1=是")
|
||||||
private Integer isCount;
|
private Integer isCount;
|
||||||
|
|
||||||
|
/** 接收人(企业微信用户ID,多个用逗号分隔) */
|
||||||
|
@Excel(name = "接收人")
|
||||||
|
private String touser;
|
||||||
|
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
@Excel(name = "创建时间")
|
@Excel(name = "创建时间")
|
||||||
private Date createdAt;
|
private Date createdAt;
|
||||||
@@ -151,4 +155,14 @@ public class SuperAdmin extends BaseEntity
|
|||||||
{
|
{
|
||||||
this.isCount = isCount;
|
this.isCount = isCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTouser()
|
||||||
|
{
|
||||||
|
return touser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTouser(String touser)
|
||||||
|
{
|
||||||
|
this.touser = touser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单搜索工具返回的简易DTO
|
||||||
|
* 只包含前端展示需要的字段,其他字段脱敏
|
||||||
|
*/
|
||||||
|
public class JDOrderSimpleDTO {
|
||||||
|
|
||||||
|
/** 内部单号 */
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/** 京东单号 */
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
/** 第三方单号 */
|
||||||
|
private String thirdPartyOrderNo;
|
||||||
|
|
||||||
|
/** 型号 */
|
||||||
|
private String modelNumber;
|
||||||
|
|
||||||
|
/** 地址 */
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/** 退款状态(0否 1是) */
|
||||||
|
private Integer isRefunded;
|
||||||
|
|
||||||
|
/** 后返到账(0否 1是) */
|
||||||
|
private Integer isRebateReceived;
|
||||||
|
|
||||||
|
/** 赔付金额 */
|
||||||
|
private Double proPriceAmount;
|
||||||
|
|
||||||
|
/** 订单状态 */
|
||||||
|
private Integer orderStatus;
|
||||||
|
|
||||||
|
/** 备注/状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(String orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThirdPartyOrderNo() {
|
||||||
|
return thirdPartyOrderNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThirdPartyOrderNo(String thirdPartyOrderNo) {
|
||||||
|
this.thirdPartyOrderNo = thirdPartyOrderNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModelNumber() {
|
||||||
|
return modelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModelNumber(String modelNumber) {
|
||||||
|
this.modelNumber = modelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIsRefunded() {
|
||||||
|
return isRefunded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRefunded(Integer isRefunded) {
|
||||||
|
this.isRefunded = isRefunded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIsRebateReceived() {
|
||||||
|
return isRebateReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRebateReceived(Integer isRebateReceived) {
|
||||||
|
this.isRebateReceived = isRebateReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getProPriceAmount() {
|
||||||
|
return proPriceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProPriceAmount(Double proPriceAmount) {
|
||||||
|
this.proPriceAmount = proPriceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderStatus() {
|
||||||
|
return orderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderStatus(Integer orderStatus) {
|
||||||
|
this.orderStatus = orderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(Date createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpProduct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼商品Mapper接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2024-01-01
|
||||||
|
*/
|
||||||
|
public interface ErpProductMapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 闲鱼商品
|
||||||
|
*/
|
||||||
|
public ErpProduct selectErpProductById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品列表
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 闲鱼商品集合
|
||||||
|
*/
|
||||||
|
public List<ErpProduct> selectErpProductList(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int insertErpProduct(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int updateErpProduct(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除闲鱼商品
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteErpProductById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除闲鱼商品
|
||||||
|
*
|
||||||
|
* @param ids 需要删除的数据主键集合
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteErpProductByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据商品ID和appid查询
|
||||||
|
*
|
||||||
|
* @param productId 商品ID
|
||||||
|
* @param appid ERP应用ID
|
||||||
|
* @return 闲鱼商品
|
||||||
|
*/
|
||||||
|
public ErpProduct selectErpProductByProductIdAndAppid(@Param("productId") Long productId, @Param("appid") String appid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入或更新闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProducts 闲鱼商品列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int batchInsertOrUpdateErpProduct(List<ErpProduct> erpProducts);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpProduct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼商品Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2024-01-01
|
||||||
|
*/
|
||||||
|
public interface IErpProductService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 闲鱼商品
|
||||||
|
*/
|
||||||
|
public ErpProduct selectErpProductById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品列表
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 闲鱼商品集合
|
||||||
|
*/
|
||||||
|
public List<ErpProduct> selectErpProductList(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int insertErpProduct(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int updateErpProduct(ErpProduct erpProduct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除闲鱼商品
|
||||||
|
*
|
||||||
|
* @param ids 需要删除的闲鱼商品主键集合
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteErpProductByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除闲鱼商品信息
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteErpProductById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从闲鱼ERP拉取商品列表并保存
|
||||||
|
*
|
||||||
|
* @param appid ERP应用ID
|
||||||
|
* @param pageNo 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param productStatus 商品状态
|
||||||
|
* @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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,23 @@ public interface IInstructionService {
|
|||||||
*/
|
*/
|
||||||
java.util.List<String> execute(String command);
|
java.util.List<String> execute(String command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行文本指令,返回结果文本(支持强制生成参数)
|
||||||
|
* @param command 指令内容
|
||||||
|
* @param forceGenerate 是否强制生成表单(跳过地址重复检查)
|
||||||
|
* @return 执行结果文本列表(可能为单条或多条)
|
||||||
|
*/
|
||||||
|
java.util.List<String> execute(String command, boolean forceGenerate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行文本指令,返回结果文本(支持强制生成参数和控制台入口标识)
|
||||||
|
* @param command 指令内容
|
||||||
|
* @param forceGenerate 是否强制生成表单(跳过地址重复检查)
|
||||||
|
* @param isFromConsole 是否来自控制台入口(控制台入口跳过订单查询校验)
|
||||||
|
* @return 执行结果文本列表(可能为单条或多条)
|
||||||
|
*/
|
||||||
|
java.util.List<String> execute(String command, boolean forceGenerate, boolean isFromConsole);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取历史消息记录
|
* 获取历史消息记录
|
||||||
* @param type 消息类型:request(请求) 或 response(响应)
|
* @param type 消息类型:request(请求) 或 response(响应)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小红书/抖音内容生成Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-XX
|
||||||
|
*/
|
||||||
|
public interface ISocialMediaService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 提取商品标题关键词
|
||||||
|
*
|
||||||
|
* @param productName 商品名称
|
||||||
|
* @return 关键词结果
|
||||||
|
*/
|
||||||
|
Map<String, Object> extractKeywords(String productName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成文案
|
||||||
|
*
|
||||||
|
* @param productName 商品名称
|
||||||
|
* @param originalPrice 原价
|
||||||
|
* @param finalPrice 到手价
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @param style 文案风格
|
||||||
|
* @return 生成的文案
|
||||||
|
*/
|
||||||
|
Map<String, Object> generateContent(String productName, Object originalPrice,
|
||||||
|
Object finalPrice, String keywords, String style);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键生成完整内容(关键词 + 文案 + 图片)
|
||||||
|
*
|
||||||
|
* @param productImageUrl 商品主图URL
|
||||||
|
* @param productName 商品名称
|
||||||
|
* @param originalPrice 原价
|
||||||
|
* @param finalPrice 到手价
|
||||||
|
* @param style 文案风格
|
||||||
|
* @return 完整内容
|
||||||
|
*/
|
||||||
|
Map<String, Object> generateCompleteContent(String productImageUrl, String productName,
|
||||||
|
Object originalPrice, Object finalPrice, String style);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取提示词模板列表
|
||||||
|
*/
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult listPromptTemplates();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个提示词模板
|
||||||
|
*/
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult getPromptTemplate(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存提示词模板
|
||||||
|
*/
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult savePromptTemplate(Map<String, Object> request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除提示词模板(恢复默认)
|
||||||
|
*/
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult deletePromptTemplate(String key);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -98,9 +98,18 @@ private String cleanForbiddenPhrases(String text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
String cleaned = text;
|
String cleaned = text;
|
||||||
|
// 新增:清理【】符号(包括单独出现或成对出现的情况)
|
||||||
|
cleaned = cleaned.replaceAll("【", ""); // 移除左括号【
|
||||||
|
cleaned = cleaned.replaceAll("】", ""); // 移除右括号】
|
||||||
|
// 新增:清理"咨询客服立减""咨询客服""客服"及变体(含空格)
|
||||||
|
// 优先处理长组合,避免被拆分后遗漏
|
||||||
|
cleaned = cleaned.replaceAll("咨询\\s*客服\\s*立减", ""); // 匹配"咨询客服立减""咨询 客服 立减"等
|
||||||
|
cleaned = cleaned.replaceAll("咨询\\s*客服", ""); // 匹配"咨询客服""咨询 客服"等
|
||||||
|
cleaned = cleaned.replaceAll("客\\s*服", ""); // 匹配"客服""客 服"等
|
||||||
|
|
||||||
// 一、政策补贴及特殊渠道类(长组合优先)
|
// 一、政策补贴及特殊渠道类(长组合优先)
|
||||||
cleaned = cleaned.replaceAll("咨询客服领\\s*国补", "");
|
cleaned = cleaned.replaceAll("咨询客服领\\s*国补", "");
|
||||||
cleaned = cleaned.replaceAll("政府\\s*补贴", ""); // 匹配"政府 补贴"等带空格的情况
|
cleaned = cleaned.replaceAll("政府\\s*补贴", "");
|
||||||
cleaned = cleaned.replaceAll("购车\\s*补贴", "");
|
cleaned = cleaned.replaceAll("购车\\s*补贴", "");
|
||||||
cleaned = cleaned.replaceAll("家电\\s*下乡", "");
|
cleaned = cleaned.replaceAll("家电\\s*下乡", "");
|
||||||
cleaned = cleaned.replaceAll("内部\\s*渠道", "");
|
cleaned = cleaned.replaceAll("内部\\s*渠道", "");
|
||||||
@@ -128,15 +137,15 @@ private String cleanForbiddenPhrases(String text) {
|
|||||||
cleaned = cleaned.replaceAll("原单", "");
|
cleaned = cleaned.replaceAll("原单", "");
|
||||||
cleaned = cleaned.replaceAll("尾单", "");
|
cleaned = cleaned.replaceAll("尾单", "");
|
||||||
cleaned = cleaned.replaceAll("工厂\\s*货", "");
|
cleaned = cleaned.replaceAll("工厂\\s*货", "");
|
||||||
cleaned = cleaned.replaceAll("专柜\\s*验货", ""); // 无授权时违规
|
cleaned = cleaned.replaceAll("专柜\\s*验货", "");
|
||||||
|
|
||||||
// 四、线下导流及规避监管类(多变体覆盖)
|
// 四、线下导流及规避监管类(多变体覆盖)
|
||||||
cleaned = cleaned.replaceAll("微\\s*信", ""); // 匹配"微信""微 信"
|
cleaned = cleaned.replaceAll("微\\s*信", "");
|
||||||
cleaned = cleaned.replaceAll("薇\\s*信", ""); // 谐音变体
|
cleaned = cleaned.replaceAll("薇\\s*信", "");
|
||||||
cleaned = cleaned.replaceAll("V我", "");
|
cleaned = cleaned.replaceAll("V我", "");
|
||||||
cleaned = cleaned.replaceAll("加\\s*卫星", "");
|
cleaned = cleaned.replaceAll("加\\s*卫星", "");
|
||||||
cleaned = cleaned.replaceAll("QQ", "");
|
cleaned = cleaned.replaceAll("QQ", "");
|
||||||
cleaned = cleaned.replaceAll("扣扣", ""); // 谐音
|
cleaned = cleaned.replaceAll("扣扣", "");
|
||||||
cleaned = cleaned.replaceAll("手机\\s*号", "");
|
cleaned = cleaned.replaceAll("手机\\s*号", "");
|
||||||
cleaned = cleaned.replaceAll("淘宝\\s*链接", "");
|
cleaned = cleaned.replaceAll("淘宝\\s*链接", "");
|
||||||
cleaned = cleaned.replaceAll("拼多\\s*多", "");
|
cleaned = cleaned.replaceAll("拼多\\s*多", "");
|
||||||
@@ -146,7 +155,7 @@ private String cleanForbiddenPhrases(String text) {
|
|||||||
cleaned = cleaned.replaceAll("滚", "");
|
cleaned = cleaned.replaceAll("滚", "");
|
||||||
cleaned = cleaned.replaceAll("垃圾", "");
|
cleaned = cleaned.replaceAll("垃圾", "");
|
||||||
cleaned = cleaned.replaceAll("笨蛋", "");
|
cleaned = cleaned.replaceAll("笨蛋", "");
|
||||||
cleaned = cleaned.replaceAll("SB", ""); // 单独出现时清理(避免误判可后续加上下文校验)
|
cleaned = cleaned.replaceAll("SB", "");
|
||||||
cleaned = cleaned.replaceAll("原味", "");
|
cleaned = cleaned.replaceAll("原味", "");
|
||||||
cleaned = cleaned.replaceAll("情趣", "");
|
cleaned = cleaned.replaceAll("情趣", "");
|
||||||
|
|
||||||
@@ -922,7 +931,8 @@ private String cleanForbiddenPhrases(String text) {
|
|||||||
// 调用ERP上架接口
|
// 调用ERP上架接口
|
||||||
ProductPublishRequest publishRequest = new ProductPublishRequest(account);
|
ProductPublishRequest publishRequest = new ProductPublishRequest(account);
|
||||||
publishRequest.setProductId(item.getProductId());
|
publishRequest.setProductId(item.getProductId());
|
||||||
publishRequest.setUserName(commonParams.getUserName());
|
// 【修复】使用商品对应的子账号,而不是通用参数中的第一个子账号
|
||||||
|
publishRequest.setUserName(item.getSubAccount() != null ? item.getSubAccount() : commonParams.getUserName());
|
||||||
publishRequest.setSpecifyPublishTime(null); // 立即上架
|
publishRequest.setSpecifyPublishTime(null); // 立即上架
|
||||||
|
|
||||||
String resp = publishRequest.getResponseBody();
|
String resp = publishRequest.getResponseBody();
|
||||||
@@ -1005,32 +1015,57 @@ private String cleanForbiddenPhrases(String text) {
|
|||||||
throw new RuntimeException("任务不存在: " + taskId);
|
throw new RuntimeException("任务不存在: " + taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅重试 待发布(0)/发布失败(3) 的明细
|
|
||||||
List<BatchPublishItem> allItems = itemMapper.selectBatchPublishItemByTaskId(taskId);
|
List<BatchPublishItem> allItems = itemMapper.selectBatchPublishItemByTaskId(taskId);
|
||||||
List<BatchPublishItem> itemsToRetry = new ArrayList<>();
|
|
||||||
|
// 分类处理:需要重新发布的 和 需要重新上架的
|
||||||
|
List<BatchPublishItem> itemsToRepublish = new ArrayList<>(); // 待发布(0)/发布失败(3)
|
||||||
|
List<BatchPublishItem> itemsToRelist = new ArrayList<>(); // 发布成功(2)/上架失败(6)
|
||||||
|
|
||||||
for (BatchPublishItem it : allItems) {
|
for (BatchPublishItem it : allItems) {
|
||||||
if (it.getStatus() != null && (it.getStatus() == 0 || it.getStatus() == 3)) {
|
if (it.getStatus() != null) {
|
||||||
itemsToRetry.add(it);
|
if (it.getStatus() == 0 || it.getStatus() == 3) {
|
||||||
|
// 待发布或发布失败,需要重新发布
|
||||||
|
itemsToRepublish.add(it);
|
||||||
|
} else if (it.getStatus() == 2 || it.getStatus() == 6) {
|
||||||
|
// 发布成功但未上架,或上架失败,需要重新上架
|
||||||
|
itemsToRelist.add(it);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemsToRetry.isEmpty()) {
|
if (itemsToRepublish.isEmpty() && itemsToRelist.isEmpty()) {
|
||||||
log.info("任务{} 无需重试,未发现待发布/失败的明细", taskId);
|
log.info("任务{} 无需重试,未发现待处理的明细", taskId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造最小请求对象,仅提供通用参数用于发布
|
// 处理需要重新发布的商品
|
||||||
BatchPublishRequest req = new BatchPublishRequest();
|
if (!itemsToRepublish.isEmpty()) {
|
||||||
try {
|
BatchPublishRequest req = new BatchPublishRequest();
|
||||||
BatchPublishRequest.CommonParams commonParams = JSON.parseObject(
|
try {
|
||||||
task.getCommonParams(), BatchPublishRequest.CommonParams.class);
|
BatchPublishRequest.CommonParams commonParams = JSON.parseObject(
|
||||||
req.setCommonParams(commonParams);
|
task.getCommonParams(), BatchPublishRequest.CommonParams.class);
|
||||||
} catch (Exception e) {
|
req.setCommonParams(commonParams);
|
||||||
log.warn("解析任务通用参数失败,将使用默认参数: {}", task.getCommonParams(), e);
|
} catch (Exception e) {
|
||||||
|
log.warn("解析任务通用参数失败,将使用默认参数: {}", task.getCommonParams(), e);
|
||||||
|
}
|
||||||
|
log.info("开始重新发布任务{} 的 {} 条明细", taskId, itemsToRepublish.size());
|
||||||
|
self.asyncBatchPublish(taskId, itemsToRepublish, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("开始重试任务{} 的 {} 条明细", taskId, itemsToRetry.size());
|
// 处理需要重新上架的商品(直接上架,不需要重新发布)
|
||||||
self.asyncBatchPublish(taskId, itemsToRetry, req);
|
if (!itemsToRelist.isEmpty()) {
|
||||||
|
log.info("开始重新上架任务{} 的 {} 条明细", taskId, itemsToRelist.size());
|
||||||
|
for (BatchPublishItem item : itemsToRelist) {
|
||||||
|
// 重置状态为发布成功,准备上架
|
||||||
|
item.setStatus(2);
|
||||||
|
item.setErrorMessage(null);
|
||||||
|
itemMapper.updateBatchPublishItem(item);
|
||||||
|
appendItemLogSafe(item.getId(), "【重试】准备重新上架");
|
||||||
|
|
||||||
|
// 立即调度上架(延迟1秒,避免过快)
|
||||||
|
schedulePublish(item.getId(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
package com.ruoyi.jarvis.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.ruoyi.jarvis.mapper.ErpProductMapper;
|
||||||
|
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业务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2024-01-01
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ErpProductServiceImpl implements IErpProductService
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ErpProductServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ErpProductMapper erpProductMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 闲鱼商品
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ErpProduct selectErpProductById(Long id)
|
||||||
|
{
|
||||||
|
return erpProductMapper.selectErpProductById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲鱼商品列表
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 闲鱼商品
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ErpProduct> selectErpProductList(ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
return erpProductMapper.selectErpProductList(erpProduct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int insertErpProduct(ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
// 检查是否已存在
|
||||||
|
ErpProduct existing = erpProductMapper.selectErpProductByProductIdAndAppid(
|
||||||
|
erpProduct.getProductId(), erpProduct.getAppid());
|
||||||
|
if (existing != null) {
|
||||||
|
// 更新已存在的商品
|
||||||
|
erpProduct.setId(existing.getId());
|
||||||
|
return erpProductMapper.updateErpProduct(erpProduct);
|
||||||
|
}
|
||||||
|
return erpProductMapper.insertErpProduct(erpProduct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改闲鱼商品
|
||||||
|
*
|
||||||
|
* @param erpProduct 闲鱼商品
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int updateErpProduct(ErpProduct erpProduct)
|
||||||
|
{
|
||||||
|
return erpProductMapper.updateErpProduct(erpProduct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除闲鱼商品
|
||||||
|
*
|
||||||
|
* @param ids 需要删除的闲鱼商品主键
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int deleteErpProductByIds(Long[] ids)
|
||||||
|
{
|
||||||
|
return erpProductMapper.deleteErpProductByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除闲鱼商品信息
|
||||||
|
*
|
||||||
|
* @param id 闲鱼商品主键
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int deleteErpProductById(Long id)
|
||||||
|
{
|
||||||
|
return erpProductMapper.deleteErpProductById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从闲鱼ERP拉取商品列表并保存
|
||||||
|
*
|
||||||
|
* @param appid ERP应用ID
|
||||||
|
* @param pageNo 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param productStatus 商品状态
|
||||||
|
* @return 拉取并保存的商品数量
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int pullAndSaveProductList(String appid, Integer pageNo, Integer pageSize, Integer productStatus)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 解析ERP账号
|
||||||
|
ERPAccount account = resolveAccount(appid);
|
||||||
|
|
||||||
|
// 创建查询请求
|
||||||
|
ProductListQueryRequest request = new ProductListQueryRequest(account);
|
||||||
|
if (pageNo != null) {
|
||||||
|
request.setPageNo(pageNo);
|
||||||
|
}
|
||||||
|
if (pageSize != null) {
|
||||||
|
request.setPageSize(pageSize);
|
||||||
|
}
|
||||||
|
if (productStatus != null) {
|
||||||
|
// API要求的状态值:-1(全部), 10(上架), 21(下架), 22(草稿), 23(审核中), 31(已售), 33(已删除), 36(违规)
|
||||||
|
// 前端传入的简化状态值:1(上架), 2(下架), 3(已售)
|
||||||
|
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("拉取商品列表失败: code={}, msg={}, response={}",
|
||||||
|
response != null ? response.getInteger("code") : null, errorMsg, responseBody);
|
||||||
|
throw new RuntimeException("拉取商品列表失败: " + errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析商品列表
|
||||||
|
JSONObject data = response.getJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
log.warn("拉取商品列表返回数据为空");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray productList = data.getJSONArray("list");
|
||||||
|
Integer totalCount = data.getInteger("count");
|
||||||
|
|
||||||
|
if (productList == null || productList.isEmpty()) {
|
||||||
|
String statusMsg = productStatus != null ? "(状态:" + productStatus + ")" : "";
|
||||||
|
if (totalCount != null && totalCount > 0) {
|
||||||
|
log.info("拉取商品列表为空,但总数显示为 {},可能是分页问题", totalCount);
|
||||||
|
} else {
|
||||||
|
log.info("拉取商品列表为空{},该账号下没有符合条件的商品", statusMsg);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("拉取到 {} 个商品,开始保存", productList.size());
|
||||||
|
|
||||||
|
// 转换为实体对象并保存
|
||||||
|
List<ErpProduct> erpProducts = new ArrayList<>();
|
||||||
|
for (int i = 0; i < productList.size(); i++) {
|
||||||
|
JSONObject productJson = productList.getJSONObject(i);
|
||||||
|
ErpProduct erpProduct = parseProductJson(productJson, appid);
|
||||||
|
if (erpProduct != null) {
|
||||||
|
erpProducts.add(erpProduct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量保存或更新
|
||||||
|
if (!erpProducts.isEmpty()) {
|
||||||
|
// 逐个保存(兼容更新)
|
||||||
|
int savedCount = 0;
|
||||||
|
for (ErpProduct product : erpProducts) {
|
||||||
|
ErpProduct existing = erpProductMapper.selectErpProductByProductIdAndAppid(
|
||||||
|
product.getProductId(), product.getAppid());
|
||||||
|
if (existing != null) {
|
||||||
|
product.setId(existing.getId());
|
||||||
|
erpProductMapper.updateErpProduct(product);
|
||||||
|
} else {
|
||||||
|
erpProductMapper.insertErpProduct(product);
|
||||||
|
}
|
||||||
|
savedCount++;
|
||||||
|
}
|
||||||
|
log.info("成功拉取并保存 {} 个商品", savedCount);
|
||||||
|
return savedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("拉取商品列表异常", e);
|
||||||
|
throw new RuntimeException("拉取商品列表失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析商品JSON数据
|
||||||
|
*/
|
||||||
|
private ErpProduct parseProductJson(JSONObject productJson, String appid) {
|
||||||
|
try {
|
||||||
|
ErpProduct product = new ErpProduct();
|
||||||
|
|
||||||
|
// 管家商品ID
|
||||||
|
Long productId = productJson.getLong("product_id");
|
||||||
|
if (productId == null) {
|
||||||
|
log.warn("商品ID为空,跳过: {}", productJson);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
product.setProductId(productId);
|
||||||
|
|
||||||
|
// 商品标题
|
||||||
|
product.setTitle(productJson.getString("title"));
|
||||||
|
|
||||||
|
// 商品图片(取第一张)
|
||||||
|
JSONArray images = productJson.getJSONArray("images");
|
||||||
|
if (images != null && !images.isEmpty()) {
|
||||||
|
product.setMainImage(images.getString(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 价格(分)
|
||||||
|
Long price = productJson.getLong("price");
|
||||||
|
if (price == null) {
|
||||||
|
// 尝试从price字段解析
|
||||||
|
Object priceObj = productJson.get("price");
|
||||||
|
if (priceObj instanceof Number) {
|
||||||
|
price = ((Number) priceObj).longValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
product.setPrice(price);
|
||||||
|
|
||||||
|
// 库存
|
||||||
|
Integer stock = productJson.getInteger("stock");
|
||||||
|
product.setStock(stock);
|
||||||
|
|
||||||
|
// 商品状态
|
||||||
|
Integer productStatus = productJson.getInteger("product_status");
|
||||||
|
product.setProductStatus(productStatus);
|
||||||
|
|
||||||
|
// 销售状态
|
||||||
|
Integer saleStatus = productJson.getInteger("sale_status");
|
||||||
|
product.setSaleStatus(saleStatus);
|
||||||
|
|
||||||
|
// 闲鱼会员名
|
||||||
|
product.setUserName(productJson.getString("user_name"));
|
||||||
|
|
||||||
|
// 时间字段(时间戳,秒)
|
||||||
|
product.setOnlineTime(productJson.getLong("online_time"));
|
||||||
|
product.setOfflineTime(productJson.getLong("offline_time"));
|
||||||
|
product.setSoldTime(productJson.getLong("sold_time"));
|
||||||
|
product.setCreateTimeXy(productJson.getLong("create_time"));
|
||||||
|
product.setUpdateTimeXy(productJson.getLong("update_time"));
|
||||||
|
|
||||||
|
// ERP应用ID
|
||||||
|
product.setAppid(appid);
|
||||||
|
|
||||||
|
// 商品链接
|
||||||
|
product.setProductUrl(productJson.getString("product_url"));
|
||||||
|
|
||||||
|
return product;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析商品JSON失败: {}", productJson, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换商品状态值:将前端状态值转换为API需要的状态值
|
||||||
|
* 实际状态值:-1(删除), 21(待发布), 22(销售中), 23(已售罄), 31(手动下架), 33(售出下架), 36(自动下架)
|
||||||
|
* API支持的状态值:-1, 10, 21, 22, 23, 31, 33, 36
|
||||||
|
* 前端传入的状态值直接使用,不做转换
|
||||||
|
*/
|
||||||
|
private Integer convertProductStatus(Integer frontendStatus) {
|
||||||
|
if (frontendStatus == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 直接使用前端传入的状态值(-1, 21, 22, 23, 31, 33, 36)
|
||||||
|
// API支持的状态值列表
|
||||||
|
if (frontendStatus == -1 || frontendStatus == 10 || frontendStatus == 21 ||
|
||||||
|
frontendStatus == 22 || frontendStatus == 23 || frontendStatus == 31 ||
|
||||||
|
frontendStatus == 33 || frontendStatus == 36) {
|
||||||
|
return frontendStatus;
|
||||||
|
}
|
||||||
|
log.warn("未知的商品状态值: {}, 将不设置状态筛选", frontendStatus);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量同步商品列表(自动遍历所有页码,同步更新和删除)
|
||||||
|
*/
|
||||||
|
@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账号
|
||||||
|
*/
|
||||||
|
private ERPAccount resolveAccount(String appid) {
|
||||||
|
if (appid != null && !appid.isEmpty()) {
|
||||||
|
for (ERPAccount account : ERPAccount.values()) {
|
||||||
|
if (account.getApiKey().equals(appid)) {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ERPAccount.ACCOUNT_HUGE; // 默认账号
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,13 +5,17 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
import com.ruoyi.common.utils.http.HttpUtils;
|
import com.ruoyi.common.utils.http.HttpUtils;
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
import com.ruoyi.jarvis.service.ILogisticsService;
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,13 +26,19 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(LogisticsServiceImpl.class);
|
private static final Logger logger = LoggerFactory.getLogger(LogisticsServiceImpl.class);
|
||||||
|
|
||||||
private static final String REDIS_WAYBILL_KEY_PREFIX = "logistics:waybill:order:";
|
private static final String REDIS_WAYBILL_KEY_PREFIX = "logistics:waybill:order:";
|
||||||
|
private static final String REDIS_LOCK_KEY_PREFIX = "logistics:lock:order:";
|
||||||
private static final String EXTERNAL_API_URL = "http://192.168.8.88:5001/fetch_logistics?tracking_url=";
|
private static final String EXTERNAL_API_URL = "http://192.168.8.88:5001/fetch_logistics?tracking_url=";
|
||||||
private static final String PUSH_URL = "https://wxts.van333.cn/wx/send/pdd";
|
private static final String PUSH_URL = "https://wxts.van333.cn/wx/send/pdd";
|
||||||
private static final String PUSH_TOKEN = "super_token_b62190c26";
|
private static final String PUSH_TOKEN = "super_token_b62190c26";
|
||||||
|
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
||||||
|
private static final long LOCK_EXPIRE_SECONDS = 300; // 锁过期时间5分钟,防止死锁
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysConfigService sysConfigService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOrderProcessed(Long orderId) {
|
public boolean isOrderProcessed(Long orderId) {
|
||||||
if (orderId == null) {
|
if (orderId == null) {
|
||||||
@@ -45,27 +55,49 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查物流链接
|
Long orderId = order.getId();
|
||||||
String logisticsLink = order.getLogisticsLink();
|
|
||||||
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
// 双重检查:先检查是否已处理过
|
||||||
logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", order.getId());
|
if (isOrderProcessed(orderId)) {
|
||||||
return false;
|
logger.info("订单已处理过,跳过 - 订单ID: {}", orderId);
|
||||||
|
return true; // 返回true表示已处理,避免重复处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分布式锁,防止并发处理同一订单
|
||||||
|
String lockKey = REDIS_LOCK_KEY_PREFIX + orderId;
|
||||||
|
Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if (Boolean.FALSE.equals(lockAcquired)) {
|
||||||
|
logger.warn("订单正在被其他线程处理,跳过 - 订单ID: {}", orderId);
|
||||||
|
return false; // 其他线程正在处理,返回false让调用方稍后重试
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 获取锁后再次检查是否已处理(双重检查锁定模式)
|
||||||
|
if (isOrderProcessed(orderId)) {
|
||||||
|
logger.info("订单在获取锁后检查发现已处理,跳过 - 订单ID: {}", orderId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查物流链接
|
||||||
|
String logisticsLink = order.getLogisticsLink();
|
||||||
|
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||||
|
logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", orderId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// 构建外部接口URL
|
// 构建外部接口URL
|
||||||
String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8");
|
String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8");
|
||||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", order.getId(), externalUrl);
|
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
||||||
|
|
||||||
// 在服务端执行HTTP请求
|
// 在服务端执行HTTP请求
|
||||||
String result = HttpUtils.sendGet(externalUrl);
|
String result = HttpUtils.sendGet(externalUrl);
|
||||||
|
|
||||||
if (result == null || result.trim().isEmpty()) {
|
if (result == null || result.trim().isEmpty()) {
|
||||||
logger.warn("外部接口返回空结果 - 订单ID: {}", order.getId());
|
logger.warn("外部接口返回空结果 - 订单ID: {}", orderId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", order.getId(), result.length());
|
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", orderId, result.length());
|
||||||
|
|
||||||
// 解析返回结果
|
// 解析返回结果
|
||||||
JSONObject parsedData = null;
|
JSONObject parsedData = null;
|
||||||
@@ -74,42 +106,97 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
if (parsed instanceof JSONObject) {
|
if (parsed instanceof JSONObject) {
|
||||||
parsedData = (JSONObject) parsed;
|
parsedData = (JSONObject) parsed;
|
||||||
} else {
|
} else {
|
||||||
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", order.getId());
|
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", orderId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage());
|
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查waybill_no
|
// 检查waybill_no
|
||||||
JSONObject dataObj = parsedData.getJSONObject("data");
|
JSONObject dataObj = parsedData.getJSONObject("data");
|
||||||
if (dataObj == null) {
|
if (dataObj == null) {
|
||||||
logger.info("返回数据中没有data字段 - 订单ID: {}", order.getId());
|
logger.info("返回数据中没有data字段 - 订单ID: {}", orderId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String waybillNo = dataObj.getString("waybill_no");
|
String waybillNo = dataObj.getString("waybill_no");
|
||||||
if (waybillNo == null || waybillNo.trim().isEmpty()) {
|
if (waybillNo == null || waybillNo.trim().isEmpty()) {
|
||||||
logger.info("waybill_no为空,无需处理 - 订单ID: {}", order.getId());
|
logger.info("waybill_no为空,无需处理 - 订单ID: {}", orderId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, order.getId());
|
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, orderId);
|
||||||
|
|
||||||
// 保存运单号到Redis(避免重复处理)
|
// 兼容处理:检查Redis中是否已有该订单的运单号记录
|
||||||
String redisKey = REDIS_WAYBILL_KEY_PREFIX + order.getId();
|
// 如果存在且运单号一致,说明之前已经推送过了(可能是之前没有配置接收人但推送成功的情况)
|
||||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
String redisKey = REDIS_WAYBILL_KEY_PREFIX + orderId;
|
||||||
|
String existingWaybillNo = stringRedisTemplate.opsForValue().get(redisKey);
|
||||||
|
|
||||||
// 调用企业应用推送
|
if (existingWaybillNo != null && existingWaybillNo.trim().equals(waybillNo.trim())) {
|
||||||
sendEnterprisePushNotification(order, waybillNo);
|
// 运单号一致,说明之前已经推送过了,直接标记为已处理,跳过推送
|
||||||
|
logger.info("订单运单号已存在且一致,说明之前已推送过,跳过重复推送 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||||
|
// 更新过期时间,确保记录不会过期
|
||||||
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
// 兼容处理:如果Redis中没有记录,但订单创建时间在30天之前,且获取到了运单号
|
||||||
|
// 说明可能是之前推送过但没标记的情况(比如之前没有配置接收人但推送成功,响应解析失败)
|
||||||
|
// 这种情况下,直接标记为已处理,跳过推送,避免重复推送旧订单
|
||||||
|
if (existingWaybillNo == null && order.getCreateTime() != null) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, -30); // 30天前
|
||||||
|
Date thresholdDate = calendar.getTime();
|
||||||
|
|
||||||
|
if (order.getCreateTime().before(thresholdDate)) {
|
||||||
|
// 订单创建时间在30天之前,且Redis中没有记录,但获取到了运单号
|
||||||
|
// 视为之前已推送过但未标记,直接标记为已处理,跳过推送
|
||||||
|
logger.info("订单创建时间较早({}),且Redis中无记录但已获取到运单号,视为之前已推送过,直接标记为已处理,跳过推送 - 订单ID: {}, waybill_no: {}",
|
||||||
|
order.getCreateTime(), orderId, waybillNo);
|
||||||
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果Redis中有记录但运单号不一致,记录警告
|
||||||
|
if (existingWaybillNo != null && !existingWaybillNo.trim().equals(waybillNo.trim())) {
|
||||||
|
logger.warn("订单运单号发生变化 - 订单ID: {}, 旧运单号: {}, 新运单号: {}, 将重新推送",
|
||||||
|
orderId, existingWaybillNo, waybillNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用企业应用推送,只有推送成功才记录状态
|
||||||
|
boolean pushSuccess = sendEnterprisePushNotification(order, waybillNo);
|
||||||
|
if (!pushSuccess) {
|
||||||
|
logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存运单号到Redis(避免重复处理)- 使用原子操作确保只写入一次
|
||||||
|
Boolean setSuccess = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
if (Boolean.FALSE.equals(setSuccess)) {
|
||||||
|
// 如果Redis中已存在,说明可能被其他线程处理了,记录警告但不算失败
|
||||||
|
logger.warn("订单运单号已存在(可能被并发处理),但推送已成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||||
|
// 更新过期时间,确保记录不会过期
|
||||||
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e);
|
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage(), e);
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
// 释放分布式锁
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.delete(lockKey);
|
||||||
|
logger.debug("释放订单处理锁 - 订单ID: {}", orderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("释放订单处理锁失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +205,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
* @param order 订单信息
|
* @param order 订单信息
|
||||||
* @param waybillNo 运单号
|
* @param waybillNo 运单号
|
||||||
*/
|
*/
|
||||||
private void sendEnterprisePushNotification(JDOrder order, String waybillNo) {
|
private boolean sendEnterprisePushNotification(JDOrder order, String waybillNo) {
|
||||||
try {
|
try {
|
||||||
// 构建推送消息内容
|
// 构建推送消息内容
|
||||||
StringBuilder pushContent = new StringBuilder();
|
StringBuilder pushContent = new StringBuilder();
|
||||||
@@ -127,12 +214,9 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
String distributionMark = order.getDistributionMark() != null ? order.getDistributionMark() : "未知";
|
String distributionMark = order.getDistributionMark() != null ? order.getDistributionMark() : "未知";
|
||||||
pushContent.append(distributionMark).append("\n");
|
pushContent.append(distributionMark).append("\n");
|
||||||
|
|
||||||
// PDD订单包含第三方单号,F订单不包含
|
String thirdPartyOrderNo = order.getThirdPartyOrderNo();
|
||||||
if ("PDD".equals(distributionMark)) {
|
if (thirdPartyOrderNo != null && !thirdPartyOrderNo.trim().isEmpty()) {
|
||||||
String thirdPartyOrderNo = order.getThirdPartyOrderNo();
|
pushContent.append("第三方单号:").append(thirdPartyOrderNo).append("\n");
|
||||||
if (thirdPartyOrderNo != null && !thirdPartyOrderNo.trim().isEmpty()) {
|
|
||||||
pushContent.append("第三方单号:").append(thirdPartyOrderNo).append("\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 型号
|
// 型号
|
||||||
@@ -140,22 +224,182 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
// 收货地址
|
// 收货地址
|
||||||
pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n");
|
pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n");
|
||||||
// 运单号
|
// 运单号
|
||||||
pushContent.append("运单号:").append(waybillNo).append("\n");
|
pushContent.append("运单号:").append("\n").append("\n").append("\n").append("\n").append(waybillNo).append("\n");
|
||||||
|
|
||||||
|
|
||||||
// 调用企业微信推送接口
|
// 调用企业微信推送接口
|
||||||
JSONObject pushParam = new JSONObject();
|
JSONObject pushParam = new JSONObject();
|
||||||
pushParam.put("title", "JD物流信息推送");
|
pushParam.put("title", "JD物流信息推送");
|
||||||
pushParam.put("text", pushContent.toString());
|
pushParam.put("text", pushContent.toString());
|
||||||
|
|
||||||
|
// 根据分销标识获取接收人列表
|
||||||
|
String touser = getTouserByDistributionMark(distributionMark);
|
||||||
|
if (StringUtils.hasText(touser)) {
|
||||||
|
pushParam.put("touser", touser);
|
||||||
|
logger.info("企业微信推送设置接收人 - 订单ID: {}, 分销标识: {}, 接收人: {}",
|
||||||
|
order.getId(), distributionMark, touser);
|
||||||
|
} else {
|
||||||
|
// 未配置接收人时,使用远程接口的默认接收人,这是正常情况
|
||||||
|
logger.info("未找到分销标识对应的接收人配置,将使用远程接口默认接收人 - 订单ID: {}, 分销标识: {}",
|
||||||
|
order.getId(), distributionMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录完整的推送参数(用于调试)
|
||||||
|
String jsonBody = pushParam.toJSONString();
|
||||||
|
logger.info("企业微信推送完整参数 - 订单ID: {}, JSON: {}", order.getId(), jsonBody);
|
||||||
|
|
||||||
// 使用支持自定义header的HTTP请求
|
// 使用支持自定义header的HTTP请求
|
||||||
String pushResult = sendPostWithHeaders(PUSH_URL, pushParam.toJSONString(), PUSH_TOKEN);
|
String pushResult = sendPostWithHeaders(PUSH_URL, jsonBody, PUSH_TOKEN);
|
||||||
logger.info("企业应用推送调用结果 - 订单ID: {}, waybill_no: {}, 推送结果: {}",
|
if (pushResult == null || pushResult.trim().isEmpty()) {
|
||||||
order.getId(), waybillNo, pushResult);
|
logger.warn("企业应用推送响应为空 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = isPushResponseSuccess(pushResult);
|
||||||
|
if (success) {
|
||||||
|
logger.info("企业应用推送成功 - 订单ID: {}, waybill_no: {}, 推送结果: {}",
|
||||||
|
order.getId(), waybillNo, pushResult);
|
||||||
|
} else {
|
||||||
|
logger.warn("企业应用推送响应未确认成功 - 订单ID: {}, waybill_no: {}, 响应: {}",
|
||||||
|
order.getId(), waybillNo, pushResult);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("调用企业应用推送失败 - 订单ID: {}, waybill_no: {}, 错误: {}",
|
logger.error("调用企业应用推送失败 - 订单ID: {}, waybill_no: {}, 错误: {}",
|
||||||
order.getId(), waybillNo, e.getMessage(), e);
|
order.getId(), waybillNo, e.getMessage(), e);
|
||||||
// 不抛出异常,避免影响主流程
|
// 不抛出异常,主流程根据返回值决定是否重试
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分销标识获取接收人列表
|
||||||
|
* 从系统配置中读取,配置键名格式:logistics.push.touser.{分销标识}
|
||||||
|
* 配置值格式:接收人1,接收人2,接收人3(逗号分隔)
|
||||||
|
*
|
||||||
|
* @param distributionMark 分销标识
|
||||||
|
* @return 接收人列表(逗号分隔),如果未配置则返回null
|
||||||
|
*/
|
||||||
|
private String getTouserByDistributionMark(String distributionMark) {
|
||||||
|
if (!StringUtils.hasText(distributionMark)) {
|
||||||
|
logger.warn("分销标识为空,无法获取接收人配置");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建配置键名
|
||||||
|
String configKey = CONFIG_KEY_PREFIX + distributionMark.trim();
|
||||||
|
|
||||||
|
// 从系统配置中获取接收人列表
|
||||||
|
String configValue = sysConfigService.selectConfigByKey(configKey);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(configValue)) {
|
||||||
|
// 清理配置值(去除空格)
|
||||||
|
String touser = configValue.trim().replaceAll(",\\s+", ",");
|
||||||
|
logger.info("从配置获取接收人列表 - 分销标识: {}, 配置键: {}, 接收人: {}",
|
||||||
|
distributionMark, configKey, touser);
|
||||||
|
return touser;
|
||||||
|
} else {
|
||||||
|
logger.debug("未找到接收人配置 - 分销标识: {}, 配置键: {}", distributionMark, configKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取接收人配置失败 - 分销标识: {}, 错误: {}", distributionMark, e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断推送返回值是否为成功状态
|
||||||
|
* @param pushResult 推送接口返回结果
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
private boolean isPushResponseSuccess(String pushResult) {
|
||||||
|
if (pushResult == null || pushResult.trim().isEmpty()) {
|
||||||
|
logger.warn("推送响应为空,视为失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject response = JSON.parseObject(pushResult);
|
||||||
|
if (response == null) {
|
||||||
|
logger.warn("推送响应解析为null,视为失败。原始响应: {}", pushResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer code = response.getInteger("code");
|
||||||
|
Boolean successFlag = response.getBoolean("success");
|
||||||
|
String status = response.getString("status");
|
||||||
|
String message = response.getString("msg");
|
||||||
|
String errcode = response.getString("errcode");
|
||||||
|
|
||||||
|
// 记录完整的响应信息用于调试
|
||||||
|
logger.debug("推送响应解析 - code: {}, success: {}, status: {}, msg: {}, errcode: {}",
|
||||||
|
code, successFlag, status, message, errcode);
|
||||||
|
|
||||||
|
// 检查错误码(errcode为0表示成功)
|
||||||
|
if (errcode != null && "0".equals(errcode)) {
|
||||||
|
logger.info("推送成功(通过errcode=0判断)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查code字段(0或200表示成功)
|
||||||
|
if (code != null && (code == 0 || code == 200)) {
|
||||||
|
logger.info("推送成功(通过code={}判断)", code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查success字段
|
||||||
|
if (Boolean.TRUE.equals(successFlag)) {
|
||||||
|
logger.info("推送成功(通过success=true判断)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查status字段
|
||||||
|
if (status != null && ("success".equalsIgnoreCase(status) || "ok".equalsIgnoreCase(status))) {
|
||||||
|
logger.info("推送成功(通过status={}判断)", status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查message字段(某些接口可能用message表示成功)
|
||||||
|
if (message != null && ("success".equalsIgnoreCase(message) || "ok".equalsIgnoreCase(message))) {
|
||||||
|
logger.info("推送成功(通过message={}判断)", message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含明确的错误标识
|
||||||
|
String responseStr = pushResult.toLowerCase();
|
||||||
|
boolean hasErrorKeyword = responseStr.contains("\"error\"") || responseStr.contains("\"fail\"") ||
|
||||||
|
responseStr.contains("\"failed\"") || responseStr.contains("\"errmsg\"");
|
||||||
|
|
||||||
|
// 如果包含错误标识,检查是否有明确的错误码
|
||||||
|
if (hasErrorKeyword) {
|
||||||
|
if (code != null && code < 0) {
|
||||||
|
logger.warn("推送失败(检测到错误标识和负错误码) - code: {}, 完整响应: {}", code, pushResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (errcode != null && !"0".equals(errcode) && !errcode.isEmpty()) {
|
||||||
|
logger.warn("推送失败(检测到错误标识和非零错误码) - errcode: {}, 完整响应: {}", errcode, pushResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果响应是有效的JSON,且没有明确的错误标识,视为成功
|
||||||
|
// 因为远程接口有默认接收人,即使没有配置接收人,推送也应该成功
|
||||||
|
// 如果响应格式特殊(不是标准格式),只要没有错误,也视为成功
|
||||||
|
if (!hasErrorKeyword) {
|
||||||
|
logger.info("推送成功(响应格式有效且无错误标识,使用默认接收人) - 完整响应: {}", pushResult);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有判断都失败,记录详细信息
|
||||||
|
logger.warn("推送响应未确认成功 - code: {}, success: {}, status: {}, msg: {}, errcode: {}, 完整响应: {}",
|
||||||
|
code, successFlag, status, message, errcode, pushResult);
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("解析企业应用推送响应失败,将视为未成功。原始响应: {}, 错误: {}", pushResult, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
package com.ruoyi.jarvis.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.utils.http.HttpUtils;
|
||||||
|
import com.ruoyi.jarvis.service.ISocialMediaService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小红书/抖音内容生成Service业务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-XX
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SocialMediaServiceImpl implements ISocialMediaService
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SocialMediaServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private StringRedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
// jarvis_java 服务地址
|
||||||
|
private final static String JARVIS_BASE_URL = "http://192.168.8.88:6666";
|
||||||
|
|
||||||
|
// Redis Key 前缀
|
||||||
|
private static final String REDIS_KEY_PREFIX = "social_media:prompt:";
|
||||||
|
|
||||||
|
// 模板键名列表
|
||||||
|
private static final String[] TEMPLATE_KEYS = {
|
||||||
|
"keywords",
|
||||||
|
"content:xhs",
|
||||||
|
"content:douyin",
|
||||||
|
"content:both"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模板说明
|
||||||
|
private static final Map<String, String> TEMPLATE_DESCRIPTIONS = new HashMap<String, String>() {{
|
||||||
|
put("keywords", "关键词提取提示词模板\n占位符:%s - 商品名称");
|
||||||
|
put("content:xhs", "小红书文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息");
|
||||||
|
put("content:douyin", "抖音文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息");
|
||||||
|
put("content:both", "通用文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息");
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取商品标题关键词
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> extractKeywords(String productName) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(productName)) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "商品名称不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用 jarvis_java 的接口
|
||||||
|
String url = JARVIS_BASE_URL + "/jarvis/social-media/extract-keywords";
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
requestBody.put("productName", productName);
|
||||||
|
|
||||||
|
log.info("调用jarvis_java提取关键词接口,URL: {}, 参数: {}", url, requestBody.toJSONString());
|
||||||
|
String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
|
||||||
|
log.info("jarvis_java响应: {}", response);
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(response)) {
|
||||||
|
throw new Exception("jarvis_java返回空结果");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
Object parsed = JSON.parse(response);
|
||||||
|
if (parsed instanceof JSONObject) {
|
||||||
|
JSONObject jsonResponse = (JSONObject) parsed;
|
||||||
|
if (jsonResponse.getInteger("code") == 200) {
|
||||||
|
Object data = jsonResponse.get("data");
|
||||||
|
if (data instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> dataMap = (Map<String, Object>) data;
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String msg = jsonResponse.getString("msg");
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", msg != null ? msg : "提取关键词失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "响应格式错误");
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("提取关键词失败", e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "提取关键词失败: " + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成文案
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> generateContent(String productName, Object originalPrice,
|
||||||
|
Object finalPrice, String keywords, String style) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(productName)) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "商品名称不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用 jarvis_java 的接口
|
||||||
|
String url = JARVIS_BASE_URL + "/jarvis/social-media/generate-content";
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
requestBody.put("productName", productName);
|
||||||
|
if (originalPrice != null) {
|
||||||
|
requestBody.put("originalPrice", parseDouble(originalPrice));
|
||||||
|
}
|
||||||
|
if (finalPrice != null) {
|
||||||
|
requestBody.put("finalPrice", parseDouble(finalPrice));
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(keywords)) {
|
||||||
|
requestBody.put("keywords", keywords);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(style)) {
|
||||||
|
requestBody.put("style", style);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("调用jarvis_java生成文案接口,URL: {}, 参数: {}", url, requestBody.toJSONString());
|
||||||
|
String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
|
||||||
|
log.info("jarvis_java响应: {}", response);
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(response)) {
|
||||||
|
throw new Exception("jarvis_java返回空结果");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
Object parsed = JSON.parse(response);
|
||||||
|
if (parsed instanceof JSONObject) {
|
||||||
|
JSONObject jsonResponse = (JSONObject) parsed;
|
||||||
|
if (jsonResponse.getInteger("code") == 200) {
|
||||||
|
Object data = jsonResponse.get("data");
|
||||||
|
if (data instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> dataMap = (Map<String, Object>) data;
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String msg = jsonResponse.getString("msg");
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", msg != null ? msg : "生成文案失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "响应格式错误");
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成文案失败", e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "生成文案失败: " + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键生成完整内容(关键词 + 文案 + 图片)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> generateCompleteContent(String productImageUrl, String productName,
|
||||||
|
Object originalPrice, Object finalPrice, String style) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(productName)) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "商品名称不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用 jarvis_java 的接口
|
||||||
|
String url = JARVIS_BASE_URL + "/jarvis/social-media/generate-complete";
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
if (StringUtils.isNotEmpty(productImageUrl)) {
|
||||||
|
requestBody.put("productImageUrl", productImageUrl);
|
||||||
|
}
|
||||||
|
requestBody.put("productName", productName);
|
||||||
|
if (originalPrice != null) {
|
||||||
|
requestBody.put("originalPrice", parseDouble(originalPrice));
|
||||||
|
}
|
||||||
|
if (finalPrice != null) {
|
||||||
|
requestBody.put("finalPrice", parseDouble(finalPrice));
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(style)) {
|
||||||
|
requestBody.put("style", style);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("调用jarvis_java生成完整内容接口,URL: {}, 参数: {}", url, requestBody.toJSONString());
|
||||||
|
String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
|
||||||
|
log.info("jarvis_java响应: {}", response);
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(response)) {
|
||||||
|
throw new Exception("jarvis_java返回空结果");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
Object parsed = JSON.parse(response);
|
||||||
|
if (parsed instanceof JSONObject) {
|
||||||
|
JSONObject jsonResponse = (JSONObject) parsed;
|
||||||
|
if (jsonResponse.getInteger("code") == 200) {
|
||||||
|
Object data = jsonResponse.get("data");
|
||||||
|
if (data instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> dataMap = (Map<String, Object>) data;
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String msg = jsonResponse.getString("msg");
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", msg != null ? msg : "生成完整内容失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "响应格式错误");
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成完整内容失败", e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "生成完整内容失败: " + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取提示词模板列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult listPromptTemplates() {
|
||||||
|
try {
|
||||||
|
Map<String, Object> templates = new HashMap<>();
|
||||||
|
|
||||||
|
for (String key : TEMPLATE_KEYS) {
|
||||||
|
Map<String, Object> templateInfo = new HashMap<>();
|
||||||
|
templateInfo.put("key", key);
|
||||||
|
templateInfo.put("description", TEMPLATE_DESCRIPTIONS.get(key));
|
||||||
|
|
||||||
|
String template = getTemplateFromRedis(key);
|
||||||
|
templateInfo.put("template", template);
|
||||||
|
templateInfo.put("isDefault", template == null);
|
||||||
|
|
||||||
|
templates.put(key, templateInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AjaxResult.success(templates);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取提示词模板列表失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个提示词模板
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult getPromptTemplate(String key) {
|
||||||
|
try {
|
||||||
|
if (!isValidKey(key)) {
|
||||||
|
return AjaxResult.error("无效的模板键名");
|
||||||
|
}
|
||||||
|
|
||||||
|
String template = getTemplateFromRedis(key);
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("key", key);
|
||||||
|
data.put("description", TEMPLATE_DESCRIPTIONS.get(key));
|
||||||
|
data.put("template", template);
|
||||||
|
data.put("isDefault", template == null);
|
||||||
|
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取提示词模板失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存提示词模板
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult savePromptTemplate(Map<String, Object> request) {
|
||||||
|
try {
|
||||||
|
String key = (String) request.get("key");
|
||||||
|
String template = (String) request.get("template");
|
||||||
|
|
||||||
|
if (!isValidKey(key)) {
|
||||||
|
return AjaxResult.error("无效的模板键名");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(template)) {
|
||||||
|
return AjaxResult.error("模板内容不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置,无法保存模板");
|
||||||
|
}
|
||||||
|
|
||||||
|
String redisKey = REDIS_KEY_PREFIX + key;
|
||||||
|
String templateValue = template.trim();
|
||||||
|
if (StringUtils.isEmpty(templateValue)) {
|
||||||
|
return AjaxResult.error("模板内容不能为空");
|
||||||
|
}
|
||||||
|
redisTemplate.opsForValue().set(redisKey, templateValue);
|
||||||
|
|
||||||
|
log.info("保存提示词模板成功: {}", key);
|
||||||
|
return AjaxResult.success("保存成功");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("保存提示词模板失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除提示词模板(恢复默认)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult deletePromptTemplate(String key) {
|
||||||
|
try {
|
||||||
|
if (!isValidKey(key)) {
|
||||||
|
return AjaxResult.error("无效的模板键名");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置,无法删除模板");
|
||||||
|
}
|
||||||
|
|
||||||
|
String redisKey = REDIS_KEY_PREFIX + key;
|
||||||
|
redisTemplate.delete(redisKey);
|
||||||
|
|
||||||
|
log.info("删除提示词模板成功: {}", key);
|
||||||
|
return AjaxResult.success("删除成功,已恢复默认模板");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除提示词模板失败", e);
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Redis 获取模板
|
||||||
|
*/
|
||||||
|
private String getTemplateFromRedis(String key) {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String redisKey = REDIS_KEY_PREFIX + key;
|
||||||
|
String template = redisTemplate.opsForValue().get(redisKey);
|
||||||
|
return StringUtils.isNotEmpty(template) ? template : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("读取Redis模板失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证模板键名是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidKey(String key) {
|
||||||
|
if (StringUtils.isEmpty(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String validKey : TEMPLATE_KEYS) {
|
||||||
|
if (validKey.equals(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析Double值
|
||||||
|
*/
|
||||||
|
private Double parseDouble(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof Double) {
|
||||||
|
return (Double) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(value.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,12 +7,13 @@ import com.ruoyi.jarvis.service.ITencentDocDelayedPushService;
|
|||||||
import com.ruoyi.jarvis.service.ITencentDocTokenService;
|
import com.ruoyi.jarvis.service.ITencentDocTokenService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.*;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
@@ -33,7 +34,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushService {
|
public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushService, ApplicationContextAware {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(TencentDocDelayedPushServiceImpl.class);
|
private static final Logger log = LoggerFactory.getLogger(TencentDocDelayedPushServiceImpl.class);
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ITencentDocTokenService tokenService;
|
private ITencentDocTokenService tokenService;
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 延迟时间(分钟),可通过配置文件修改
|
* 延迟时间(分钟),可通过配置文件修改
|
||||||
@@ -77,6 +78,11 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
|
|||||||
*/
|
*/
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化定时任务
|
* 初始化定时任务
|
||||||
*/
|
*/
|
||||||
@@ -262,9 +268,11 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
|
|||||||
log.info("开始执行批量同步...");
|
log.info("开始执行批量同步...");
|
||||||
|
|
||||||
// 从 Redis 读取配置信息(用户通过前端配置页面设置)
|
// 从 Redis 读取配置信息(用户通过前端配置页面设置)
|
||||||
String fileId = redisCache.getCacheObject("tendoc:config:fileId");
|
// 注意:使用与 TencentDocConfigController 相同的 key 前缀
|
||||||
String sheetId = redisCache.getCacheObject("tendoc:config:sheetId");
|
final String CONFIG_KEY_PREFIX = "tencent:doc:auto:config:";
|
||||||
Integer startRow = redisCache.getCacheObject("tendoc:config:startRow");
|
String fileId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "fileId");
|
||||||
|
String sheetId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "sheetId");
|
||||||
|
Integer startRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "startRow");
|
||||||
|
|
||||||
if (startRow == null) {
|
if (startRow == null) {
|
||||||
startRow = 3; // 默认值
|
startRow = 3; // 默认值
|
||||||
@@ -288,26 +296,30 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
|
|||||||
);
|
);
|
||||||
log.info("✓ 创建批量推送记录,批次ID: {}", batchId);
|
log.info("✓ 创建批量推送记录,批次ID: {}", batchId);
|
||||||
|
|
||||||
// 使用RestTemplate调用本地Controller(内部调用,无需认证)
|
// 直接通过 ApplicationContext 获取 Controller Bean 并调用方法
|
||||||
String url = "http://localhost:30313/jarvis-api/jarvis/tendoc/fillLogisticsByOrderNo?batchId=" + batchId;
|
// 这样避免了 HTTP 调用,是后端内部方法调用
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
|
|
||||||
// 构造请求体
|
|
||||||
String requestBody = "{}";
|
|
||||||
|
|
||||||
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("调用批量同步API: {}", url);
|
log.info("开始调用批量同步方法(后端内部调用)...");
|
||||||
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
|
||||||
|
|
||||||
log.info("批量同步调用完成,HTTP状态码: {}", response.getStatusCodeValue());
|
// 获取 TencentDocController Bean
|
||||||
log.info("批量同步结果: {}", response.getBody());
|
Object controller = applicationContext.getBean("tencentDocController");
|
||||||
|
|
||||||
// 注意:由于是内部调用,401错误说明需要添加@Anonymous注解
|
// 通过反射调用 fillLogisticsByOrderNo 方法
|
||||||
// 暂时记录日志,后续需要修改Controller添加@Anonymous注解
|
java.lang.reflect.Method method = controller.getClass().getMethod(
|
||||||
|
"fillLogisticsByOrderNo",
|
||||||
|
java.util.Map.class
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构造参数
|
||||||
|
java.util.Map<String, Object> params = new java.util.HashMap<>();
|
||||||
|
params.put("batchId", batchId);
|
||||||
|
params.put("fileId", fileId);
|
||||||
|
params.put("sheetId", sheetId);
|
||||||
|
|
||||||
|
// 调用方法
|
||||||
|
Object result = method.invoke(controller, params);
|
||||||
|
|
||||||
|
log.info("✓ 批量同步执行完成,结果: {}", result);
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("批量同步调用失败", ex);
|
log.error("批量同步调用失败", ex);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class LogisticsScanTask {
|
|||||||
* 定时任务:每1小时执行一次
|
* 定时任务:每1小时执行一次
|
||||||
* Cron表达式:0 0 * * * ? 表示每小时的第0分钟执行
|
* Cron表达式:0 0 * * * ? 表示每小时的第0分钟执行
|
||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 */30 * * * ?")
|
@Scheduled(cron = "0 */20 * * * ?")
|
||||||
public void scanAndFetchLogistics() {
|
public void scanAndFetchLogistics() {
|
||||||
logger.info("========== 开始执行物流信息扫描定时任务 ==========");
|
logger.info("========== 开始执行物流信息扫描定时任务 ==========");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.jarvis.mapper.ErpProductMapper">
|
||||||
|
|
||||||
|
<resultMap type="ErpProduct" id="ErpProductResult">
|
||||||
|
<result property="id" column="id" />
|
||||||
|
<result property="productId" column="product_id" />
|
||||||
|
<result property="title" column="title" />
|
||||||
|
<result property="mainImage" column="main_image" />
|
||||||
|
<result property="price" column="price" />
|
||||||
|
<result property="stock" column="stock" />
|
||||||
|
<result property="productStatus" column="product_status" />
|
||||||
|
<result property="saleStatus" column="sale_status" />
|
||||||
|
<result property="userName" column="user_name" />
|
||||||
|
<result property="onlineTime" column="online_time" />
|
||||||
|
<result property="offlineTime" column="offline_time" />
|
||||||
|
<result property="soldTime" column="sold_time" />
|
||||||
|
<result property="createTimeXy" column="create_time_xy" />
|
||||||
|
<result property="updateTimeXy" column="update_time_xy" />
|
||||||
|
<result property="appid" column="appid" />
|
||||||
|
<result property="productUrl" column="product_url" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectErpProductVo">
|
||||||
|
select id, product_id, title, main_image, price, stock, product_status, sale_status,
|
||||||
|
user_name, online_time, offline_time, sold_time, create_time_xy, update_time_xy,
|
||||||
|
appid, product_url, remark, create_time, update_time
|
||||||
|
from erp_product
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectErpProductList" parameterType="ErpProduct" resultMap="ErpProductResult">
|
||||||
|
<include refid="selectErpProductVo"/>
|
||||||
|
<where>
|
||||||
|
<if test="productId != null "> and product_id = #{productId}</if>
|
||||||
|
<if test="title != null and title != ''"> and title like concat('%', #{title}, '%')</if>
|
||||||
|
<if test="productStatus != null "> and product_status = #{productStatus}</if>
|
||||||
|
<if test="saleStatus != null "> and sale_status = #{saleStatus}</if>
|
||||||
|
<if test="userName != null and userName != ''"> and user_name = #{userName}</if>
|
||||||
|
<if test="appid != null and appid != ''"> and appid = #{appid}</if>
|
||||||
|
</where>
|
||||||
|
order by update_time_xy desc, id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectErpProductById" parameterType="Long" resultMap="ErpProductResult">
|
||||||
|
<include refid="selectErpProductVo"/>
|
||||||
|
where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectErpProductByProductIdAndAppid" resultMap="ErpProductResult">
|
||||||
|
<include refid="selectErpProductVo"/>
|
||||||
|
where product_id = #{productId} and appid = #{appid}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertErpProduct" parameterType="ErpProduct" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into erp_product
|
||||||
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
|
<if test="productId != null">product_id,</if>
|
||||||
|
<if test="title != null">title,</if>
|
||||||
|
<if test="mainImage != null">main_image,</if>
|
||||||
|
<if test="price != null">price,</if>
|
||||||
|
<if test="stock != null">stock,</if>
|
||||||
|
<if test="productStatus != null">product_status,</if>
|
||||||
|
<if test="saleStatus != null">sale_status,</if>
|
||||||
|
<if test="userName != null">user_name,</if>
|
||||||
|
<if test="onlineTime != null">online_time,</if>
|
||||||
|
<if test="offlineTime != null">offline_time,</if>
|
||||||
|
<if test="soldTime != null">sold_time,</if>
|
||||||
|
<if test="createTimeXy != null">create_time_xy,</if>
|
||||||
|
<if test="updateTimeXy != null">update_time_xy,</if>
|
||||||
|
<if test="appid != null">appid,</if>
|
||||||
|
<if test="productUrl != null">product_url,</if>
|
||||||
|
<if test="remark != null">remark,</if>
|
||||||
|
<if test="createTime != null">create_time,</if>
|
||||||
|
<if test="updateTime != null">update_time,</if>
|
||||||
|
</trim>
|
||||||
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
|
<if test="productId != null">#{productId},</if>
|
||||||
|
<if test="title != null">#{title},</if>
|
||||||
|
<if test="mainImage != null">#{mainImage},</if>
|
||||||
|
<if test="price != null">#{price},</if>
|
||||||
|
<if test="stock != null">#{stock},</if>
|
||||||
|
<if test="productStatus != null">#{productStatus},</if>
|
||||||
|
<if test="saleStatus != null">#{saleStatus},</if>
|
||||||
|
<if test="userName != null">#{userName},</if>
|
||||||
|
<if test="onlineTime != null">#{onlineTime},</if>
|
||||||
|
<if test="offlineTime != null">#{offlineTime},</if>
|
||||||
|
<if test="soldTime != null">#{soldTime},</if>
|
||||||
|
<if test="createTimeXy != null">#{createTimeXy},</if>
|
||||||
|
<if test="updateTimeXy != null">#{updateTimeXy},</if>
|
||||||
|
<if test="appid != null">#{appid},</if>
|
||||||
|
<if test="productUrl != null">#{productUrl},</if>
|
||||||
|
<if test="remark != null">#{remark},</if>
|
||||||
|
<if test="createTime != null">#{createTime},</if>
|
||||||
|
<if test="updateTime != null">#{updateTime},</if>
|
||||||
|
</trim>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateErpProduct" parameterType="ErpProduct">
|
||||||
|
update erp_product
|
||||||
|
<trim prefix="SET" suffixOverrides=",">
|
||||||
|
<if test="productId != null">product_id = #{productId},</if>
|
||||||
|
<if test="title != null">title = #{title},</if>
|
||||||
|
<if test="mainImage != null">main_image = #{mainImage},</if>
|
||||||
|
<if test="price != null">price = #{price},</if>
|
||||||
|
<if test="stock != null">stock = #{stock},</if>
|
||||||
|
<if test="productStatus != null">product_status = #{productStatus},</if>
|
||||||
|
<if test="saleStatus != null">sale_status = #{saleStatus},</if>
|
||||||
|
<if test="userName != null">user_name = #{userName},</if>
|
||||||
|
<if test="onlineTime != null">online_time = #{onlineTime},</if>
|
||||||
|
<if test="offlineTime != null">offline_time = #{offlineTime},</if>
|
||||||
|
<if test="soldTime != null">sold_time = #{soldTime},</if>
|
||||||
|
<if test="createTimeXy != null">create_time_xy = #{createTimeXy},</if>
|
||||||
|
<if test="updateTimeXy != null">update_time_xy = #{updateTimeXy},</if>
|
||||||
|
<if test="appid != null">appid = #{appid},</if>
|
||||||
|
<if test="productUrl != null">product_url = #{productUrl},</if>
|
||||||
|
<if test="remark != null">remark = #{remark},</if>
|
||||||
|
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||||
|
</trim>
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteErpProductById" parameterType="Long">
|
||||||
|
delete from erp_product where id = #{id}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<delete id="deleteErpProductByIds" parameterType="String">
|
||||||
|
delete from erp_product where id in
|
||||||
|
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||||
|
#{id}
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<insert id="batchInsertOrUpdateErpProduct" parameterType="java.util.List">
|
||||||
|
insert into erp_product
|
||||||
|
(product_id, title, main_image, price, stock, product_status, sale_status,
|
||||||
|
user_name, online_time, offline_time, sold_time, create_time_xy, update_time_xy,
|
||||||
|
appid, product_url, remark)
|
||||||
|
values
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(#{item.productId}, #{item.title}, #{item.mainImage}, #{item.price}, #{item.stock},
|
||||||
|
#{item.productStatus}, #{item.saleStatus}, #{item.userName}, #{item.onlineTime},
|
||||||
|
#{item.offlineTime}, #{item.soldTime}, #{item.createTimeXy}, #{item.updateTimeXy},
|
||||||
|
#{item.appid}, #{item.productUrl}, #{item.remark})
|
||||||
|
</foreach>
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
title = VALUES(title),
|
||||||
|
main_image = VALUES(main_image),
|
||||||
|
price = VALUES(price),
|
||||||
|
stock = VALUES(stock),
|
||||||
|
product_status = VALUES(product_status),
|
||||||
|
sale_status = VALUES(sale_status),
|
||||||
|
user_name = VALUES(user_name),
|
||||||
|
online_time = VALUES(online_time),
|
||||||
|
offline_time = VALUES(offline_time),
|
||||||
|
sold_time = VALUES(sold_time),
|
||||||
|
create_time_xy = VALUES(create_time_xy),
|
||||||
|
update_time_xy = VALUES(update_time_xy),
|
||||||
|
product_url = VALUES(product_url),
|
||||||
|
remark = VALUES(remark),
|
||||||
|
update_time = NOW()
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -23,11 +23,18 @@
|
|||||||
<result property="isCountEnabled" column="is_count_enabled"/>
|
<result property="isCountEnabled" column="is_count_enabled"/>
|
||||||
<result property="thirdPartyOrderNo" column="third_party_order_no"/>
|
<result property="thirdPartyOrderNo" column="third_party_order_no"/>
|
||||||
<result property="jingfenActualPrice" column="jingfen_actual_price"/>
|
<result property="jingfenActualPrice" column="jingfen_actual_price"/>
|
||||||
|
<result property="isRefunded" column="is_refunded"/>
|
||||||
|
<result property="refundDate" column="refund_date"/>
|
||||||
|
<result property="isRefundReceived" column="is_refund_received"/>
|
||||||
|
<result property="refundReceivedDate" column="refund_received_date"/>
|
||||||
|
<result property="isRebateReceived" column="is_rebate_received"/>
|
||||||
|
<result property="rebateReceivedDate" column="rebate_received_date"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectJDOrderBase">
|
<sql id="selectJDOrderBase">
|
||||||
select id, remark, distribution_mark, model_number, link, payment_amount, rebate_amount,
|
select id, remark, distribution_mark, model_number, link, payment_amount, rebate_amount,
|
||||||
address, logistics_link, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price
|
address, logistics_link, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price,
|
||||||
|
is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date
|
||||||
from jd_order
|
from jd_order
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@@ -35,17 +42,26 @@
|
|||||||
<include refid="selectJDOrderBase"/>
|
<include refid="selectJDOrderBase"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="remark != null and remark != ''"> and remark like concat('%', #{remark}, '%')</if>
|
<if test="remark != null and remark != ''"> and remark like concat('%', #{remark}, '%')</if>
|
||||||
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark like concat('%', #{distributionMark}, '%')</if>
|
<if test="params.orderSearch != null and params.orderSearch != ''">
|
||||||
|
and (
|
||||||
|
order_id like concat('%', #{params.orderSearch}, '%')
|
||||||
|
or third_party_order_no like concat('%', #{params.orderSearch}, '%')
|
||||||
|
or distribution_mark like concat('%', #{params.orderSearch}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if>
|
||||||
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if>
|
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if>
|
||||||
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
||||||
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
||||||
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
|
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
|
||||||
<if test="address != null and address != ''"> and address like concat('%', #{address}, '%')</if>
|
<if test="address != null and address != ''"> and address like concat('%', #{address}, '%')</if>
|
||||||
<if test="logisticsLink != null and logisticsLink != ''"> and logistics_link like concat('%', #{logisticsLink}, '%')</if>
|
<if test="logisticsLink != null and logisticsLink != ''"> and logistics_link like concat('%', #{logisticsLink}, '%')</if>
|
||||||
<if test="orderId != null and orderId != ''"> and order_id like concat('%', #{orderId}, '%')</if>
|
|
||||||
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
||||||
<if test="orderTime != null"> and order_time = #{orderTime}</if>
|
<if test="orderTime != null"> and order_time = #{orderTime}</if>
|
||||||
<if test="status != null and status != ''"> and status like concat('%', #{status}, '%')</if>
|
<if test="status != null and status != ''"> and status like concat('%', #{status}, '%')</if>
|
||||||
|
<if test="isRefunded != null"> and is_refunded = #{isRefunded}</if>
|
||||||
|
<if test="isRefundReceived != null"> and is_refund_received = #{isRefundReceived}</if>
|
||||||
|
<if test="isRebateReceived != null"> and is_rebate_received = #{isRebateReceived}</if>
|
||||||
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
|
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
|
||||||
and date(order_time) >= #{params.beginTime}
|
and date(order_time) >= #{params.beginTime}
|
||||||
</if>
|
</if>
|
||||||
@@ -60,17 +76,26 @@
|
|||||||
<include refid="selectJDOrderBase"/>
|
<include refid="selectJDOrderBase"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="remark != null and remark != ''"> and remark like concat('%', #{remark}, '%')</if>
|
<if test="remark != null and remark != ''"> and remark like concat('%', #{remark}, '%')</if>
|
||||||
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark like concat('%', #{distributionMark}, '%')</if>
|
<if test="params.orderSearch != null and params.orderSearch != ''">
|
||||||
|
and (
|
||||||
|
order_id like concat('%', #{params.orderSearch}, '%')
|
||||||
|
or third_party_order_no like concat('%', #{params.orderSearch}, '%')
|
||||||
|
or distribution_mark like concat('%', #{params.orderSearch}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if>
|
||||||
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if>
|
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if>
|
||||||
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
||||||
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
||||||
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
|
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
|
||||||
<if test="address != null and address != ''"> and address like concat('%', #{address}, '%')</if>
|
<if test="address != null and address != ''"> and address like concat('%', #{address}, '%')</if>
|
||||||
<if test="logisticsLink != null and logisticsLink != ''"> and logistics_link like concat('%', #{logisticsLink}, '%')</if>
|
<if test="logisticsLink != null and logisticsLink != ''"> and logistics_link like concat('%', #{logisticsLink}, '%')</if>
|
||||||
<if test="orderId != null and orderId != ''"> and order_id like concat('%', #{orderId}, '%')</if>
|
|
||||||
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
||||||
<if test="orderTime != null"> and order_time = #{orderTime}</if>
|
<if test="orderTime != null"> and order_time = #{orderTime}</if>
|
||||||
<if test="status != null and status != ''"> and status like concat('%', #{status}, '%')</if>
|
<if test="status != null and status != ''"> and status like concat('%', #{status}, '%')</if>
|
||||||
|
<if test="isRefunded != null"> and is_refunded = #{isRefunded}</if>
|
||||||
|
<if test="isRefundReceived != null"> and is_refund_received = #{isRefundReceived}</if>
|
||||||
|
<if test="isRebateReceived != null"> and is_rebate_received = #{isRebateReceived}</if>
|
||||||
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
|
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
|
||||||
and date(order_time) >= #{params.beginTime}
|
and date(order_time) >= #{params.beginTime}
|
||||||
</if>
|
</if>
|
||||||
@@ -100,12 +125,14 @@
|
|||||||
remark, distribution_mark, model_number, link,
|
remark, distribution_mark, model_number, link,
|
||||||
payment_amount, rebate_amount, address, logistics_link,
|
payment_amount, rebate_amount, address, logistics_link,
|
||||||
tencent_doc_pushed, tencent_doc_push_time,
|
tencent_doc_pushed, tencent_doc_push_time,
|
||||||
order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price
|
order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price,
|
||||||
|
is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date
|
||||||
) values (
|
) values (
|
||||||
#{remark}, #{distributionMark}, #{modelNumber}, #{link},
|
#{remark}, #{distributionMark}, #{modelNumber}, #{link},
|
||||||
#{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink},
|
#{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink},
|
||||||
0, null,
|
0, null,
|
||||||
#{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice}
|
#{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice},
|
||||||
|
#{isRefunded}, #{refundDate}, #{isRefundReceived}, #{refundReceivedDate}, #{isRebateReceived}, #{rebateReceivedDate}
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@@ -129,6 +156,12 @@
|
|||||||
<if test="isCountEnabled != null"> is_count_enabled = #{isCountEnabled},</if>
|
<if test="isCountEnabled != null"> is_count_enabled = #{isCountEnabled},</if>
|
||||||
<if test="thirdPartyOrderNo != null"> third_party_order_no = #{thirdPartyOrderNo},</if>
|
<if test="thirdPartyOrderNo != null"> third_party_order_no = #{thirdPartyOrderNo},</if>
|
||||||
<if test="jingfenActualPrice != null"> jingfen_actual_price = #{jingfenActualPrice},</if>
|
<if test="jingfenActualPrice != null"> jingfen_actual_price = #{jingfenActualPrice},</if>
|
||||||
|
<if test="isRefunded != null"> is_refunded = #{isRefunded},</if>
|
||||||
|
<if test="refundDate != null"> refund_date = #{refundDate},</if>
|
||||||
|
<if test="isRefundReceived != null"> is_refund_received = #{isRefundReceived},</if>
|
||||||
|
<if test="refundReceivedDate != null"> refund_received_date = #{refundReceivedDate},</if>
|
||||||
|
<if test="isRebateReceived != null"> is_rebate_received = #{isRebateReceived},</if>
|
||||||
|
<if test="rebateReceivedDate != null"> rebate_received_date = #{rebateReceivedDate},</if>
|
||||||
update_time = now()
|
update_time = now()
|
||||||
</set>
|
</set>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
@@ -159,7 +192,7 @@
|
|||||||
order by order_time desc
|
order by order_time desc
|
||||||
limit 1
|
limit 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectJDOrderByThirdPartyOrderNo" parameterType="string" resultMap="JDOrderResult">
|
<select id="selectJDOrderByThirdPartyOrderNo" parameterType="string" resultMap="JDOrderResult">
|
||||||
<include refid="selectJDOrderBase"/>
|
<include refid="selectJDOrderBase"/>
|
||||||
where third_party_order_no = #{thirdPartyOrderNo}
|
where third_party_order_no = #{thirdPartyOrderNo}
|
||||||
@@ -176,7 +209,7 @@
|
|||||||
<select id="selectJDOrderListByDistributionMarkFOrPDD" resultMap="JDOrderResult">
|
<select id="selectJDOrderListByDistributionMarkFOrPDD" resultMap="JDOrderResult">
|
||||||
<include refid="selectJDOrderBase"/>
|
<include refid="selectJDOrderBase"/>
|
||||||
<where>
|
<where>
|
||||||
(distribution_mark = 'F' OR distribution_mark = 'PDD')
|
(distribution_mark = 'F' OR distribution_mark = 'PDD' OR distribution_mark = 'H' OR distribution_mark = 'W' OR distribution_mark = 'PDD-W')
|
||||||
AND logistics_link IS NOT NULL
|
AND logistics_link IS NOT NULL
|
||||||
AND logistics_link != ''
|
AND logistics_link != ''
|
||||||
</where>
|
</where>
|
||||||
|
|||||||
@@ -11,12 +11,13 @@
|
|||||||
<result property="secretKey" column="secret_key"/>
|
<result property="secretKey" column="secret_key"/>
|
||||||
<result property="isActive" column="is_active"/>
|
<result property="isActive" column="is_active"/>
|
||||||
<result property="isCount" column="is_count"/>
|
<result property="isCount" column="is_count"/>
|
||||||
|
<result property="touser" column="touser"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
<result property="updatedAt" column="updated_at"/>
|
<result property="updatedAt" column="updated_at"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectSuperAdminVo">
|
<sql id="selectSuperAdminVo">
|
||||||
select id, wxid, name, union_id, app_key, secret_key, is_active, is_count, created_at, updated_at from super_admin
|
select id, wxid, name, union_id, app_key, secret_key, is_active, is_count, touser, created_at, updated_at from super_admin
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectSuperAdminList" parameterType="SuperAdmin" resultMap="SuperAdminResult">
|
<select id="selectSuperAdminList" parameterType="SuperAdmin" resultMap="SuperAdminResult">
|
||||||
@@ -51,6 +52,8 @@
|
|||||||
<if test="appKey != null and appKey != ''">app_key,</if>
|
<if test="appKey != null and appKey != ''">app_key,</if>
|
||||||
<if test="secretKey != null and secretKey != ''">secret_key,</if>
|
<if test="secretKey != null and secretKey != ''">secret_key,</if>
|
||||||
<if test="isActive != null">is_active,</if>
|
<if test="isActive != null">is_active,</if>
|
||||||
|
<if test="isCount != null">is_count,</if>
|
||||||
|
<if test="touser != null and touser != ''">touser,</if>
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at,
|
||||||
</trim>
|
</trim>
|
||||||
@@ -61,6 +64,8 @@
|
|||||||
<if test="appKey != null and appKey != ''">#{appKey},</if>
|
<if test="appKey != null and appKey != ''">#{appKey},</if>
|
||||||
<if test="secretKey != null and secretKey != ''">#{secretKey},</if>
|
<if test="secretKey != null and secretKey != ''">#{secretKey},</if>
|
||||||
<if test="isActive != null">#{isActive},</if>
|
<if test="isActive != null">#{isActive},</if>
|
||||||
|
<if test="isCount != null">#{isCount},</if>
|
||||||
|
<if test="touser != null and touser != ''">#{touser},</if>
|
||||||
now(),
|
now(),
|
||||||
now(),
|
now(),
|
||||||
</trim>
|
</trim>
|
||||||
@@ -76,6 +81,7 @@
|
|||||||
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
|
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
|
||||||
<if test="isActive != null">is_active = #{isActive},</if>
|
<if test="isActive != null">is_active = #{isActive},</if>
|
||||||
<if test="isCount != null">is_count = #{isCount},</if>
|
<if test="isCount != null">is_count = #{isCount},</if>
|
||||||
|
<if test="touser != null">touser = #{touser},</if>
|
||||||
updated_at = now(),
|
updated_at = now(),
|
||||||
</trim>
|
</trim>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
.operation-logs {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
+.operation-logs >>> .el-table {
|
||||||
|
+ border-radius: 8px;
|
||||||
|
+ overflow: hidden;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.operation-logs >>> .log-row-success td {
|
||||||
|
+ background: #f0f9eb;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.operation-logs >>> .log-row-failed td {
|
||||||
|
+ background: #fef0f0;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.operation-logs >>> .log-row-skipped td {
|
||||||
|
+ background: #fdf6ec;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.status-icon {
|
||||||
|
+ font-size: 12px;
|
||||||
|
+ margin-right: 4px;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.text-placeholder {
|
||||||
|
+ color: #c0c4cc;
|
||||||
|
+}
|
||||||
|
|
||||||
|
+.text-muted {
|
||||||
|
+ color: #909399;
|
||||||
|
+}
|
||||||
|
|
||||||
|
.logs-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
4
sql/add_super_admin_touser_field.sql
Normal file
4
sql/add_super_admin_touser_field.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-- 为超级管理员表添加接收人字段
|
||||||
|
-- 字段说明:touser 存储企业微信用户ID,多个用逗号分隔
|
||||||
|
ALTER TABLE super_admin ADD COLUMN touser VARCHAR(500) DEFAULT NULL COMMENT '接收人(企业微信用户ID,多个用逗号分隔)';
|
||||||
|
|
||||||
16
sql/jd_order_refund_fields.sql
Normal file
16
sql/jd_order_refund_fields.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
-- 为jd_order表添加退款相关字段
|
||||||
|
-- 执行日期:2025-01-XX
|
||||||
|
|
||||||
|
ALTER TABLE jd_order
|
||||||
|
ADD COLUMN is_refunded INT DEFAULT 0 COMMENT '是否退款(0否 1是)',
|
||||||
|
ADD COLUMN refund_date DATETIME NULL COMMENT '退款日期',
|
||||||
|
ADD COLUMN is_refund_received INT DEFAULT 0 COMMENT '是否退款到账(0否 1是)',
|
||||||
|
ADD COLUMN refund_received_date DATETIME NULL COMMENT '退款到账日期',
|
||||||
|
ADD COLUMN is_rebate_received INT DEFAULT 0 COMMENT '后返到账(0否 1是)',
|
||||||
|
ADD COLUMN rebate_received_date DATETIME NULL COMMENT '后返到账日期';
|
||||||
|
|
||||||
|
-- 添加索引(可选,根据查询需求)
|
||||||
|
-- CREATE INDEX idx_is_refunded ON jd_order(is_refunded);
|
||||||
|
-- CREATE INDEX idx_is_refund_received ON jd_order(is_refund_received);
|
||||||
|
-- CREATE INDEX idx_is_rebate_received ON jd_order(is_rebate_received);
|
||||||
|
|
||||||
37
sql/闲鱼商品菜单.sql
Normal file
37
sql/闲鱼商品菜单.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
-- 闲鱼商品菜单配置
|
||||||
|
-- 菜单类型:M=目录 C=菜单 F=按钮
|
||||||
|
|
||||||
|
-- 1. 主菜单(如果是放在系统管理下,parent_id为1;如果是独立菜单,需要先创建一个jarvis目录)
|
||||||
|
-- 假设放在系统管理(parent_id=1)下,可以根据实际情况调整
|
||||||
|
|
||||||
|
-- 闲鱼商品管理菜单(主菜单)
|
||||||
|
insert into sys_menu values(2000, '闲鱼商品管理', 1, 10, 'erpProduct', 'system/erpProduct/index', '', '', 1, 0, 'C', '0', '0', 'jarvis:erpProduct:list', 'shopping', 'admin', sysdate(), '', null, '闲鱼商品管理菜单');
|
||||||
|
|
||||||
|
-- 闲鱼商品管理按钮权限
|
||||||
|
insert into sys_menu values(2001, '商品查询', 2000, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:query', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2002, '商品新增', 2000, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:add', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2003, '商品修改', 2000, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:edit', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2004, '商品删除', 2000, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:remove', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2005, '商品导出', 2000, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:export', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2006, '拉取商品', 2000, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:pull', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2007, '批量上架', 2000, 7, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:publish', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
insert into sys_menu values(2008, '批量下架', 2000, 8, '', '', '', '', 1, 0, 'F', '0', '0', 'jarvis:erpProduct:downShelf', '#', 'admin', sysdate(), '', null, '');
|
||||||
|
|
||||||
|
-- 给管理员角色(role_id=1)添加闲鱼商品菜单权限
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2000);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2001);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2002);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2003);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2004);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2005);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2006);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2007);
|
||||||
|
insert into sys_role_menu(role_id, menu_id) values(1, 2008);
|
||||||
|
|
||||||
|
-- 注意:
|
||||||
|
-- 1. 如果菜单需要放在其他目录下(比如jarvis目录),请修改parent_id
|
||||||
|
-- 2. order_num 是显示顺序,可以根据需要调整(值越大越靠后)
|
||||||
|
-- 3. 如果管理员角色ID不是1,请修改上面的role_id值
|
||||||
|
-- 4. 如果需要给其他角色添加权限,可以复制上面的insert语句并修改role_id
|
||||||
|
-- 5. 执行完此SQL后,需要清除Redis缓存或重启系统才能看到菜单
|
||||||
|
|
||||||
31
sql/闲鱼商品表.sql
Normal file
31
sql/闲鱼商品表.sql
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-- 闲鱼商品表
|
||||||
|
CREATE TABLE `erp_product` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`product_id` bigint(20) NOT NULL COMMENT '管家商品ID',
|
||||||
|
`title` varchar(500) DEFAULT NULL COMMENT '商品标题',
|
||||||
|
`main_image` varchar(1000) DEFAULT NULL COMMENT '商品图片(主图)',
|
||||||
|
`price` bigint(20) DEFAULT NULL COMMENT '商品价格(分)',
|
||||||
|
`stock` int(11) DEFAULT NULL COMMENT '商品库存',
|
||||||
|
`product_status` int(11) DEFAULT NULL COMMENT '商品状态 -1:删除 21:待发布 22:销售中 23:已售罄 31:手动下架 33:售出下架 36:自动下架',
|
||||||
|
`sale_status` int(11) DEFAULT NULL COMMENT '销售状态',
|
||||||
|
`user_name` varchar(100) DEFAULT NULL COMMENT '闲鱼会员名',
|
||||||
|
`online_time` bigint(20) DEFAULT NULL COMMENT '上架时间(时间戳)',
|
||||||
|
`offline_time` bigint(20) DEFAULT NULL COMMENT '下架时间(时间戳)',
|
||||||
|
`sold_time` bigint(20) DEFAULT NULL COMMENT '售出时间(时间戳)',
|
||||||
|
`create_time_xy` bigint(20) DEFAULT NULL COMMENT '创建时间(闲鱼,时间戳)',
|
||||||
|
`update_time_xy` bigint(20) DEFAULT NULL COMMENT '更新时间(闲鱼,时间戳)',
|
||||||
|
`appid` varchar(100) DEFAULT NULL COMMENT 'ERP应用ID',
|
||||||
|
`product_url` varchar(1000) DEFAULT NULL COMMENT '商品链接',
|
||||||
|
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_product_id_appid` (`product_id`, `appid`),
|
||||||
|
KEY `idx_product_id` (`product_id`),
|
||||||
|
KEY `idx_appid` (`appid`),
|
||||||
|
KEY `idx_product_status` (`product_status`),
|
||||||
|
KEY `idx_user_name` (`user_name`),
|
||||||
|
KEY `idx_online_time` (`online_time`),
|
||||||
|
KEY `idx_update_time_xy` (`update_time_xy`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲鱼商品表';
|
||||||
|
|
||||||
Reference in New Issue
Block a user