This commit is contained in:
2025-08-27 19:41:34 +08:00
parent 3de81956cb
commit 74e80584ca
3 changed files with 400 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
package com.ruoyi.web.controller.jarvis;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.jarvis.service.IInstructionService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 指令执行控制器:将 jd/JDUtil 的指令处理迁移为 HTTP 接口
*/
@RestController
@RequestMapping("/jarvis/instruction")
public class InstructionController extends BaseController {
private final IInstructionService instructionService;
public InstructionController(IInstructionService instructionService) {
this.instructionService = instructionService;
}
/**
* 执行文本指令
* body: { command: "京今日统计" }
*/
@PostMapping("/execute")
public AjaxResult execute(@RequestBody Map<String, String> body) {
String cmd = body != null ? body.get("command") : null;
String result = instructionService.execute(cmd);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.jarvis.service;
/**
* 指令执行服务
*/
public interface IInstructionService {
/**
* 执行文本指令,返回结果文本
* @param command 指令内容
* @return 执行结果文本
*/
String execute(String command);
}

View File

@@ -0,0 +1,347 @@
package com.ruoyi.jarvis.service.impl;
import com.ruoyi.jarvis.domain.OrderRows;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.service.IInstructionService;
import com.ruoyi.jarvis.service.IOrderRowsService;
import com.ruoyi.jarvis.service.IJDOrderService;
import com.ruoyi.jarvis.service.SuperAdminService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 将 jd 项目中 JDUtil 的指令能力,抽取为无微信依赖的本地执行版。
* 仅实现最常用统计/订单查询与转链占位能力,按需扩展。
*/
@Service
public class InstructionServiceImpl implements IInstructionService {
@Resource
private IOrderRowsService orderRowsService;
@Resource
private IJDOrderService jdOrderService;
@Resource
private SuperAdminService superAdminService;
@Override
public String execute(String command) {
if (command == null || command.trim().isEmpty()) {
return "请输入指令";
}
String input = command.trim();
// 一级命令分流:京系(统计/订单)、录单/慢单、转链/礼金…
if (input.startsWith("") || menuKeywords().contains(input)) {
return handleJingFen(input.replaceFirst("^京", ""));
}
if (input.startsWith("录单") || input.startsWith("慢单") || input.startsWith("慢搜") || input.startsWith("慢查") || input.startsWith("")) {
return handleRecordLike(input);
}
if (input.startsWith("转链") || input.startsWith("文案") || input.startsWith("")) {
return handleTransferLike(input);
}
return helpText();
}
private Set<String> menuKeywords() {
return new HashSet<>(Arrays.asList("菜单","今日统计","昨日统计","三日统计","七日统计","一个月统计","两个月统计","三个月统计","这个月统计","上个月统计","今日订单","昨日订单","七日订单","总统计"));
}
private String handleJingFen(String cmd) {
String action = cmd.trim();
if (action.isEmpty() || action.equals("菜单")) {
return jingMenu();
}
// 取出所有订单(排除被删除/无效:这里沿用 OrderRowsService 的常规查询,必要时可增加过滤参数)
List<OrderRows> all = orderRowsService.selectOrderRowsList(new OrderRows());
if (all == null) all = Collections.emptyList();
switch (action) {
case "今日统计": return statsText(filterByDays(all, 0), "今日统计");
case "昨日统计": return statsText(filterYesterday(all), "昨日统计");
case "三日统计": return statsText(filterByRange(all, 3), "三日统计");
case "七日统计": return statsText(filterByRange(all, 7), "七日统计");
case "一个月统计": return statsText(filterByRange(all, 30), "一个月统计");
case "两个月统计": return statsText(filterByRange(all, 60), "两个月统计");
case "三个月统计": return statsText(filterByRange(all, 90), "三个月统计");
case "这个月统计": return statsText(filterThisMonth(all), "这个月统计");
case "上个月统计": return statsText(filterLastMonth(all), "上个月统计");
case "总统计": return statsText(all, "总统计");
case "今日订单": return listOrders(filterByDays(all, 0), "今日订单");
case "昨日订单": return listOrders(filterYesterday(all), "昨日订单");
case "七日订单": return listOrders(filterByRange(all, 7), "七日订单");
default:
// 高级命令违规N、SKU、搜索、JF… 此处按需扩展
if (action.startsWith("高级")) {
return "高级指令暂未开放";
}
return jingMenu();
}
}
private String handleRecordLike(String input) {
// 仅实现“慢搜/慢查 关键词”与“录单/慢单 日期范围”的只读输出,避免侵入写库
if (input.startsWith("慢搜") || input.startsWith("慢查")) {
String kw = input.replaceFirst("^慢搜|^慢查", "").trim();
if (kw.isEmpty()) return "请输入搜索关键词";
List<JDOrder> list = jdOrderService.selectJDOrderList(new JDOrder());
if (list == null) list = Collections.emptyList();
String low = kw.toLowerCase(Locale.ROOT);
List<JDOrder> matched = list.stream().filter(o ->
contains(o.getRemark(), low) || contains(o.getOrderId(), low) || contains(o.getModelNumber(), low) || contains(o.getAddress(), low) || contains(o.getBuyer(), low)
).limit(50).collect(Collectors.toList());
if (matched.isEmpty()) return "未找到匹配的订单";
StringBuilder sb = new StringBuilder();
int i = 0;
for (JDOrder o : matched) {
i++;
sb.append(i).append(", 单:").append(nvl(o.getRemark()))
.append("\n分销标记").append(nvl(o.getDistributionMark()))
.append("\n型号").append(nvl(o.getModelNumber()))
.append("\n链接").append(nvl(o.getLink()))
.append("\n下单付款").append(nvl(o.getPaymentAmount()))
.append("\n后返金额").append(nvl(o.getRebateAmount()))
.append("\n地址").append(nvl(o.getAddress()))
.append("\n物流链接").append(nvl(o.getLogisticsLink()))
.append("\n订单号").append(nvl(o.getOrderId()))
.append("\n下单人").append(nvl(o.getBuyer()))
.append("\n下单时间").append(nvl(o.getOrderTime()))
.append("\n备注").append(nvl(o.getStatus()))
.append("\n━━━━━━━━━━━━\n");
}
return sb.toString();
}
if (input.startsWith("录单") || input.startsWith("慢单")) {
// 提取日期yyyyMMdd 或 yyyyMMdd-yyyyMMdd 或 关键词:昨日/三日/七日)
List<LocalDate> range = parseDateRange(input.replaceFirst("^录单|^慢单", "").trim());
if (range == null) {
LocalDate today = effectiveToday().toLocalDate();
range = Arrays.asList(today, today);
}
Date start = Date.from(range.get(0).atStartOfDay(ZoneId.systemDefault()).toInstant());
Date end = Date.from(range.get(1).atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
List<JDOrder> list = jdOrderService.selectJDOrderList(new JDOrder());
if (list == null) list = Collections.emptyList();
List<JDOrder> filtered = list.stream().filter(o -> o.getOrderTime()!=null && !o.getOrderTime().before(start) && !o.getOrderTime().after(end)).collect(Collectors.toList());
if (filtered.isEmpty()) return "时间范围内没有订单";
StringBuilder sb = new StringBuilder();
for (JDOrder o : filtered) {
sb.append(nvl(o.getRemark())).append('\t')
.append(nvl(o.getOrderId())).append('\t')
.append(fmt(o.getOrderTime())).append('\t')
.append(nvl(o.getModelNumber())).append('\t')
.append(nvl(o.getAddress())).append('\t')
.append(nvl(o.getLogisticsLink())).append('\t')
.append('\t').append(nvl(o.getBuyer())).append('\t')
.append(nvl(o.getPaymentAmount())).append('\t')
.append(nvl(o.getRebateAmount())).append('\t')
.append(mapDistribution(o.getDistributionMark())).append('\t')
.append(nvl(o.getStatus())).append("\n");
}
return sb.toString();
}
// 单… 的解析/入库在 jd 工程内较复杂,这里仅返回模板校验结果
if (input.startsWith("")) {
return "录单模板校验暂未开放(仅支持 慢搜/慢查、录单/慢单 查询)";
}
return helpText();
}
private String handleTransferLike(String input) {
// 前端仍复用已有 /jarvis/jdorder/generatePromotionContent 接口,这里仅提供一个回显占位
return "指令入口:请在\"一键转链\"页面使用(输入文案/链接后点生成)";
}
// ===== 统计与列表渲染 =====
private String statsText(List<OrderRows> rows, String title) {
long total = rows.size();
long paid = rows.stream().filter(o -> Integer.valueOf(16).equals(o.getValidCode())).count();
long pending = rows.stream().filter(o -> Integer.valueOf(15).equals(o.getValidCode())).count();
long completed = rows.stream().filter(o -> Integer.valueOf(17).equals(o.getValidCode())).count();
long canceled = rows.stream().filter(o -> o.getValidCode()!=null && o.getValidCode()!=16 && o.getValidCode()!=17).count();
long violation = rows.stream().filter(o -> o.getValidCode()!=null && (o.getValidCode()==13 || o.getValidCode()==25 || o.getValidCode()==26 || o.getValidCode()==27 || o.getValidCode()==28 || o.getValidCode()==29)).count();
double paidCommission = rows.stream().filter(o -> Integer.valueOf(16).equals(o.getValidCode()) && o.getEstimateFee()!=null).mapToDouble(OrderRows::getEstimateFee).sum();
double pendingCommission = rows.stream().filter(o -> Integer.valueOf(15).equals(o.getValidCode()) && o.getEstimateFee()!=null).mapToDouble(OrderRows::getEstimateFee).sum();
double completedCommission = rows.stream().filter(o -> Integer.valueOf(17).equals(o.getValidCode()) && o.getEstimateFee()!=null).mapToDouble(OrderRows::getEstimateFee).sum();
double violationCommission = rows.stream().filter(o -> o.getValidCode()!=null && (o.getValidCode()==13 || o.getValidCode()==25 || o.getValidCode()==26 || o.getValidCode()==27 || o.getValidCode()==28 || o.getValidCode()==29) && o.getEstimateCosPrice()!=null && o.getCommissionRate()!=null).mapToDouble(o -> o.getEstimateCosPrice()*o.getCommissionRate()*0.01).sum();
StringBuilder sb = new StringBuilder();
sb.append("* ").append(title).append(" *\n")
.append("━━━━━━━━━━━━\n")
.append("订单总数:").append(total).append("\n")
.append("有效订单:").append(total - canceled).append("\n")
.append("已取消:").append(canceled).append("\n")
.append("────────────\n")
.append("已付款:").append(paid).append("\n")
.append("已付款佣金:").append(fmt(paidCommission)).append("\n")
.append("────────────\n")
.append("待付款:").append(pending).append("\n")
.append("待付款佣金:").append(fmt(pendingCommission)).append("\n")
.append("────────────\n")
.append("已完成:").append(completed).append("\n")
.append("已完成佣金:").append(fmt(completedCommission)).append("\n")
.append("────────────\n")
.append("违规订单:").append(violation).append("\n")
.append("违规佣金:").append(fmt(violationCommission)).append("\n")
.append("━━━━━━━━━━━━");
return sb.toString();
}
private String listOrders(List<OrderRows> rows, String title) {
if (rows.isEmpty()) return title + ":无";
StringBuilder sb = new StringBuilder();
sb.append(title).append("").append(rows.size()).append("条)\n");
int i = 0;
for (OrderRows r : rows) {
i++;
sb.append(i).append(". ").append(Optional.ofNullable(r.getSkuName()).orElse("-"))
.append(" | 订单号:").append(Optional.ofNullable(r.getOrderId()).map(String::valueOf).orElse("-"))
.append(" | 时间:").append(fmt(r.getOrderTime()))
.append(" | 有效:").append(r.getValidCode()==null?"-":r.getValidCode()).append("\n");
if (i>=100) break; // 最多100条
}
return sb.toString();
}
// ===== 过滤器 =====
private List<OrderRows> filterByDays(List<OrderRows> list, int daysBack) {
LocalDate now = LocalDate.now();
return list.stream().filter(o -> {
Date d = o.getOrderTime();
if (d == null) return false;
LocalDate od = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
if (daysBack == 0) return od.equals(now);
return !od.isBefore(now.minusDays(daysBack)) && !od.isAfter(now);
}).collect(Collectors.toList());
}
private List<OrderRows> filterByRange(List<OrderRows> list, int days) {
return filterByDays(list, days);
}
private List<OrderRows> filterYesterday(List<OrderRows> list) {
LocalDate y = LocalDate.now().minusDays(1);
return list.stream().filter(o -> {
Date d = o.getOrderTime(); if (d==null) return false;
LocalDate od = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return od.equals(y);
}).collect(Collectors.toList());
}
private List<OrderRows> filterThisMonth(List<OrderRows> list) {
LocalDate now = LocalDate.now();
return list.stream().filter(o -> {
Date d = o.getOrderTime(); if (d==null) return false;
LocalDate od = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return od.getYear()==now.getYear() && od.getMonth()==now.getMonth();
}).collect(Collectors.toList());
}
private List<OrderRows> filterLastMonth(List<OrderRows> list) {
LocalDate now = LocalDate.now().minusMonths(1);
return list.stream().filter(o -> {
Date d = o.getOrderTime(); if (d==null) return false;
LocalDate od = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return od.getYear()==now.getYear() && od.getMonth()==now.getMonth();
}).collect(Collectors.toList());
}
// ===== 工具 =====
private String jingMenu() {
StringBuilder content = new StringBuilder();
content.append("菜单:京+命令,如: 京今日统计\r\n")
.append("今日统计\r\n")
.append("昨日统计\r\n")
.append("七日统计\r\n")
.append("一个月统计\r\n")
.append("两个月统计\r\n")
.append("三个月统计\r\n")
.append("总统计\r\n")
.append("这个月统计\r\n")
.append("上个月统计\r\n")
.append("\r\n")
.append("今日订单\r\n")
.append("昨日订单\r\n")
.append("七日订单\r\n");
return content.toString();
}
private LocalDateTime effectiveToday() {
LocalDateTime now = LocalDateTime.now();
if (now.getHour() < 8) return now.minusDays(1);
return now;
}
private List<LocalDate> parseDateRange(String s) {
if (s==null || s.isEmpty()) return null;
if ("昨日".equals(s)) {
LocalDate y = LocalDate.now().minusDays(1);
return Arrays.asList(y, y);
}
if ("三日".equals(s)) {
LocalDate end = LocalDate.now().minusDays(1);
LocalDate start = LocalDate.now().minusDays(3);
return Arrays.asList(start, end);
}
if ("七日".equals(s)) {
LocalDate end = LocalDate.now().minusDays(1);
LocalDate start = LocalDate.now().minusDays(7);
return Arrays.asList(start, end);
}
Pattern p = Pattern.compile("(\\d{8})(?:-(\\d{8}))?");
Matcher m = p.matcher(s);
if (m.find()) {
LocalDate d1 = parseYmd(m.group(1));
LocalDate d2 = m.group(2)!=null ? parseYmd(m.group(2)) : d1;
return Arrays.asList(d1, d2);
}
return null;
}
private LocalDate parseYmd(String ymd) {
int y = Integer.parseInt(ymd.substring(0,4));
int mo = Integer.parseInt(ymd.substring(4,6));
int d = Integer.parseInt(ymd.substring(6,8));
return LocalDate.of(y, mo, d);
}
private String mapDistribution(String v) {
if (v == null) return "";
if (v.startsWith("H")) return "鸿";
if (v.startsWith("F")) return "";
return "";
}
private boolean contains(Object field, String kwLower) {
if (field == null) return false;
return String.valueOf(field).toLowerCase(Locale.ROOT).contains(kwLower);
}
private String nvl(Object v) {
return v == null ? "" : String.valueOf(v);
}
private String fmt(Object v) {
if (v == null) return "";
if (v instanceof Number) {
return String.format(Locale.ROOT, "%.2f", ((Number) v).doubleValue());
}
if (v instanceof Date) {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format((Date) v);
}
return String.valueOf(v);
}
}