From a88600788ab96334a8984ec16e8a90f98afa95e2 Mon Sep 17 00:00:00 2001 From: van Date: Thu, 30 Apr 2026 17:10:34 +0800 Subject: [PATCH] 1 --- .../WeComShareLinkLogisticsJobMapper.java | 11 + .../IWeComShareLinkLogisticsJobService.java | 6 + .../service/impl/InstructionServiceImpl.java | 210 ++++++++++++++++++ .../service/impl/WeComInboundServiceImpl.java | 1 + ...WeComShareLinkLogisticsJobServiceImpl.java | 26 +++ .../WeComShareLinkLogisticsJobMapper.xml | 16 ++ 6 files changed, 270 insertions(+) diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComShareLinkLogisticsJobMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComShareLinkLogisticsJobMapper.java index d27aae6..408bd47 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComShareLinkLogisticsJobMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComShareLinkLogisticsJobMapper.java @@ -27,4 +27,15 @@ public interface WeComShareLinkLogisticsJobMapper { List selectJobsNeedingQueueReconcile(@Param("limit") int limit); int deleteByJobKey(@Param("jobKey") String jobKey); + + /** + * 机器人「京外物列表」:最近若干天内的任务,按 id 倒序,可选备注子串筛选。 + */ + List selectRecentForInstruction(@Param("remarkKeyword") String remarkKeyword, + @Param("days") int days, @Param("limit") int limit); + + /** + * 机器人「京外物删」:按备注与短链精确匹配(trim)物理删除,返回删除行数。 + */ + int deleteByRemarkAndTrackingUrl(@Param("remark") String remark, @Param("trackingUrl") String trackingUrl); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComShareLinkLogisticsJobService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComShareLinkLogisticsJobService.java index 219a6b2..3eca5d6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComShareLinkLogisticsJobService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComShareLinkLogisticsJobService.java @@ -16,4 +16,10 @@ public interface IWeComShareLinkLogisticsJobService { * jobKey 固定为 tracebf{traceId},可重复执行跳过已存在项。 */ Map backfillImportedFromInboundTrace(); + + List selectRecentForInstruction(String remarkKeyword, int days, int limit); + + int deleteByJobKey(String jobKey); + + int deleteByRemarkAndTrackingUrl(String remark, String trackingUrl); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java index c8b0f27..c8a4f2a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java @@ -2,12 +2,14 @@ package com.ruoyi.jarvis.service.impl; import com.ruoyi.jarvis.domain.OrderRows; import com.ruoyi.jarvis.domain.JDOrder; +import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob; import com.ruoyi.jarvis.service.IInstructionService; import com.ruoyi.jarvis.service.IOrderRowsService; import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IProductJdConfigService; import com.ruoyi.jarvis.service.IPhoneReplaceConfigService; import com.ruoyi.jarvis.service.SuperAdminService; +import com.ruoyi.jarvis.service.IWeComShareLinkLogisticsJobService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -51,12 +53,20 @@ public class InstructionServiceImpl implements IInstructionService { @Resource private com.ruoyi.jarvis.service.ITencentDocTokenService tencentDocTokenService; @Resource + private IWeComShareLinkLogisticsJobService weComShareLinkLogisticsJobService; + @Resource private com.ruoyi.jarvis.config.TencentDocConfig tencentDocConfig; @Resource private com.ruoyi.common.core.redis.RedisCache redisCache; @Autowired(required = false) private com.ruoyi.jarvis.service.ITencentDocDelayedPushService tencentDocDelayedPushService; + /** 与企微物流分享链解析一致 */ + private static final Pattern JD_3CN = Pattern.compile("https://3\\.cn/[A-Za-z0-9\\-]+"); + + private static final int EXTERNAL_LOGISTICS_LIST_DAYS = 60; + private static final int EXTERNAL_LOGISTICS_LIST_LIMIT = 35; + // 录单模板(与 jd/JDUtil 中 WENAN_D 保持一致) private static final String WENAN_D = "单:\n" + "{单号} \n" + "分销标记:{分销标记}\n" + "第三方单号:{第三方单号}\n" + "—————————\n" + "下单链接(必须用这个):\n" + "{链接}\n" + "下单地址(注意带分机):\n" + "{地址}\n" + "—————————\n" + "型号:{型号}\n" + "\n" + "下单人(需填):\n" + "\n" + "下单付款(注意核对):\n" + "\n" + "后返金额(注意核对):\n" + "\n" + "订单号(需填):\n" + "\n" + "物流链接(需填):\n" + "\n" + "备注(下单号码有变动/没法带分机号的写这里):\n" + "{单的备注}\n" + "—————————\n" + "京粉实际价格:不用填"; @@ -198,6 +208,15 @@ public class InstructionServiceImpl implements IInstructionService { return jingMenu(); } + if (action.startsWith("外物列表")) { + String kw = action.substring("外物列表".length()).trim(); + return textExternalShareLinkLogisticsList(kw); + } + if (action.startsWith("外物删")) { + String rest = action.substring("外物删".length()).trim(); + return textExternalShareLinkLogisticsDelete(rest); + } + // 取出所有订单(排除被删除/无效:这里沿用 OrderRowsService 的常规查询,必要时可增加过滤参数) List all = orderRowsService.selectOrderRowsList(new OrderRows()); if (all == null) all = Collections.emptyList(); @@ -2128,6 +2147,194 @@ public class InstructionServiceImpl implements IInstructionService { }).collect(Collectors.toList()); } + /** + * 企微「外部分享链物流」登记查询:用于发现同一备注、不同短链等重复登记。 + */ + private String textExternalShareLinkLogisticsList(String remarkKeyword) { + if (weComShareLinkLogisticsJobService == null) { + return "「外物列表」\n\n服务未就绪。"; + } + List rows = weComShareLinkLogisticsJobService.selectRecentForInstruction( + remarkKeyword, EXTERNAL_LOGISTICS_LIST_DAYS, EXTERNAL_LOGISTICS_LIST_LIMIT); + if (rows == null || rows.isEmpty()) { + return "「外物列表」\n\n近 " + EXTERNAL_LOGISTICS_LIST_DAYS + " 天内无匹配记录。" + + (remarkKeyword.isEmpty() ? "" : "\n关键词:「" + remarkKeyword + "」"); + } + Map remarkCount = rows.stream() + .map(j -> j.getUserRemark() != null ? j.getUserRemark().trim() : "") + .collect(Collectors.groupingBy(s -> s, Collectors.counting())); + StringBuilder sb = new StringBuilder(); + sb.append("「外物列表」近").append(EXTERNAL_LOGISTICS_LIST_DAYS).append("天,最多") + .append(EXTERNAL_LOGISTICS_LIST_LIMIT).append("条"); + if (!remarkKeyword.isEmpty()) { + sb.append(",关键词「").append(remarkKeyword).append("」"); + } + sb.append("\n\n"); + int i = 0; + for (WeComShareLinkLogisticsJob j : rows) { + i++; + String rk = j.getJobKey() != null ? j.getJobKey() : ""; + String st = j.getStatus() != null ? j.getStatus() : ""; + String rm = j.getUserRemark() != null ? j.getUserRemark().trim() : ""; + if (rm.length() > 80) { + rm = rm.substring(0, 80) + "…"; + } + String url = j.getTrackingUrl() != null ? j.getTrackingUrl().trim() : ""; + long dup = remarkCount.getOrDefault(j.getUserRemark() != null ? j.getUserRemark().trim() : "", 0L); + sb.append(i).append(". ").append(st); + if (dup > 1) { + sb.append(" ·本批同备注").append(dup).append("条"); + } + sb.append("\nkey=").append(rk); + sb.append("\n备注:").append(rm.isEmpty() ? "(空)" : rm); + sb.append("\n链:").append(url); + if (j.getCreateTime() != null) { + sb.append("\n时:").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm") + .format(j.getCreateTime())); + } + sb.append("\n——————\n"); + } + sb.append("删:京外物删 key\n或:京外物删 后接换行写备注,最后一行写含 3.cn 的链接。"); + return sb.toString(); + } + + /** + * 删除外部分享链物流任务行(与后台物理删除一致;Redis 中未消费项会因库无行而跳过)。 + */ + private String textExternalShareLinkLogisticsDelete(String rest) { + if (weComShareLinkLogisticsJobService == null) { + return "「外物删」\n\n服务未就绪。"; + } + if (rest == null || rest.isEmpty()) { + return "「外物删」\n\n用法:\n1) 京外物删 (见「京外物列表」中的 key=)\n" + + "2) 京外物删\n<备注全文>\nhttps://3.cn/…"; + } + String oneLine = rest.replace("\r\n", "\n").trim(); + if (!oneLine.contains("\n") && looksLikeShareLinkJobKey(oneLine)) { + WeComShareLinkLogisticsJob existed = weComShareLinkLogisticsJobService.selectByJobKey(oneLine); + int n = weComShareLinkLogisticsJobService.deleteByJobKey(oneLine); + if (n <= 0) { + return "「外物删」\n\n未删除任何行(jobKey 可能不存在):" + oneLine; + } + String hint = existed != null + ? "\n备注:" + shortenForReply(existed.getUserRemark(), 120) + + "\n链:" + nvl(existed.getTrackingUrl()) + : ""; + return "「外物删」\n\n已删除 " + n + " 条。" + hint; + } + ParsedRemarkAndUrl parsed = parseRemarkAnd3cnFromDeletePayload(rest); + if (parsed == null) { + return "「外物删」\n\n未能解析备注与 3.cn 链接。请多行发送:倒数第一个含 3.cn 的为链接,其上一行起为备注;" + + "或单行:京外物删 "; + } + int n = weComShareLinkLogisticsJobService.deleteByRemarkAndTrackingUrl(parsed.remark, parsed.trackingUrl); + if (n <= 0) { + return "「外物删」\n\n未找到完全匹配的行(备注与短链需与登记一致,含空格请对齐)。\n备注:" + + shortenForReply(parsed.remark, 200) + "\n链:" + parsed.trackingUrl; + } + return "「外物删」\n\n已按备注+链接删除 " + n + " 条。"; + } + + private static boolean looksLikeShareLinkJobKey(String s) { + if (s == null) { + return false; + } + String t = s.trim(); + return t.matches("^[a-fA-F0-9]{32}$") || t.matches("^tracebf\\d+$"); + } + + private static String shortenForReply(String s, int maxChars) { + if (s == null) { + return ""; + } + String t = s.trim(); + if (t.length() <= maxChars) { + return t; + } + return t.substring(0, maxChars) + "…"; + } + + private static final class ParsedRemarkAndUrl { + final String remark; + final String trackingUrl; + + ParsedRemarkAndUrl(String remark, String trackingUrl) { + this.remark = remark; + this.trackingUrl = trackingUrl; + } + } + + private static ParsedRemarkAndUrl parseRemarkAnd3cnFromDeletePayload(String rest) { + if (rest == null) { + return null; + } + String normalized = rest.replace("\r\n", "\n").trim(); + String[] lines = normalized.split("\n"); + if (lines.length == 1) { + ParsedRemarkAndUrl one = tryParseRemarkAndUrlSingleLine(lines[0]); + if (one != null) { + return one; + } + } + int urlIndex = -1; + String canonicalUrl = null; + for (int i = lines.length - 1; i >= 0; i--) { + String u = extractJd3cnForInstruction(lines[i]); + if (u != null) { + urlIndex = i; + canonicalUrl = u; + break; + } + } + if (canonicalUrl == null || urlIndex < 0) { + return null; + } + StringBuilder rem = new StringBuilder(); + for (int i = 0; i < urlIndex; i++) { + if (i > 0) { + rem.append('\n'); + } + rem.append(lines[i].trim()); + } + String remark = rem.toString().trim(); + return new ParsedRemarkAndUrl(remark, canonicalUrl); + } + + /** 单行「…备注… https://3.cn/…」 */ + private static ParsedRemarkAndUrl tryParseRemarkAndUrlSingleLine(String line) { + if (line == null || line.isEmpty()) { + return null; + } + Matcher m = JD_3CN.matcher(line); + if (m.find()) { + String url = m.group(); + String rem = line.substring(0, m.start()).trim(); + return new ParsedRemarkAndUrl(rem, url); + } + Matcher m2 = Pattern.compile("http://3\\.cn/[A-Za-z0-9\\-]+").matcher(line); + if (m2.find()) { + String url = m2.group().replace("http://", "https://"); + String rem = line.substring(0, m2.start()).trim(); + return new ParsedRemarkAndUrl(rem, url); + } + return null; + } + + private static String extractJd3cnForInstruction(String text) { + if (text == null) { + return null; + } + Matcher m = JD_3CN.matcher(text); + if (m.find()) { + return m.group(); + } + Matcher m2 = Pattern.compile("http://3\\.cn/[A-Za-z0-9\\-]+").matcher(text); + if (m2.find()) { + return m2.group().replace("http://", "https://"); + } + return null; + } + // ===== 工具 ===== private String jingMenu() { return "「京粉 · 菜单」\n\n" @@ -2138,6 +2345,8 @@ public class InstructionServiceImpl implements IInstructionService { + "这个月统计、上个月统计、总统计\n\n" + "—— 订单 ——\n" + "今日订单、昨日订单、七日订单\n\n" + + "—— 外部分享链物流 ——\n" + + "京外物列表 [关键词]、京外物删(见列表说明)\n\n" + "发「京」单独或「京菜单」可再次打开本列表。"; } @@ -2148,6 +2357,7 @@ public class InstructionServiceImpl implements IInstructionService { + "· 京今日统计 / 京昨日统计 / 京七日统计 …\n" + "· 京今日订单 / 京昨日订单 / 京七日订单\n" + "· 慢搜 关键词、慢查 关键词(录单库模糊查询)\n" + + "· 京外物列表 / 京外物删 — 企微 3.cn 分享链登记查询与删除\n" + "· 录单20250101-20250107 或 录单昨日|三日|七日(导出)\n\n" + "说明:转链、礼金等请使用系统内「一键转链」页面。"; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundServiceImpl.java index 8dbfabf..f1627d8 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundServiceImpl.java @@ -56,6 +56,7 @@ public class WeComInboundServiceImpl implements IWeComInboundService { + "当前账号支持:\n" + "· 「京」开头的统计、订单类指令(可先发「京菜单」查看列表)\n" + "· 含 3.cn 的京东物流分享:先发链接,再发备注\n" + + "· 京外物列表 / 京外物删 — 查询或删除外部分享链物流登记\n" + "· 以「单」或「开始」开头,且含「分销标记」的录单正文\n" + "· 以「开」或「慢开」开头且含手机号的查询\n\n" + "如需其他指令,请联系管理员。"; diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComShareLinkLogisticsJobServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComShareLinkLogisticsJobServiceImpl.java index f85fe94..1598436 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComShareLinkLogisticsJobServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComShareLinkLogisticsJobServiceImpl.java @@ -47,6 +47,32 @@ public class WeComShareLinkLogisticsJobServiceImpl implements IWeComShareLinkLog return weComShareLinkLogisticsJobMapper.selectWeComShareLinkLogisticsJobList(query); } + @Override + public List selectRecentForInstruction(String remarkKeyword, int days, int limit) { + int d = Math.max(1, Math.min(days, 365)); + int lim = Math.max(1, Math.min(limit, 100)); + String kw = + remarkKeyword != null && !remarkKeyword.trim().isEmpty() ? remarkKeyword.trim() : null; + return weComShareLinkLogisticsJobMapper.selectRecentForInstruction(kw, d, lim); + } + + @Override + public int deleteByJobKey(String jobKey) { + if (jobKey == null || !StringUtils.hasText(jobKey.trim())) { + return 0; + } + return weComShareLinkLogisticsJobMapper.deleteByJobKey(jobKey.trim()); + } + + @Override + public int deleteByRemarkAndTrackingUrl(String remark, String trackingUrl) { + if (!StringUtils.hasText(trackingUrl)) { + return 0; + } + String r = remark != null ? remark : ""; + return weComShareLinkLogisticsJobMapper.deleteByRemarkAndTrackingUrl(r, trackingUrl.trim()); + } + @Override public Map backfillImportedFromInboundTrace() { int imported = 0; diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/WeComShareLinkLogisticsJobMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/WeComShareLinkLogisticsJobMapper.xml index d98b3be..2a12a39 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/WeComShareLinkLogisticsJobMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/WeComShareLinkLogisticsJobMapper.xml @@ -82,4 +82,20 @@ delete from wecom_share_link_logistics_job where job_key = #{jobKey} + + + + + delete from wecom_share_link_logistics_job + where trim(remark) = trim(#{remark}) + and trim(tracking_url) = trim(#{trackingUrl}) +