Compare commits
39 Commits
b8981ffc98
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a8c7b1039 | ||
|
|
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 |
@@ -0,0 +1,137 @@
|
||||
package com.ruoyi.web.controller.jarvis;
|
||||
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.jarvis.domain.Comment;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentApiStatistics;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentStatistics;
|
||||
import com.ruoyi.jarvis.service.ICommentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 评论管理 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/jarvis/comment")
|
||||
public class CommentController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ICommentService commentService;
|
||||
|
||||
/**
|
||||
* 查询京东评论列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/jd/list")
|
||||
public TableDataInfo list(Comment comment) {
|
||||
startPage();
|
||||
List<Comment> list = commentService.selectCommentList(comment);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出京东评论列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:export')")
|
||||
@Log(title = "京东评论", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/jd/export")
|
||||
public void export(HttpServletResponse response, Comment comment) {
|
||||
List<Comment> list = commentService.selectCommentList(comment);
|
||||
ExcelUtil<Comment> util = new ExcelUtil<Comment>(Comment.class);
|
||||
util.exportExcel(response, list, "京东评论数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取京东评论详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:query')")
|
||||
@GetMapping("/jd/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||
return success(commentService.selectCommentById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改评论使用状态
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:edit')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/jd")
|
||||
public AjaxResult edit(@RequestBody Comment comment) {
|
||||
return toAjax(commentService.updateCommentIsUse(comment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:remove')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/jd/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(commentService.deleteCommentByIds(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(按商品ID)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:edit')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/jd/reset/{productId}")
|
||||
public AjaxResult resetByProductId(@PathVariable String productId) {
|
||||
return toAjax(commentService.resetCommentIsUseByProductId(productId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论统计信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/statistics")
|
||||
public AjaxResult getStatistics(@RequestParam(required = false) String source) {
|
||||
List<CommentStatistics> statistics = commentService.getCommentStatistics(source);
|
||||
return success(statistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口调用统计
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/api/statistics")
|
||||
public AjaxResult getApiStatistics(
|
||||
@RequestParam(required = false) String apiType,
|
||||
@RequestParam(required = false) String productType,
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate) {
|
||||
List<CommentApiStatistics> statistics = commentService.getApiStatistics(apiType, productType, startDate, endDate);
|
||||
return success(statistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Redis产品类型映射(京东)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/redis/jd/map")
|
||||
public AjaxResult getJdProductTypeMap() {
|
||||
Map<String, String> map = commentService.getJdProductTypeMap();
|
||||
return success(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Redis产品类型映射(淘宝)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/redis/tb/map")
|
||||
public AjaxResult getTbProductTypeMap() {
|
||||
Map<String, String> map = commentService.getTbProductTypeMap();
|
||||
return success(map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ErpProductController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 从闲鱼ERP拉取商品列表并保存
|
||||
* 从闲鱼ERP拉取商品列表并保存(单页,保留用于兼容)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:pull')")
|
||||
@Log(title = "拉取闲鱼商品", businessType = BusinessType.INSERT)
|
||||
@@ -124,13 +124,31 @@ public class ErpProductController extends BaseController
|
||||
if (productStatus != null) {
|
||||
message += "(筛选条件:状态=" + statusText + ")";
|
||||
}
|
||||
message += "。建议:1.尝试不选择状态拉取全部商品;2.尝试其他状态(下架、已售等)";
|
||||
message += "。建议:使用全量同步功能自动遍历所有页码";
|
||||
return success(message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return error("拉取商品列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量同步商品(自动遍历所有页码,同步更新和删除)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:pull')")
|
||||
@Log(title = "全量同步闲鱼商品", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/syncAll")
|
||||
public AjaxResult syncAllProducts(
|
||||
@RequestParam(required = false) String appid,
|
||||
@RequestParam(required = false) Integer productStatus)
|
||||
{
|
||||
try {
|
||||
IErpProductService.SyncResult result = erpProductService.syncAllProducts(appid, productStatus);
|
||||
return success(result.getMessage());
|
||||
} catch (Exception e) {
|
||||
return error("全量同步失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本(用于提示信息)
|
||||
@@ -140,16 +158,20 @@ public class ErpProductController extends BaseController
|
||||
return "全部";
|
||||
}
|
||||
switch (status) {
|
||||
case 1:
|
||||
return "上架";
|
||||
case 2:
|
||||
return "下架";
|
||||
case 3:
|
||||
return "已售";
|
||||
case -1:
|
||||
return "删除";
|
||||
case 21:
|
||||
return "待发布";
|
||||
case 22:
|
||||
return "草稿";
|
||||
return "销售中";
|
||||
case 23:
|
||||
return "审核中";
|
||||
return "已售罄";
|
||||
case 31:
|
||||
return "手动下架";
|
||||
case 33:
|
||||
return "售出下架";
|
||||
case 36:
|
||||
return "自动下架";
|
||||
default:
|
||||
return String.valueOf(status);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@ public class InstructionController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行文本指令
|
||||
* 执行文本指令(控制台入口,需要权限)
|
||||
* body: { command: "京今日统计", forceGenerate: false }
|
||||
*/
|
||||
@PostMapping("/execute")
|
||||
public AjaxResult execute(@RequestBody Map<String, Object> body) {
|
||||
String cmd = body != null ? (body.get("command") != null ? String.valueOf(body.get("command")) : null) : null;
|
||||
boolean forceGenerate = body != null && body.get("forceGenerate") != null && Boolean.parseBoolean(String.valueOf(body.get("forceGenerate")));
|
||||
java.util.List<String> result = instructionService.execute(cmd, forceGenerate);
|
||||
// 控制台入口,传递 isFromConsole=true,跳过订单查询校验
|
||||
java.util.List<String> result = instructionService.execute(cmd, forceGenerate, true);
|
||||
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.IGiftCouponService;
|
||||
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.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -37,6 +39,8 @@ public class JDOrderController extends BaseController {
|
||||
private final IJDOrderService jdOrderService;
|
||||
private final IOrderRowsService orderRowsService;
|
||||
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);
|
||||
@@ -47,10 +51,12 @@ public class JDOrderController extends BaseController {
|
||||
"^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.orderRowsService = orderRowsService;
|
||||
this.giftCouponService = giftCouponService;
|
||||
this.sysConfigService = sysConfigService;
|
||||
}
|
||||
|
||||
private final static String skey = "2192057370ef8140c201079969c956a3";
|
||||
@@ -1017,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 订单信息
|
||||
@@ -1045,7 +1088,7 @@ public class JDOrderController extends BaseController {
|
||||
// 收货地址
|
||||
pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n");
|
||||
// 运单号
|
||||
pushContent.append("运单号:").append("\n").append("---------").append(waybillNo).append("\n");
|
||||
pushContent.append("运单号:").append("\n").append("\n").append("\n").append("\n").append(waybillNo).append("\n");
|
||||
|
||||
// 调用企业微信推送接口(参考WxtsUtil的实现)
|
||||
String pushUrl = "https://wxts.van333.cn/wx/send/pdd";
|
||||
@@ -1055,8 +1098,23 @@ public class JDOrderController extends BaseController {
|
||||
String content = pushContent.toString();
|
||||
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请求
|
||||
String pushResult = sendPostWithHeaders(pushUrl, pushParam.toJSONString(), token);
|
||||
String pushResult = sendPostWithHeaders(pushUrl, jsonBody, token);
|
||||
logger.info("企业应用推送调用结果 - 订单ID: {}, waybill_no: {}, 推送结果: {}",
|
||||
order.getId(), waybillNo, pushResult);
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.ruoyi.web.controller.jarvis;
|
||||
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.jarvis.domain.TaobaoComment;
|
||||
import com.ruoyi.jarvis.service.ITaobaoCommentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 淘宝评论管理 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/jarvis/taobaoComment")
|
||||
public class TaobaoCommentController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ITaobaoCommentService taobaoCommentService;
|
||||
|
||||
/**
|
||||
* 查询淘宝评论列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(TaobaoComment taobaoComment) {
|
||||
startPage();
|
||||
List<TaobaoComment> list = taobaoCommentService.selectTaobaoCommentList(taobaoComment);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出淘宝评论列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:export')")
|
||||
@Log(title = "淘宝评论", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, TaobaoComment taobaoComment) {
|
||||
List<TaobaoComment> list = taobaoCommentService.selectTaobaoCommentList(taobaoComment);
|
||||
ExcelUtil<TaobaoComment> util = new ExcelUtil<TaobaoComment>(TaobaoComment.class);
|
||||
util.exportExcel(response, list, "淘宝评论数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取淘宝评论详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:query')")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Integer id) {
|
||||
return success(taobaoCommentService.selectTaobaoCommentById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改评论使用状态
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:edit')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TaobaoComment taobaoComment) {
|
||||
return toAjax(taobaoCommentService.updateTaobaoCommentIsUse(taobaoComment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:remove')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(taobaoCommentService.deleteTaobaoCommentByIds(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(按商品ID)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('jarvis:comment:edit')")
|
||||
@Log(title = "评论管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/reset/{productId}")
|
||||
public AjaxResult resetByProductId(@PathVariable String productId) {
|
||||
return toAjax(taobaoCommentService.resetTaobaoCommentIsUseByProductId(productId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1680,5 +1680,352 @@ public class TencentDocController extends BaseController {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.utils.http.HttpUtils;
|
||||
import com.ruoyi.jarvis.service.ICommentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -22,18 +24,28 @@ public class CommentPublicController extends BaseController {
|
||||
private static final String JD_BASE = "http://192.168.8.88:6666/jd";
|
||||
private static final String SKEY = "2192057370ef8140c201079969c956a3";
|
||||
|
||||
@Autowired(required = false)
|
||||
private ICommentService commentService;
|
||||
|
||||
/**
|
||||
* 获取可选型号/类型(示例)
|
||||
*/
|
||||
@GetMapping("/types")
|
||||
public AjaxResult types() {
|
||||
boolean success = false;
|
||||
try {
|
||||
String url = JD_BASE + "/comment/types?skey=" + SKEY;
|
||||
String result = HttpUtils.sendGet(url);
|
||||
Object parsed = JSON.parse(result);
|
||||
success = true;
|
||||
return AjaxResult.success(parsed);
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("types failed: " + e.getMessage());
|
||||
} finally {
|
||||
// 记录接口调用统计
|
||||
if (commentService != null) {
|
||||
commentService.recordApiCall("jd", "types", success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,18 +55,27 @@ public class CommentPublicController extends BaseController {
|
||||
*/
|
||||
@PostMapping("/generate")
|
||||
public AjaxResult generate(@RequestBody Map<String, String> body) {
|
||||
boolean success = false;
|
||||
String productType = null;
|
||||
try {
|
||||
String url = JD_BASE + "/comment/generate";
|
||||
JSONObject param = new JSONObject();
|
||||
param.put("skey", SKEY);
|
||||
if (body != null && body.get("productType") != null) {
|
||||
param.put("productType", body.get("productType"));
|
||||
productType = body.get("productType");
|
||||
param.put("productType", productType);
|
||||
}
|
||||
String result = HttpUtils.sendJsonPost(url, param.toJSONString());
|
||||
Object parsed = JSON.parse(result);
|
||||
success = true;
|
||||
return AjaxResult.success(parsed);
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("generate failed: " + e.getMessage());
|
||||
} finally {
|
||||
// 记录接口调用统计
|
||||
if (commentService != null && productType != null) {
|
||||
commentService.recordApiCall("jd", productType, success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
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.JDOrder;
|
||||
import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO;
|
||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||
import com.ruoyi.jarvis.service.IInstructionService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
@@ -30,10 +33,12 @@ public class JDOrderListController extends BaseController
|
||||
|
||||
private final IJDOrderService jdOrderService;
|
||||
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.orderRowsService = orderRowsService;
|
||||
this.instructionService = instructionService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +69,12 @@ public class JDOrderListController extends BaseController
|
||||
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;
|
||||
if (orderBy != null && !orderBy.isEmpty()) {
|
||||
// 设置排序参数
|
||||
@@ -85,9 +96,11 @@ public class JDOrderListController extends BaseController
|
||||
if (orderRows != null) {
|
||||
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
||||
jdOrder.setFinishTime(orderRows.getFinishTime());
|
||||
jdOrder.setOrderStatus(orderRows.getValidCode());
|
||||
} else {
|
||||
jdOrder.setProPriceAmount(0.0);
|
||||
jdOrder.setFinishTime(null);
|
||||
jdOrder.setOrderStatus(null);
|
||||
}
|
||||
}
|
||||
// 过滤掉完成时间为空的订单
|
||||
@@ -101,8 +114,10 @@ public class JDOrderListController extends BaseController
|
||||
if (orderRows != null) {
|
||||
jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
|
||||
jdOrder.setFinishTime(orderRows.getFinishTime());
|
||||
jdOrder.setOrderStatus(orderRows.getValidCode());
|
||||
} else {
|
||||
jdOrder.setProPriceAmount(0.0);
|
||||
jdOrder.setOrderStatus(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,4 +186,251 @@ public class JDOrderListController extends BaseController
|
||||
{
|
||||
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:
|
||||
push:
|
||||
# 延迟时间(分钟),默认10分钟
|
||||
minutes: 30
|
||||
minutes: 10
|
||||
|
||||
@@ -217,6 +217,79 @@ public class HttpUtils
|
||||
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)
|
||||
{
|
||||
return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.jarvis.domain;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 京东评论对象 comments
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class Comment extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
@Excel(name = "ID")
|
||||
private Long id;
|
||||
|
||||
/** 商品ID */
|
||||
@Excel(name = "商品ID")
|
||||
private String productId;
|
||||
|
||||
/** 用户名 */
|
||||
@Excel(name = "用户名")
|
||||
private String userName;
|
||||
|
||||
/** 评论内容 */
|
||||
@Excel(name = "评论内容")
|
||||
private String commentText;
|
||||
|
||||
/** 评论ID */
|
||||
@Excel(name = "评论ID")
|
||||
private String commentId;
|
||||
|
||||
/** 图片URLs */
|
||||
@Excel(name = "图片URLs")
|
||||
private String pictureUrls;
|
||||
|
||||
/** 创建时间 */
|
||||
@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createdAt;
|
||||
|
||||
/** 评论日期 */
|
||||
@Excel(name = "评论日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date commentDate;
|
||||
|
||||
/** 是否已使用 0-未使用 1-已使用 */
|
||||
@Excel(name = "使用状态", readConverterExp = "0=未使用,1=已使用")
|
||||
private Integer isUse;
|
||||
|
||||
/** 产品类型(从Redis映射获取) */
|
||||
private String productType;
|
||||
|
||||
/** Redis映射的产品ID */
|
||||
private String mappedProductId;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ public class ErpProduct extends BaseEntity
|
||||
@Excel(name = "商品库存")
|
||||
private Integer stock;
|
||||
|
||||
/** 商品状态 1:上架 2:下架 3:已售 */
|
||||
@Excel(name = "商品状态", readConverterExp = "1=上架,2=下架,3=已售")
|
||||
/** 商品状态 -1:删除 21:待发布 22:销售中 23:已售罄 31:手动下架 33:售出下架 36:自动下架 */
|
||||
@Excel(name = "商品状态", readConverterExp = "-1=删除,21=待发布,22=销售中,23=已售罄,31=手动下架,33=售出下架,36=自动下架")
|
||||
private Integer productStatus;
|
||||
|
||||
/** 销售状态 */
|
||||
|
||||
@@ -80,6 +80,11 @@ public class JDOrder extends BaseEntity {
|
||||
@Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date finishTime;
|
||||
|
||||
/** 订单状态(从order_rows表查询) */
|
||||
@Transient
|
||||
@Excel(name = "订单状态")
|
||||
private Integer orderStatus;
|
||||
|
||||
/** 是否参与统计(0否 1是) */
|
||||
@Excel(name = "参与统计")
|
||||
private Integer isCountEnabled;
|
||||
@@ -92,6 +97,30 @@ public class JDOrder extends BaseEntity {
|
||||
@Excel(name = "京粉实际价格")
|
||||
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=是")
|
||||
private Integer isCount;
|
||||
|
||||
/** 接收人(企业微信用户ID,多个用逗号分隔) */
|
||||
@Excel(name = "接收人")
|
||||
private String touser;
|
||||
|
||||
/** 创建时间 */
|
||||
@Excel(name = "创建时间")
|
||||
private Date createdAt;
|
||||
@@ -151,4 +155,14 @@ public class SuperAdmin extends BaseEntity
|
||||
{
|
||||
this.isCount = isCount;
|
||||
}
|
||||
|
||||
public String getTouser()
|
||||
{
|
||||
return touser;
|
||||
}
|
||||
|
||||
public void setTouser(String touser)
|
||||
{
|
||||
this.touser = touser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ruoyi.jarvis.domain;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 淘宝评论对象 taobao_comments
|
||||
*/
|
||||
@Data
|
||||
public class TaobaoComment extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
@Excel(name = "ID")
|
||||
private Integer id;
|
||||
|
||||
/** 商品ID */
|
||||
@Excel(name = "商品ID")
|
||||
private String productId;
|
||||
|
||||
/** 用户名 */
|
||||
@Excel(name = "用户名")
|
||||
private String userName;
|
||||
|
||||
/** 评论内容 */
|
||||
@Excel(name = "评论内容")
|
||||
private String commentText;
|
||||
|
||||
/** 评论ID */
|
||||
@Excel(name = "评论ID")
|
||||
private String commentId;
|
||||
|
||||
/** 图片URLs */
|
||||
@Excel(name = "图片URLs")
|
||||
private String pictureUrls;
|
||||
|
||||
/** 创建时间 */
|
||||
@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createdAt;
|
||||
|
||||
/** 评论日期 */
|
||||
@Excel(name = "评论日期")
|
||||
private String commentDate;
|
||||
|
||||
/** 是否已使用 0-未使用 1-已使用 */
|
||||
@Excel(name = "使用状态", readConverterExp = "0=未使用,1=已使用")
|
||||
private Integer isUse;
|
||||
|
||||
/** 产品类型(从Redis映射获取) */
|
||||
private String productType;
|
||||
|
||||
/** Redis映射的产品ID */
|
||||
private String mappedProductId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.jarvis.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 评论接口调用统计
|
||||
*/
|
||||
@Data
|
||||
public class CommentApiStatistics {
|
||||
/** 统计日期 */
|
||||
private Date statDate;
|
||||
|
||||
/** 接口类型:jd-京东,tb-淘宝 */
|
||||
private String apiType;
|
||||
|
||||
/** 产品类型 */
|
||||
private String productType;
|
||||
|
||||
/** 调用次数 */
|
||||
private Long callCount;
|
||||
|
||||
/** 成功次数 */
|
||||
private Long successCount;
|
||||
|
||||
/** 失败次数 */
|
||||
private Long failCount;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.jarvis.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 评论统计信息
|
||||
*/
|
||||
@Data
|
||||
public class CommentStatistics {
|
||||
/** 评论来源:jd-京东,tb-淘宝 */
|
||||
private String source;
|
||||
|
||||
/** 产品类型 */
|
||||
private String productType;
|
||||
|
||||
/** 产品ID */
|
||||
private String productId;
|
||||
|
||||
/** 总评论数 */
|
||||
private Long totalCount;
|
||||
|
||||
/** 可用评论数(未使用) */
|
||||
private Long availableCount;
|
||||
|
||||
/** 已使用评论数 */
|
||||
private Long usedCount;
|
||||
|
||||
/** 接口调用次数 */
|
||||
private Long apiCallCount;
|
||||
|
||||
/** 今日调用次数 */
|
||||
private Long todayCallCount;
|
||||
}
|
||||
|
||||
@@ -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,42 @@
|
||||
package com.ruoyi.jarvis.mapper;
|
||||
|
||||
import com.ruoyi.jarvis.domain.Comment;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 京东评论 Mapper 接口
|
||||
*/
|
||||
public interface CommentMapper {
|
||||
|
||||
/**
|
||||
* 查询京东评论列表
|
||||
*/
|
||||
List<Comment> selectCommentList(Comment comment);
|
||||
|
||||
/**
|
||||
* 根据ID查询京东评论
|
||||
*/
|
||||
Comment selectCommentById(Long id);
|
||||
|
||||
/**
|
||||
* 根据商品ID查询评论统计
|
||||
*/
|
||||
Map<String, Object> selectCommentStatisticsByProductId(String productId);
|
||||
|
||||
/**
|
||||
* 更新评论使用状态
|
||||
*/
|
||||
int updateCommentIsUse(Comment comment);
|
||||
|
||||
/**
|
||||
* 批量删除评论
|
||||
*/
|
||||
int deleteCommentByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(批量)
|
||||
*/
|
||||
int resetCommentIsUseByProductId(String productId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.jarvis.mapper;
|
||||
|
||||
import com.ruoyi.jarvis.domain.TaobaoComment;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 淘宝评论 Mapper 接口
|
||||
*/
|
||||
public interface TaobaoCommentMapper {
|
||||
|
||||
/**
|
||||
* 查询淘宝评论列表
|
||||
*/
|
||||
List<TaobaoComment> selectTaobaoCommentList(TaobaoComment taobaoComment);
|
||||
|
||||
/**
|
||||
* 根据ID查询淘宝评论
|
||||
*/
|
||||
TaobaoComment selectTaobaoCommentById(Integer id);
|
||||
|
||||
/**
|
||||
* 根据商品ID查询评论统计
|
||||
*/
|
||||
Map<String, Object> selectTaobaoCommentStatisticsByProductId(String productId);
|
||||
|
||||
/**
|
||||
* 更新评论使用状态
|
||||
*/
|
||||
int updateTaobaoCommentIsUse(TaobaoComment taobaoComment);
|
||||
|
||||
/**
|
||||
* 批量删除评论
|
||||
*/
|
||||
int deleteTaobaoCommentByIds(Integer[] ids);
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(批量)
|
||||
*/
|
||||
int resetTaobaoCommentIsUseByProductId(String productId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.ruoyi.jarvis.service;
|
||||
|
||||
import com.ruoyi.jarvis.domain.Comment;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentStatistics;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentApiStatistics;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 评论管理 Service 接口
|
||||
*/
|
||||
public interface ICommentService {
|
||||
|
||||
/**
|
||||
* 查询京东评论列表
|
||||
*/
|
||||
List<Comment> selectCommentList(Comment comment);
|
||||
|
||||
/**
|
||||
* 根据ID查询京东评论
|
||||
*/
|
||||
Comment selectCommentById(Long id);
|
||||
|
||||
/**
|
||||
* 更新评论使用状态
|
||||
*/
|
||||
int updateCommentIsUse(Comment comment);
|
||||
|
||||
/**
|
||||
* 批量删除评论
|
||||
*/
|
||||
int deleteCommentByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(批量)
|
||||
*/
|
||||
int resetCommentIsUseByProductId(String productId);
|
||||
|
||||
/**
|
||||
* 获取评论统计信息(包含Redis映射)
|
||||
*/
|
||||
List<CommentStatistics> getCommentStatistics(String source);
|
||||
|
||||
/**
|
||||
* 记录接口调用统计
|
||||
*/
|
||||
void recordApiCall(String apiType, String productType, boolean success);
|
||||
|
||||
/**
|
||||
* 获取接口调用统计
|
||||
*/
|
||||
List<CommentApiStatistics> getApiStatistics(String apiType, String productType, String startDate, String endDate);
|
||||
|
||||
/**
|
||||
* 获取Redis产品类型映射(京东)
|
||||
*/
|
||||
Map<String, String> getJdProductTypeMap();
|
||||
|
||||
/**
|
||||
* 获取Redis产品类型映射(淘宝)
|
||||
*/
|
||||
Map<String, String> getTbProductTypeMap();
|
||||
}
|
||||
|
||||
@@ -69,5 +69,39 @@ public interface IErpProductService
|
||||
* @return 拉取结果
|
||||
*/
|
||||
public int pullAndSaveProductList(String appid, Integer pageNo, Integer pageSize, Integer productStatus);
|
||||
|
||||
/**
|
||||
* 全量同步商品列表(自动遍历所有页码,同步更新和删除)
|
||||
*
|
||||
* @param appid ERP应用ID
|
||||
* @param productStatus 商品状态(null表示全部状态)
|
||||
* @return 同步结果
|
||||
*/
|
||||
public SyncResult syncAllProducts(String appid, Integer productStatus);
|
||||
|
||||
/**
|
||||
* 同步结果
|
||||
*/
|
||||
public static class SyncResult {
|
||||
private int totalPulled; // 拉取总数
|
||||
private int added; // 新增数量
|
||||
private int updated; // 更新数量
|
||||
private int deleted; // 删除数量
|
||||
private int failed; // 失败数量
|
||||
private String message; // 结果消息
|
||||
|
||||
public int getTotalPulled() { return totalPulled; }
|
||||
public void setTotalPulled(int totalPulled) { this.totalPulled = totalPulled; }
|
||||
public int getAdded() { return added; }
|
||||
public void setAdded(int added) { this.added = added; }
|
||||
public int getUpdated() { return updated; }
|
||||
public void setUpdated(int updated) { this.updated = updated; }
|
||||
public int getDeleted() { return deleted; }
|
||||
public void setDeleted(int deleted) { this.deleted = deleted; }
|
||||
public int getFailed() { return failed; }
|
||||
public void setFailed(int failed) { this.failed = failed; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,15 @@ public interface IInstructionService {
|
||||
*/
|
||||
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(响应)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.jarvis.service;
|
||||
|
||||
import com.ruoyi.jarvis.domain.TaobaoComment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 淘宝评论管理 Service 接口
|
||||
*/
|
||||
public interface ITaobaoCommentService {
|
||||
|
||||
/**
|
||||
* 查询淘宝评论列表
|
||||
*/
|
||||
List<TaobaoComment> selectTaobaoCommentList(TaobaoComment taobaoComment);
|
||||
|
||||
/**
|
||||
* 根据ID查询淘宝评论
|
||||
*/
|
||||
TaobaoComment selectTaobaoCommentById(Integer id);
|
||||
|
||||
/**
|
||||
* 更新评论使用状态
|
||||
*/
|
||||
int updateTaobaoCommentIsUse(TaobaoComment taobaoComment);
|
||||
|
||||
/**
|
||||
* 批量删除评论
|
||||
*/
|
||||
int deleteTaobaoCommentByIds(Integer[] ids);
|
||||
|
||||
/**
|
||||
* 重置评论使用状态(批量)
|
||||
*/
|
||||
int resetTaobaoCommentIsUseByProductId(String productId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
package com.ruoyi.jarvis.service.impl;
|
||||
|
||||
import com.ruoyi.jarvis.domain.Comment;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentApiStatistics;
|
||||
import com.ruoyi.jarvis.domain.dto.CommentStatistics;
|
||||
import com.ruoyi.jarvis.mapper.CommentMapper;
|
||||
import com.ruoyi.jarvis.service.ICommentService;
|
||||
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.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 评论管理 Service 实现
|
||||
*/
|
||||
@Service
|
||||
public class CommentServiceImpl implements ICommentService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CommentServiceImpl.class);
|
||||
|
||||
private static final String PRODUCT_TYPE_MAP_PREFIX = "product_type_map";
|
||||
private static final String PRODUCT_TYPE_MAP_PREFIX_TB = "product_type_map_tb";
|
||||
private static final String API_CALL_STAT_PREFIX = "comment:api:stat:";
|
||||
private static final String API_CALL_TODAY_PREFIX = "comment:api:today:";
|
||||
|
||||
@Autowired
|
||||
private CommentMapper commentMapper;
|
||||
|
||||
@Autowired(required = false)
|
||||
private com.ruoyi.jarvis.mapper.TaobaoCommentMapper taobaoCommentMapper;
|
||||
|
||||
@Autowired(required = false)
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public List<Comment> selectCommentList(Comment comment) {
|
||||
List<Comment> list = commentMapper.selectCommentList(comment);
|
||||
// 填充Redis映射的产品类型信息
|
||||
if (stringRedisTemplate != null) {
|
||||
Map<String, String> jdMap = getJdProductTypeMap();
|
||||
for (Comment c : list) {
|
||||
// 查找对应的产品类型
|
||||
String productId = c.getProductId();
|
||||
if (jdMap != null) {
|
||||
for (Map.Entry<String, String> entry : jdMap.entrySet()) {
|
||||
if (entry.getValue().equals(productId)) {
|
||||
c.setProductType(entry.getKey());
|
||||
c.setMappedProductId(productId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comment selectCommentById(Long id) {
|
||||
Comment comment = commentMapper.selectCommentById(id);
|
||||
if (comment != null && stringRedisTemplate != null) {
|
||||
Map<String, String> jdMap = getJdProductTypeMap();
|
||||
if (jdMap != null) {
|
||||
String productId = comment.getProductId();
|
||||
for (Map.Entry<String, String> entry : jdMap.entrySet()) {
|
||||
if (entry.getValue().equals(productId)) {
|
||||
comment.setProductType(entry.getKey());
|
||||
comment.setMappedProductId(productId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateCommentIsUse(Comment comment) {
|
||||
return commentMapper.updateCommentIsUse(comment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteCommentByIds(Long[] ids) {
|
||||
return commentMapper.deleteCommentByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int resetCommentIsUseByProductId(String productId) {
|
||||
return commentMapper.resetCommentIsUseByProductId(productId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommentStatistics> getCommentStatistics(String source) {
|
||||
List<CommentStatistics> statisticsList = new ArrayList<>();
|
||||
|
||||
Map<String, String> productTypeMap = null;
|
||||
if ("jd".equals(source) || source == null) {
|
||||
productTypeMap = getJdProductTypeMap();
|
||||
} else if ("tb".equals(source)) {
|
||||
productTypeMap = getTbProductTypeMap();
|
||||
}
|
||||
|
||||
if (productTypeMap == null || productTypeMap.isEmpty()) {
|
||||
return statisticsList;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : productTypeMap.entrySet()) {
|
||||
String productType = entry.getKey();
|
||||
String productId = entry.getValue();
|
||||
|
||||
CommentStatistics stats = new CommentStatistics();
|
||||
stats.setSource("jd".equals(source) ? "京东评论" : "淘宝评论");
|
||||
stats.setProductType(productType);
|
||||
stats.setProductId(productId);
|
||||
|
||||
// 查询评论统计
|
||||
Map<String, Object> statMap = null;
|
||||
if ("jd".equals(source) || source == null) {
|
||||
statMap = commentMapper.selectCommentStatisticsByProductId(productId);
|
||||
} else if ("tb".equals(source) && taobaoCommentMapper != null) {
|
||||
statMap = taobaoCommentMapper.selectTaobaoCommentStatisticsByProductId(productId);
|
||||
}
|
||||
|
||||
if (statMap != null) {
|
||||
stats.setTotalCount(((Number) statMap.get("totalCount")).longValue());
|
||||
stats.setAvailableCount(((Number) statMap.get("availableCount")).longValue());
|
||||
stats.setUsedCount(((Number) statMap.get("usedCount")).longValue());
|
||||
}
|
||||
|
||||
// 获取接口调用统计
|
||||
if (stringRedisTemplate != null) {
|
||||
String todayKey = API_CALL_TODAY_PREFIX + source + ":" + productType + ":" + getTodayDate();
|
||||
String todayCount = stringRedisTemplate.opsForValue().get(todayKey);
|
||||
stats.setTodayCallCount(todayCount != null ? Long.parseLong(todayCount) : 0L);
|
||||
|
||||
// 获取总调用次数(从Redis中统计)
|
||||
String statKey = API_CALL_STAT_PREFIX + source + ":" + productType;
|
||||
String totalCount = stringRedisTemplate.opsForValue().get(statKey);
|
||||
stats.setApiCallCount(totalCount != null ? Long.parseLong(totalCount) : 0L);
|
||||
}
|
||||
|
||||
statisticsList.add(stats);
|
||||
}
|
||||
|
||||
return statisticsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordApiCall(String apiType, String productType, boolean success) {
|
||||
if (stringRedisTemplate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String today = getTodayDate();
|
||||
String todayKey = API_CALL_TODAY_PREFIX + apiType + ":" + productType + ":" + today;
|
||||
stringRedisTemplate.opsForValue().increment(todayKey);
|
||||
stringRedisTemplate.expire(todayKey, 7, TimeUnit.DAYS); // 保留7天
|
||||
|
||||
String statKey = API_CALL_STAT_PREFIX + apiType + ":" + productType;
|
||||
stringRedisTemplate.opsForValue().increment(statKey);
|
||||
|
||||
// 记录成功/失败统计
|
||||
String successKey = API_CALL_STAT_PREFIX + apiType + ":" + productType + ":success";
|
||||
String failKey = API_CALL_STAT_PREFIX + apiType + ":" + productType + ":fail";
|
||||
if (success) {
|
||||
stringRedisTemplate.opsForValue().increment(successKey);
|
||||
} else {
|
||||
stringRedisTemplate.opsForValue().increment(failKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("记录接口调用统计失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommentApiStatistics> getApiStatistics(String apiType, String productType, String startDate, String endDate) {
|
||||
List<CommentApiStatistics> statisticsList = new ArrayList<>();
|
||||
|
||||
if (stringRedisTemplate == null) {
|
||||
return statisticsList;
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果指定了产品类型,只查询该类型的统计
|
||||
if (productType != null && !productType.isEmpty()) {
|
||||
CommentApiStatistics stats = new CommentApiStatistics();
|
||||
stats.setApiType(apiType);
|
||||
stats.setProductType(productType);
|
||||
|
||||
String statKey = API_CALL_STAT_PREFIX + apiType + ":" + productType;
|
||||
String totalCount = stringRedisTemplate.opsForValue().get(statKey);
|
||||
stats.setCallCount(totalCount != null ? Long.parseLong(totalCount) : 0L);
|
||||
|
||||
String successKey = statKey + ":success";
|
||||
String successCount = stringRedisTemplate.opsForValue().get(successKey);
|
||||
stats.setSuccessCount(successCount != null ? Long.parseLong(successCount) : 0L);
|
||||
|
||||
String failKey = statKey + ":fail";
|
||||
String failCount = stringRedisTemplate.opsForValue().get(failKey);
|
||||
stats.setFailCount(failCount != null ? Long.parseLong(failCount) : 0L);
|
||||
|
||||
statisticsList.add(stats);
|
||||
} else {
|
||||
// 查询所有产品类型的统计
|
||||
Map<String, String> productTypeMap = "jd".equals(apiType) ? getJdProductTypeMap() : getTbProductTypeMap();
|
||||
if (productTypeMap != null) {
|
||||
for (String pt : productTypeMap.keySet()) {
|
||||
CommentApiStatistics stats = new CommentApiStatistics();
|
||||
stats.setApiType(apiType);
|
||||
stats.setProductType(pt);
|
||||
|
||||
String statKey = API_CALL_STAT_PREFIX + apiType + ":" + pt;
|
||||
String totalCount = stringRedisTemplate.opsForValue().get(statKey);
|
||||
stats.setCallCount(totalCount != null ? Long.parseLong(totalCount) : 0L);
|
||||
|
||||
String successKey = statKey + ":success";
|
||||
String successCount = stringRedisTemplate.opsForValue().get(successKey);
|
||||
stats.setSuccessCount(successCount != null ? Long.parseLong(successCount) : 0L);
|
||||
|
||||
String failKey = statKey + ":fail";
|
||||
String failCount = stringRedisTemplate.opsForValue().get(failKey);
|
||||
stats.setFailCount(failCount != null ? Long.parseLong(failCount) : 0L);
|
||||
|
||||
statisticsList.add(stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取接口调用统计失败", e);
|
||||
}
|
||||
|
||||
return statisticsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getJdProductTypeMap() {
|
||||
if (stringRedisTemplate == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
Map<Object, Object> rawMap = stringRedisTemplate.opsForHash().entries(PRODUCT_TYPE_MAP_PREFIX);
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : rawMap.entrySet()) {
|
||||
result.put(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("获取京东产品类型映射失败", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getTbProductTypeMap() {
|
||||
if (stringRedisTemplate == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
Map<Object, Object> rawMap = stringRedisTemplate.opsForHash().entries(PRODUCT_TYPE_MAP_PREFIX_TB);
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : rawMap.entrySet()) {
|
||||
result.put(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("获取淘宝产品类型映射失败", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private String getTodayDate() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ import com.ruoyi.jarvis.domain.ErpProduct;
|
||||
import com.ruoyi.jarvis.service.IErpProductService;
|
||||
import com.ruoyi.erp.request.ERPAccount;
|
||||
import com.ruoyi.erp.request.ProductListQueryRequest;
|
||||
import com.ruoyi.erp.request.ProductDeleteRequest;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 闲鱼商品Service业务层处理
|
||||
@@ -281,35 +285,181 @@ public class ErpProductServiceImpl implements IErpProductService
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换商品状态值:将前端的简化状态值转换为API需要的状态值
|
||||
* 前端:1(上架), 2(下架), 3(已售), 22(草稿), 23(审核中)
|
||||
* API:-1(全部), 10(上架), 21(下架), 22(草稿), 23(审核中), 31(已售), 33(已删除), 36(违规)
|
||||
* 如果前端传入的是API已有的状态值(如22、23),直接返回;否则进行映射转换
|
||||
* 转换商品状态值:将前端状态值转换为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;
|
||||
}
|
||||
// 如果是API标准状态值(22、23),直接返回
|
||||
if (frontendStatus == 22 || frontendStatus == 23) {
|
||||
// 直接使用前端传入的状态值(-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;
|
||||
}
|
||||
// 前端简化状态值到API状态值的映射
|
||||
switch (frontendStatus) {
|
||||
case 1: // 上架
|
||||
return 10;
|
||||
case 2: // 下架
|
||||
return 21;
|
||||
case 3: // 已售
|
||||
return 31;
|
||||
default:
|
||||
// 如果是其他API标准状态值(-1, 10, 21, 31, 33, 36),直接返回
|
||||
if (frontendStatus == -1 || frontendStatus == 10 || frontendStatus == 21 ||
|
||||
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);
|
||||
}
|
||||
}
|
||||
log.warn("未知的商品状态值: {}, 将不设置状态筛选", frontendStatus);
|
||||
return null;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,19 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
|
||||
@Override
|
||||
public List<String> execute(String command, boolean forceGenerate) {
|
||||
return execute(command, forceGenerate, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> execute(String command, boolean forceGenerate, boolean isFromConsole) {
|
||||
// 存储接收的消息到Redis队列
|
||||
storeMessageToRedis("instruction:request", command);
|
||||
|
||||
// 检测价保/赔付消息,自动标记后返到账
|
||||
if (command != null && !command.trim().isEmpty()) {
|
||||
handleCompensationMessage(command);
|
||||
}
|
||||
|
||||
List<String> result;
|
||||
|
||||
if (command == null || command.trim().isEmpty()) {
|
||||
@@ -79,19 +89,26 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
// TF/H/生/拼多多 生成类指令
|
||||
else if (input.startsWith("TF")) {
|
||||
result = Collections.singletonList(handleTF(input));
|
||||
String tfResult = handleTF(input, forceGenerate);
|
||||
// 如果包含错误码,按 \n\n 分割成多个结果
|
||||
if (tfResult != null && (tfResult.contains("ERROR_CODE:ADDRESS_DUPLICATE") || tfResult.contains("ERROR_CODE:ORDER_NUMBER_DUPLICATE"))) {
|
||||
String[] parts = tfResult.split("\n\n");
|
||||
result = new ArrayList<>(Arrays.asList(parts));
|
||||
} else {
|
||||
result = Collections.singletonList(tfResult);
|
||||
}
|
||||
} else if (input.startsWith("H")) {
|
||||
result = Collections.singletonList(handleH(input));
|
||||
result = Collections.singletonList(handleH(input, forceGenerate));
|
||||
} else if (input.startsWith("W")) {
|
||||
result = Collections.singletonList(handleW(input));
|
||||
result = Collections.singletonList(handleW(input, forceGenerate));
|
||||
} else if (input.startsWith("生")) {
|
||||
result = Collections.singletonList(handleSheng(input));
|
||||
result = Collections.singletonList(handleSheng(input, forceGenerate));
|
||||
} else if (isPDDWCommand(input)) {
|
||||
result = Collections.singletonList(handlePDDW(input));
|
||||
result = Collections.singletonList(handlePDDW(input, forceGenerate));
|
||||
} else if (input.equals("拼多多") || input.startsWith("拼多多\n") || input.startsWith("拼多多\r\n") || isPDDFormat(input)) {
|
||||
result = Collections.singletonList(handlePDD(input));
|
||||
result = Collections.singletonList(handlePDD(input, forceGenerate));
|
||||
} else if (input.startsWith("录单") || input.startsWith("慢单") || input.startsWith("慢搜") || input.startsWith("慢查") || input.startsWith("单")) {
|
||||
result = handleRecordLikeMulti(input, forceGenerate);
|
||||
result = handleRecordLikeMulti(input, forceGenerate, isFromConsole);
|
||||
} else if (input.startsWith("转链") || input.startsWith("文案") || input.startsWith("转")) {
|
||||
result = Collections.singletonList(handleTransferLike(input));
|
||||
} else {
|
||||
@@ -209,10 +226,14 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
|
||||
private List<String> handleRecordLikeMulti(String input) {
|
||||
return handleRecordLikeMulti(input, false);
|
||||
return handleRecordLikeMulti(input, false, false);
|
||||
}
|
||||
|
||||
private List<String> handleRecordLikeMulti(String input, boolean forceGenerate) {
|
||||
return handleRecordLikeMulti(input, forceGenerate, false);
|
||||
}
|
||||
|
||||
private List<String> handleRecordLikeMulti(String input, boolean forceGenerate, boolean isFromConsole) {
|
||||
// 仅实现“慢搜/慢查 关键词”与“录单/慢单 日期范围”的只读输出,避免侵入写库
|
||||
if (input.startsWith("慢搜") || input.startsWith("慢查")) {
|
||||
String kw = input.replaceFirst("^慢搜|^慢查", "").trim();
|
||||
@@ -610,7 +631,7 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
return outputs.isEmpty() ? Collections.singletonList("无数据") : outputs;
|
||||
}
|
||||
if (input.startsWith("单")) {
|
||||
return Collections.singletonList(handleDanWriteDb(input, forceGenerate));
|
||||
return Collections.singletonList(handleDanWriteDb(input, forceGenerate, isFromConsole));
|
||||
}
|
||||
return Collections.singletonList(helpText());
|
||||
}
|
||||
@@ -629,8 +650,6 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
15639125541
|
||||
13243039070
|
||||
*/
|
||||
phoneWithTF.add("13723151190");
|
||||
phoneWithTF.add("18839187854");
|
||||
phoneWithTF.add("15639125541");
|
||||
phoneWithTF.add("13243039070");
|
||||
|
||||
@@ -646,6 +665,10 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
|
||||
private String handleTF(String input) {
|
||||
return handleTF(input, false);
|
||||
}
|
||||
|
||||
private String handleTF(String input, boolean forceGenerate) {
|
||||
String body = input.replaceFirst("^TF\\s*", "");
|
||||
body = body.replaceAll("[啊阿]", "");
|
||||
String[] lines = body.split("\\r?\\n+");
|
||||
@@ -729,7 +752,7 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
StringBuilder order = new StringBuilder();
|
||||
order.append("生"+phone).append("\n").append(fenxiaoInfo).append("\n").append(modelNumber).append("\n").append(jf).append("\n").append(quantityStr).append("\n").append(address);
|
||||
// 传递第三方单号给 generateOrderText,以便在生成订单文本时正确提取
|
||||
outputs.add(generateOrderText(order.toString(), thirdPartyOrderNo));
|
||||
outputs.add(generateOrderText(order.toString(), thirdPartyOrderNo, forceGenerate));
|
||||
} else {
|
||||
outputs.add("TF 指令格式:TF\t分销信息\t分销信息\t分销信息\t型号\t数量\t姓名\t电话\t地址 ;也支持多行,每行一条数据");
|
||||
}
|
||||
@@ -740,14 +763,26 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
|
||||
|
||||
private String handleH(String input) {
|
||||
return handleHLike(input, "H", "H");
|
||||
return handleH(input, false);
|
||||
}
|
||||
|
||||
private String handleH(String input, boolean forceGenerate) {
|
||||
return handleHLike(input, "H", "H", forceGenerate);
|
||||
}
|
||||
|
||||
private String handleW(String input) {
|
||||
return handleHLike(input, "W", "W");
|
||||
return handleW(input, false);
|
||||
}
|
||||
|
||||
private String handleW(String input, boolean forceGenerate) {
|
||||
return handleHLike(input, "W", "W", forceGenerate);
|
||||
}
|
||||
|
||||
private String handleHLike(String input, String commandKeyword, String distributionMark) {
|
||||
return handleHLike(input, commandKeyword, distributionMark, false);
|
||||
}
|
||||
|
||||
private String handleHLike(String input, String commandKeyword, String distributionMark, boolean forceGenerate) {
|
||||
String cleaned = input.replaceFirst("^" + Pattern.quote(commandKeyword), "");
|
||||
if (cleaned.startsWith("\n")) {
|
||||
cleaned = cleaned.substring(1);
|
||||
@@ -774,10 +809,14 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
StringBuilder sheng = new StringBuilder();
|
||||
sheng.append("生\n").append(distributionMark).append("\n").append(modelNumber).append("\n").append(jfLink != null ? jfLink : "").append("\n")
|
||||
.append("1\n").append(name).append(fullAddress);
|
||||
return generateOrderText(sheng.toString());
|
||||
return generateOrderText(sheng.toString(), null, forceGenerate);
|
||||
}
|
||||
|
||||
private String handleSheng(String input) {
|
||||
return handleSheng(input, false);
|
||||
}
|
||||
|
||||
private String handleSheng(String input, boolean forceGenerate) {
|
||||
// 输入格式:
|
||||
// 生\n分销标记\n型号\n转链链接\n数量\n地址
|
||||
String[] split = input.split("\n");
|
||||
@@ -797,7 +836,7 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
split[3] = link;
|
||||
split[5] = sanitizeAddress(split[5]);
|
||||
return generateOrderText(String.join("\n", split));
|
||||
return generateOrderText(String.join("\n", split), null, forceGenerate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -835,14 +874,26 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
|
||||
private String handlePDD(String input) {
|
||||
return handlePDDWithMark(input, "拼多多", "PDD");
|
||||
return handlePDD(input, false);
|
||||
}
|
||||
|
||||
private String handlePDD(String input, boolean forceGenerate) {
|
||||
return handlePDDWithMark(input, "拼多多", "PDD", forceGenerate);
|
||||
}
|
||||
|
||||
private String handlePDDW(String input) {
|
||||
return handlePDDWithMark(input, "拼多多 W", "PDD-W");
|
||||
return handlePDDW(input, false);
|
||||
}
|
||||
|
||||
private String handlePDDW(String input, boolean forceGenerate) {
|
||||
return handlePDDWithMark(input, "拼多多 W", "PDD-W", forceGenerate);
|
||||
}
|
||||
|
||||
private String handlePDDWithMark(String input, String commandKeyword, String distributionMark) {
|
||||
return handlePDDWithMark(input, commandKeyword, distributionMark, false);
|
||||
}
|
||||
|
||||
private String handlePDDWithMark(String input, String commandKeyword, String distributionMark, boolean forceGenerate) {
|
||||
// 拼多多新格式:
|
||||
// <指令>251102-457567158704072
|
||||
// 赵政委[6947]
|
||||
@@ -986,7 +1037,7 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
.append(fullAddress.toString());
|
||||
|
||||
// 传递第三方单号给 generateOrderText
|
||||
return generateOrderText(sheng.toString(), thirdPartyOrderNo);
|
||||
return generateOrderText(sheng.toString(), thirdPartyOrderNo, forceGenerate);
|
||||
}
|
||||
|
||||
private boolean shouldAutoFillLink(String distributionMark) {
|
||||
@@ -1030,10 +1081,14 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
|
||||
private String generateOrderText(String shengInput) {
|
||||
return generateOrderText(shengInput, null);
|
||||
return generateOrderText(shengInput, null, false);
|
||||
}
|
||||
|
||||
private String generateOrderText(String shengInput, String providedThirdPartyOrderNo) {
|
||||
return generateOrderText(shengInput, providedThirdPartyOrderNo, false);
|
||||
}
|
||||
|
||||
private String generateOrderText(String shengInput, String providedThirdPartyOrderNo, boolean forceGenerate) {
|
||||
String[] split = shengInput.split("\n");
|
||||
// 第一行可能是 生 或 生{备注}
|
||||
String head = split[0].trim();
|
||||
@@ -1058,12 +1113,14 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
String orderNumberKey = "order_number:" + orderNumberForDedup;
|
||||
String existedOrderNumber = stringRedisTemplate.opsForValue().get(orderNumberKey);
|
||||
if (existedOrderNumber != null) {
|
||||
String warn = "[炸弹] [炸弹] [炸弹] 此订单编号(" + orderNumberForDedup + ")已经存在,请勿重复生成订单 [炸弹] [炸弹] [炸弹] ";
|
||||
StringBuilder warnOut = new StringBuilder();
|
||||
warnOut.append(warn).append("\n");
|
||||
return warnOut.toString().trim();
|
||||
// 如果强制生成,跳过订单编号重复检查
|
||||
if (!forceGenerate) {
|
||||
// 返回特殊错误码,前端会识别并弹出验证码
|
||||
return "ERROR_CODE:ORDER_NUMBER_DUPLICATE\n此订单编号(" + orderNumberForDedup + ")已经存在,请勿重复生成订单";
|
||||
}
|
||||
// forceGenerate为true时,跳过订单编号重复检查,继续执行
|
||||
}
|
||||
// 记录订单编号,24小时过期
|
||||
// 只有在不强制生成或订单编号不存在时才设置Redis(强制生成时也更新Redis记录)
|
||||
stringRedisTemplate.opsForValue().set(orderNumberKey, orderNumberForDedup, 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@@ -1073,12 +1130,15 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
String existed = stringRedisTemplate.opsForValue().get(addressKey);
|
||||
if (existed != null) {
|
||||
if (!(existed.contains("李波") || existed.contains("吴胜硕") || existed.contains("小硕硕"))) {
|
||||
String warn = "[炸弹] [炸弹] [炸弹] 此地址已经存在,请勿重复生成订单 [炸弹] [炸弹] [炸弹] ";
|
||||
StringBuilder warnOut = new StringBuilder();
|
||||
warnOut.append(warn).append("\n");
|
||||
return warnOut.toString().trim();
|
||||
// 如果强制生成,跳过地址重复检查
|
||||
if (!forceGenerate) {
|
||||
// 返回特殊错误码,前端会识别并弹出验证码
|
||||
return "ERROR_CODE:ADDRESS_DUPLICATE\n此地址已经存在,请勿重复生成订单";
|
||||
}
|
||||
// forceGenerate为true时,跳过地址重复检查,继续执行
|
||||
}
|
||||
}
|
||||
// 只有在不强制生成或地址不存在时才设置Redis(强制生成时也更新Redis记录)
|
||||
stringRedisTemplate.opsForValue().set(addressKey, address, 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@@ -1217,10 +1277,14 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
|
||||
// ===== "单 …" 写库 =====
|
||||
private String handleDanWriteDb(String input) {
|
||||
return handleDanWriteDb(input, false);
|
||||
return handleDanWriteDb(input, false, false);
|
||||
}
|
||||
|
||||
private String handleDanWriteDb(String input, boolean forceGenerate) {
|
||||
return handleDanWriteDb(input, forceGenerate, false);
|
||||
}
|
||||
|
||||
private String handleDanWriteDb(String input, boolean forceGenerate, boolean isFromConsole) {
|
||||
// 保存原始输入,用于返回前端时保留完整物流链接
|
||||
String originalInput = input.trim().replace("元", "");
|
||||
// 与 JDUtil.parseOrderFromText 一致的模板字段
|
||||
@@ -1240,8 +1304,8 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
return warn;
|
||||
}
|
||||
|
||||
// 订单号重复校验
|
||||
if (!isEmpty(order.getOrderId())) {
|
||||
// 订单号重复校验(控制台入口跳过此校验,允许管理员更新单号)
|
||||
if (!isFromConsole && !isEmpty(order.getOrderId())) {
|
||||
JDOrder existingByOrderId = jdOrderService.selectJDOrderByOrderId(order.getOrderId());
|
||||
if (existingByOrderId != null) {
|
||||
// 如果是更新同一条记录(remark相同),则不提示重复
|
||||
@@ -1255,8 +1319,8 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
}
|
||||
|
||||
// 物流链接重复校验
|
||||
if (!isEmpty(order.getLogisticsLink())) {
|
||||
// 物流链接重复校验(控制台入口跳过此校验,允许管理员更新物流链接)
|
||||
if (!isFromConsole && !isEmpty(order.getLogisticsLink())) {
|
||||
JDOrder existingByLogistics = jdOrderService.selectJDOrderByLogisticsLink(order.getLogisticsLink());
|
||||
if (existingByLogistics != null) {
|
||||
// 如果是更新同一条记录(remark相同),则不提示重复
|
||||
@@ -1270,23 +1334,12 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
}
|
||||
|
||||
// 地址重复检查
|
||||
// 地址重复提示(不阻断写入,与 JDUtil 提示一致)
|
||||
List<JDOrder> byAddress = jdOrderService.selectJDOrderListByAddress(order.getAddress());
|
||||
if (byAddress != null && !byAddress.isEmpty()) {
|
||||
// 如果强制生成,跳过地址重复检查
|
||||
if (!forceGenerate) {
|
||||
// 返回特殊错误码,前端会识别并弹出验证码
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
int count = byAddress.size();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < Math.min(count, 3); i++) {
|
||||
if (i > 0) sb.append("\n");
|
||||
sb.append(sdf.format(byAddress.get(i).getOrderTime()));
|
||||
}
|
||||
// 使用特殊错误码标识地址重复
|
||||
return "ERROR_CODE:ADDRESS_DUPLICATE\n收货地址重复,此地址共" + count + "个订单,最近的订单时间:" + sb.toString();
|
||||
}
|
||||
// forceGenerate为true时,跳过地址重复检查,继续执行
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String latest = sdf.format(byAddress.get(0).getOrderTime());
|
||||
// 仅提示,不阻断写入
|
||||
}
|
||||
|
||||
// 根据订单号查询order_rows,获取京粉实际价格
|
||||
@@ -1302,8 +1355,8 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
}
|
||||
|
||||
// 验证1:如果查询不到对应订单,提示用户
|
||||
if (isEmpty(order.getOrderId()) || orderRow == null) {
|
||||
// 验证1:如果查询不到对应订单,提示用户(控制台入口跳过此校验)
|
||||
if (!isFromConsole && (isEmpty(order.getOrderId()) || orderRow == null)) {
|
||||
String warn = "[炸弹] [炸弹] [炸弹] 录单警告!!! \n查询不到对应订单(使用红包 或者 转链),请五分钟后再试,或重新下单";
|
||||
return warn;
|
||||
}
|
||||
@@ -1321,13 +1374,28 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
}
|
||||
|
||||
// 按 remark 判断新增/更新
|
||||
JDOrder exists = jdOrderService.selectJDOrderByRemark(order.getRemark());
|
||||
if (exists != null) {
|
||||
order.setId(exists.getId());
|
||||
jdOrderService.updateJDOrder(order);
|
||||
} else {
|
||||
jdOrderService.insertJDOrder(order);
|
||||
// 判断新增/更新:所有入口都优先根据订单号更新(用于刷新数据)
|
||||
JDOrder exists = null;
|
||||
|
||||
// 如果订单号不为空,优先根据订单号查找已存在的订单(用于刷新数据)
|
||||
if (!isEmpty(order.getOrderId())) {
|
||||
exists = jdOrderService.selectJDOrderByOrderId(order.getOrderId());
|
||||
if (exists != null) {
|
||||
// 根据订单号找到已存在的订单,更新该订单(刷新数据)
|
||||
order.setId(exists.getId());
|
||||
jdOrderService.updateJDOrder(order);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有通过订单号找到,则按 remark 判断新增/更新
|
||||
if (exists == null) {
|
||||
exists = jdOrderService.selectJDOrderByRemark(order.getRemark());
|
||||
if (exists != null) {
|
||||
order.setId(exists.getId());
|
||||
jdOrderService.updateJDOrder(order);
|
||||
} else {
|
||||
jdOrderService.insertJDOrder(order);
|
||||
}
|
||||
}
|
||||
|
||||
// H-TF订单触发延迟推送机制
|
||||
@@ -1822,6 +1890,110 @@ public class InstructionServiceImpl implements IInstructionService {
|
||||
}
|
||||
}, "TencentDoc-Writer-" + order.getRemark()).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理价保/赔付消息,自动标记后返到账
|
||||
* 消息格式示例:
|
||||
* [爱心] 价保/赔付 : 100 [爱心]
|
||||
* [裂开] 违规订单-违反京东平台规则
|
||||
* 京粉:凡
|
||||
* 订单:343216308806 (非plus)
|
||||
* ...
|
||||
*/
|
||||
private void handleCompensationMessage(String message) {
|
||||
try {
|
||||
// 检测是否包含价保/赔付和违规订单-违反京东平台规则
|
||||
if (message.contains("价保/赔付") || message.contains("价保") || message.contains("赔付")) {
|
||||
if (message.contains("违规订单-违反京东平台规则")) {
|
||||
// 提取订单号:订单:343216308806
|
||||
Pattern orderPattern = Pattern.compile("订单[::]\\s*(\\d+)");
|
||||
Matcher matcher = orderPattern.matcher(message);
|
||||
|
||||
if (matcher.find()) {
|
||||
String orderId = matcher.group(1);
|
||||
|
||||
// 查找订单
|
||||
JDOrder order = jdOrderService.selectJDOrderByOrderId(orderId);
|
||||
if (order != null) {
|
||||
// 设置后返到账状态为1,并设置到账日期为当前时间
|
||||
order.setIsRebateReceived(1);
|
||||
order.setRebateReceivedDate(new Date());
|
||||
|
||||
// 更新订单
|
||||
int updateResult = jdOrderService.updateJDOrder(order);
|
||||
if (updateResult > 0) {
|
||||
System.out.println("✓ 自动标记后返到账成功 - 订单号: " + orderId);
|
||||
} else {
|
||||
System.err.println("✗ 自动标记后返到账失败 - 订单号: " + orderId + " (更新失败)");
|
||||
}
|
||||
} else {
|
||||
System.err.println("✗ 未找到订单 - 订单号: " + orderId);
|
||||
}
|
||||
} else {
|
||||
System.err.println("✗ 无法从消息中提取订单号");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 处理价保/赔付消息时发生异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性批量更新历史订单:将赔付金额大于0的订单标记为后返到账
|
||||
* 此方法只应执行一次,用于处理历史数据
|
||||
*/
|
||||
public void batchMarkRebateReceivedForCompensation() {
|
||||
try {
|
||||
System.out.println("开始批量标记后返到账(赔付金额>0的订单)...");
|
||||
|
||||
// 查询所有订单
|
||||
List<JDOrder> allOrders = jdOrderService.selectJDOrderList(new JDOrder());
|
||||
|
||||
// 查询所有订单的赔付金额(从OrderRows表)
|
||||
int updatedCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
for (JDOrder order : allOrders) {
|
||||
// 查询订单的赔付金额
|
||||
OrderRows orderRows = orderRowsService.selectOrderRowsByOrderId(order.getOrderId());
|
||||
|
||||
if (orderRows != null && orderRows.getProPriceAmount() != null && orderRows.getProPriceAmount() > 0) {
|
||||
// 如果赔付金额大于0,且后返到账状态不是1,则更新
|
||||
if (order.getIsRebateReceived() == null || order.getIsRebateReceived() != 1) {
|
||||
order.setIsRebateReceived(1);
|
||||
// 如果到账日期为空,设置为当前时间
|
||||
if (order.getRebateReceivedDate() == null) {
|
||||
order.setRebateReceivedDate(new Date());
|
||||
}
|
||||
|
||||
int updateResult = jdOrderService.updateJDOrder(order);
|
||||
if (updateResult > 0) {
|
||||
updatedCount++;
|
||||
System.out.println("✓ 标记后返到账 - 订单号: " + order.getOrderId() +
|
||||
", 赔付金额: " + orderRows.getProPriceAmount());
|
||||
} else {
|
||||
System.err.println("✗ 更新失败 - 订单号: " + order.getOrderId());
|
||||
}
|
||||
} else {
|
||||
skippedCount++;
|
||||
}
|
||||
} else {
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("批量标记完成!");
|
||||
System.out.println("更新数量: " + updatedCount);
|
||||
System.out.println("跳过数量: " + skippedCount);
|
||||
System.out.println("总计: " + allOrders.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 批量标记后返到账时发生异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,17 @@ import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.utils.http.HttpUtils;
|
||||
import com.ruoyi.jarvis.domain.JDOrder;
|
||||
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
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 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 PUSH_URL = "https://wxts.van333.cn/wx/send/pdd";
|
||||
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
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Override
|
||||
public boolean isOrderProcessed(Long orderId) {
|
||||
if (orderId == null) {
|
||||
@@ -45,27 +55,49 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查物流链接
|
||||
String logisticsLink = order.getLogisticsLink();
|
||||
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||
logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", order.getId());
|
||||
return false;
|
||||
Long orderId = order.getId();
|
||||
|
||||
// 双重检查:先检查是否已处理过
|
||||
if (isOrderProcessed(orderId)) {
|
||||
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 {
|
||||
// 获取锁后再次检查是否已处理(双重检查锁定模式)
|
||||
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
|
||||
String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8");
|
||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", order.getId(), externalUrl);
|
||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
||||
|
||||
// 在服务端执行HTTP请求
|
||||
String result = HttpUtils.sendGet(externalUrl);
|
||||
|
||||
if (result == null || result.trim().isEmpty()) {
|
||||
logger.warn("外部接口返回空结果 - 订单ID: {}", order.getId());
|
||||
logger.warn("外部接口返回空结果 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", order.getId(), result.length());
|
||||
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", orderId, result.length());
|
||||
|
||||
// 解析返回结果
|
||||
JSONObject parsedData = null;
|
||||
@@ -74,46 +106,97 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
if (parsed instanceof JSONObject) {
|
||||
parsedData = (JSONObject) parsed;
|
||||
} else {
|
||||
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", order.getId());
|
||||
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage());
|
||||
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查waybill_no
|
||||
JSONObject dataObj = parsedData.getJSONObject("data");
|
||||
if (dataObj == null) {
|
||||
logger.info("返回数据中没有data字段 - 订单ID: {}", order.getId());
|
||||
logger.info("返回数据中没有data字段 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
String waybillNo = dataObj.getString("waybill_no");
|
||||
if (waybillNo == null || waybillNo.trim().isEmpty()) {
|
||||
logger.info("waybill_no为空,无需处理 - 订单ID: {}", order.getId());
|
||||
logger.info("waybill_no为空,无需处理 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, order.getId());
|
||||
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, orderId);
|
||||
|
||||
// 兼容处理:检查Redis中是否已有该订单的运单号记录
|
||||
// 如果存在且运单号一致,说明之前已经推送过了(可能是之前没有配置接收人但推送成功的情况)
|
||||
String redisKey = REDIS_WAYBILL_KEY_PREFIX + orderId;
|
||||
String existingWaybillNo = stringRedisTemplate.opsForValue().get(redisKey);
|
||||
|
||||
if (existingWaybillNo != null && existingWaybillNo.trim().equals(waybillNo.trim())) {
|
||||
// 运单号一致,说明之前已经推送过了,直接标记为已处理,跳过推送
|
||||
logger.info("订单运单号已存在且一致,说明之前已推送过,跳过重复推送 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
// 更新过期时间,确保记录不会过期
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 兼容处理:如果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: {}", order.getId(), waybillNo);
|
||||
logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存运单号到Redis(避免重复处理)
|
||||
String redisKey = REDIS_WAYBILL_KEY_PREFIX + order.getId();
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
// 保存运单号到Redis(避免重复处理)- 使用原子操作确保只写入一次
|
||||
Boolean setSuccess = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
|
||||
logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
||||
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;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e);
|
||||
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage(), e);
|
||||
return false;
|
||||
} finally {
|
||||
// 释放分布式锁
|
||||
try {
|
||||
stringRedisTemplate.delete(lockKey);
|
||||
logger.debug("释放订单处理锁 - 订单ID: {}", orderId);
|
||||
} catch (Exception e) {
|
||||
logger.warn("释放订单处理锁失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,15 +224,32 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
// 收货地址
|
||||
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();
|
||||
pushParam.put("title", "JD物流信息推送");
|
||||
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请求
|
||||
String pushResult = sendPostWithHeaders(PUSH_URL, pushParam.toJSONString(), PUSH_TOKEN);
|
||||
String pushResult = sendPostWithHeaders(PUSH_URL, jsonBody, PUSH_TOKEN);
|
||||
if (pushResult == null || pushResult.trim().isEmpty()) {
|
||||
logger.warn("企业应用推送响应为空 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
||||
return false;
|
||||
@@ -173,37 +273,132 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分销标识获取接收人列表
|
||||
* 从系统配置中读取,配置键名格式: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.warn("解析企业应用推送响应失败,将视为未成功: {}", e.getMessage());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.ruoyi.jarvis.service.impl;
|
||||
|
||||
import com.ruoyi.jarvis.domain.TaobaoComment;
|
||||
import com.ruoyi.jarvis.mapper.TaobaoCommentMapper;
|
||||
import com.ruoyi.jarvis.service.ITaobaoCommentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 淘宝评论管理 Service 实现
|
||||
*/
|
||||
@Service
|
||||
public class TaobaoCommentServiceImpl implements ITaobaoCommentService {
|
||||
|
||||
private static final String PRODUCT_TYPE_MAP_PREFIX_TB = "product_type_map_tb";
|
||||
|
||||
@Autowired
|
||||
private TaobaoCommentMapper taobaoCommentMapper;
|
||||
|
||||
@Autowired(required = false)
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public List<TaobaoComment> selectTaobaoCommentList(TaobaoComment taobaoComment) {
|
||||
List<TaobaoComment> list = taobaoCommentMapper.selectTaobaoCommentList(taobaoComment);
|
||||
// 填充Redis映射的产品类型信息
|
||||
if (stringRedisTemplate != null) {
|
||||
Map<String, String> tbMap = getTbProductTypeMap();
|
||||
for (TaobaoComment c : list) {
|
||||
// 查找对应的产品类型
|
||||
String productId = c.getProductId();
|
||||
if (tbMap != null) {
|
||||
for (Map.Entry<String, String> entry : tbMap.entrySet()) {
|
||||
if (entry.getValue().equals(productId)) {
|
||||
c.setProductType(entry.getKey());
|
||||
c.setMappedProductId(productId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaobaoComment selectTaobaoCommentById(Integer id) {
|
||||
TaobaoComment comment = taobaoCommentMapper.selectTaobaoCommentById(id);
|
||||
if (comment != null && stringRedisTemplate != null) {
|
||||
Map<String, String> tbMap = getTbProductTypeMap();
|
||||
if (tbMap != null) {
|
||||
String productId = comment.getProductId();
|
||||
for (Map.Entry<String, String> entry : tbMap.entrySet()) {
|
||||
if (entry.getValue().equals(productId)) {
|
||||
comment.setProductType(entry.getKey());
|
||||
comment.setMappedProductId(productId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateTaobaoCommentIsUse(TaobaoComment taobaoComment) {
|
||||
return taobaoCommentMapper.updateTaobaoCommentIsUse(taobaoComment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteTaobaoCommentByIds(Integer[] ids) {
|
||||
return taobaoCommentMapper.deleteTaobaoCommentByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int resetTaobaoCommentIsUseByProductId(String productId) {
|
||||
return taobaoCommentMapper.resetTaobaoCommentIsUseByProductId(productId);
|
||||
}
|
||||
|
||||
private Map<String, String> getTbProductTypeMap() {
|
||||
if (stringRedisTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Map<Object, Object> rawMap = stringRedisTemplate.opsForHash().entries(PRODUCT_TYPE_MAP_PREFIX_TB);
|
||||
Map<String, String> result = new java.util.LinkedHashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : rawMap.entrySet()) {
|
||||
result.put(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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.CommentMapper">
|
||||
|
||||
<resultMap id="CommentResult" type="Comment">
|
||||
<result property="id" column="id"/>
|
||||
<result property="productId" column="product_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
<result property="commentText" column="comment_text"/>
|
||||
<result property="commentId" column="comment_id"/>
|
||||
<result property="pictureUrls" column="picture_urls"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="commentDate" column="comment_date"/>
|
||||
<result property="isUse" column="is_use"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectCommentBase">
|
||||
select id, product_id, user_name, comment_text, comment_id, picture_urls,
|
||||
created_at, comment_date, is_use
|
||||
from comments
|
||||
</sql>
|
||||
|
||||
<select id="selectCommentList" parameterType="Comment" resultMap="CommentResult">
|
||||
<include refid="selectCommentBase"/>
|
||||
<where>
|
||||
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
|
||||
<if test="userName != null and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
|
||||
<if test="commentText != null and commentText != ''"> and comment_text like concat('%', #{commentText}, '%')</if>
|
||||
<if test="isUse != null"> and is_use = #{isUse}</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
and created_at >= #{params.beginTime}
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
and created_at <= #{params.endTime}
|
||||
</if>
|
||||
</where>
|
||||
order by created_at desc
|
||||
</select>
|
||||
|
||||
<select id="selectCommentById" parameterType="Long" resultMap="CommentResult">
|
||||
<include refid="selectCommentBase"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectCommentStatisticsByProductId" parameterType="String" resultType="java.util.Map">
|
||||
select
|
||||
count(*) as totalCount,
|
||||
sum(case when is_use = 0 then 1 else 0 end) as availableCount,
|
||||
sum(case when is_use = 1 then 1 else 0 end) as usedCount
|
||||
from comments
|
||||
where product_id = #{productId}
|
||||
</select>
|
||||
|
||||
<update id="updateCommentIsUse" parameterType="Comment">
|
||||
update comments
|
||||
<set>
|
||||
<if test="isUse != null">is_use = #{isUse},</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="resetCommentIsUseByProductId" parameterType="String">
|
||||
update comments
|
||||
set is_use = 0
|
||||
where product_id = #{productId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteCommentByIds" parameterType="String">
|
||||
delete from comments where id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -23,11 +23,18 @@
|
||||
<result property="isCountEnabled" column="is_count_enabled"/>
|
||||
<result property="thirdPartyOrderNo" column="third_party_order_no"/>
|
||||
<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>
|
||||
|
||||
<sql id="selectJDOrderBase">
|
||||
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
|
||||
</sql>
|
||||
|
||||
@@ -35,18 +42,26 @@
|
||||
<include refid="selectJDOrderBase"/>
|
||||
<where>
|
||||
<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="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
||||
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
||||
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</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="orderId != null and orderId != ''"> and order_id like concat('%', #{orderId}, '%')</if>
|
||||
<if test="thirdPartyOrderNo != null and thirdPartyOrderNo != ''"> and third_party_order_no like concat('%', #{thirdPartyOrderNo}, '%')</if>
|
||||
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
||||
<if test="orderTime != null"> and order_time = #{orderTime}</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 != ''"><!-- 开始时间检索 -->
|
||||
and date(order_time) >= #{params.beginTime}
|
||||
</if>
|
||||
@@ -61,18 +76,26 @@
|
||||
<include refid="selectJDOrderBase"/>
|
||||
<where>
|
||||
<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="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
|
||||
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
|
||||
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</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="orderId != null and orderId != ''"> and order_id like concat('%', #{orderId}, '%')</if>
|
||||
<if test="thirdPartyOrderNo != null and thirdPartyOrderNo != ''"> and third_party_order_no like concat('%', #{thirdPartyOrderNo}, '%')</if>
|
||||
<if test="buyer != null and buyer != ''"> and buyer like concat('%', #{buyer}, '%')</if>
|
||||
<if test="orderTime != null"> and order_time = #{orderTime}</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 != ''"><!-- 开始时间检索 -->
|
||||
and date(order_time) >= #{params.beginTime}
|
||||
</if>
|
||||
@@ -102,12 +125,14 @@
|
||||
remark, distribution_mark, model_number, link,
|
||||
payment_amount, rebate_amount, address, logistics_link,
|
||||
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 (
|
||||
#{remark}, #{distributionMark}, #{modelNumber}, #{link},
|
||||
#{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink},
|
||||
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>
|
||||
|
||||
@@ -131,6 +156,12 @@
|
||||
<if test="isCountEnabled != null"> is_count_enabled = #{isCountEnabled},</if>
|
||||
<if test="thirdPartyOrderNo != null"> third_party_order_no = #{thirdPartyOrderNo},</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()
|
||||
</set>
|
||||
where id = #{id}
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
<result property="secretKey" column="secret_key"/>
|
||||
<result property="isActive" column="is_active"/>
|
||||
<result property="isCount" column="is_count"/>
|
||||
<result property="touser" column="touser"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="updatedAt" column="updated_at"/>
|
||||
</resultMap>
|
||||
|
||||
<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>
|
||||
|
||||
<select id="selectSuperAdminList" parameterType="SuperAdmin" resultMap="SuperAdminResult">
|
||||
@@ -51,6 +52,8 @@
|
||||
<if test="appKey != null and appKey != ''">app_key,</if>
|
||||
<if test="secretKey != null and secretKey != ''">secret_key,</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,
|
||||
updated_at,
|
||||
</trim>
|
||||
@@ -61,6 +64,8 @@
|
||||
<if test="appKey != null and appKey != ''">#{appKey},</if>
|
||||
<if test="secretKey != null and secretKey != ''">#{secretKey},</if>
|
||||
<if test="isActive != null">#{isActive},</if>
|
||||
<if test="isCount != null">#{isCount},</if>
|
||||
<if test="touser != null and touser != ''">#{touser},</if>
|
||||
now(),
|
||||
now(),
|
||||
</trim>
|
||||
@@ -76,6 +81,7 @@
|
||||
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
|
||||
<if test="isActive != null">is_active = #{isActive},</if>
|
||||
<if test="isCount != null">is_count = #{isCount},</if>
|
||||
<if test="touser != null">touser = #{touser},</if>
|
||||
updated_at = now(),
|
||||
</trim>
|
||||
where id = #{id}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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.TaobaoCommentMapper">
|
||||
|
||||
<resultMap id="TaobaoCommentResult" type="TaobaoComment">
|
||||
<result property="id" column="id"/>
|
||||
<result property="productId" column="product_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
<result property="commentText" column="comment_text"/>
|
||||
<result property="commentId" column="comment_id"/>
|
||||
<result property="pictureUrls" column="picture_urls"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="commentDate" column="comment_date"/>
|
||||
<result property="isUse" column="is_use"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTaobaoCommentBase">
|
||||
select id, product_id, user_name, comment_text, comment_id, picture_urls,
|
||||
created_at, comment_date, is_use
|
||||
from taobao_comments
|
||||
</sql>
|
||||
|
||||
<select id="selectTaobaoCommentList" parameterType="TaobaoComment" resultMap="TaobaoCommentResult">
|
||||
<include refid="selectTaobaoCommentBase"/>
|
||||
<where>
|
||||
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
|
||||
<if test="userName != null and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
|
||||
<if test="commentText != null and commentText != ''"> and comment_text like concat('%', #{commentText}, '%')</if>
|
||||
<if test="isUse != null"> and is_use = #{isUse}</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
and created_at >= #{params.beginTime}
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
and created_at <= #{params.endTime}
|
||||
</if>
|
||||
</where>
|
||||
order by created_at desc
|
||||
</select>
|
||||
|
||||
<select id="selectTaobaoCommentById" parameterType="Integer" resultMap="TaobaoCommentResult">
|
||||
<include refid="selectTaobaoCommentBase"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectTaobaoCommentStatisticsByProductId" parameterType="String" resultType="java.util.Map">
|
||||
select
|
||||
count(*) as totalCount,
|
||||
sum(case when is_use = 0 then 1 else 0 end) as availableCount,
|
||||
sum(case when is_use = 1 then 1 else 0 end) as usedCount
|
||||
from taobao_comments
|
||||
where product_id = #{productId}
|
||||
</select>
|
||||
|
||||
<update id="updateTaobaoCommentIsUse" parameterType="TaobaoComment">
|
||||
update taobao_comments
|
||||
<set>
|
||||
<if test="isUse != null">is_use = #{isUse},</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="resetTaobaoCommentIsUseByProductId" parameterType="String">
|
||||
update taobao_comments
|
||||
set is_use = 0
|
||||
where product_id = #{productId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTaobaoCommentByIds" parameterType="String">
|
||||
delete from taobao_comments where id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
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);
|
||||
|
||||
@@ -6,7 +6,7 @@ CREATE TABLE `erp_product` (
|
||||
`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:上架 2:下架 3:已售',
|
||||
`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 '上架时间(时间戳)',
|
||||
|
||||
Reference in New Issue
Block a user