Compare commits
94 Commits
9bb7cfc7fb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e78cc67476 | ||
|
|
6fb46cc203 | ||
|
|
7582868b2c | ||
|
|
9d03cca517 | ||
|
|
8b5abb44ee | ||
|
|
e75f71d37b | ||
|
|
5da74a155c | ||
|
|
a88600788a | ||
|
|
cf8008bdc1 | ||
|
|
d97a977a0e | ||
|
|
1f25cc5d15 | ||
|
|
66aa339906 | ||
|
|
acd693f122 | ||
|
|
751844493b | ||
|
|
256b54ffab | ||
|
|
0ff357148b | ||
|
|
7cd7440f1f | ||
|
|
f5f14c730f | ||
|
|
a7068053e1 | ||
|
|
babe687679 | ||
|
|
de335831d4 | ||
|
|
a10d561fcb | ||
|
|
656f3d28a9 | ||
|
|
e420aaeb9e | ||
|
|
01bea5005e | ||
|
|
c825c6b81a | ||
|
|
1446ea2432 | ||
|
|
52b8f13b2d | ||
|
|
94f319514e | ||
|
|
5205d8c155 | ||
|
|
fed0158444 | ||
|
|
24cf538475 | ||
|
|
c50975bce5 | ||
|
|
042068ccf1 | ||
|
|
52d0adfc85 | ||
|
|
ede30b5f36 | ||
|
|
ce3af838bd | ||
|
|
0205fe2c09 | ||
|
|
6f482256c5 | ||
|
|
31e7e6853b | ||
|
|
a2c4589046 | ||
|
|
16bcd45c63 | ||
|
|
e94f17973c | ||
|
|
c9876df3de | ||
|
|
2d4f933791 | ||
|
|
a22d17de73 | ||
|
|
9af1a369f7 | ||
|
|
49c855ff78 | ||
|
|
0838d16652 | ||
|
|
6a43af0a34 | ||
|
|
1b4a73cd25 | ||
|
|
f02de5950e | ||
|
|
2b9cd6dd2e | ||
|
|
d0d51df465 | ||
|
|
9e0c6d88b1 | ||
|
|
8a77598c88 | ||
|
|
43e44c8f7f | ||
|
|
c3cafdcbe0 | ||
|
|
6fbfecf690 | ||
|
|
bce83f680c | ||
|
|
74e7990947 | ||
|
|
c31a34f519 | ||
|
|
ca452882b4 | ||
|
|
07c9aac9e6 | ||
|
|
3e37587074 | ||
|
|
fa7e26cf6e | ||
|
|
d4fdf076e9 | ||
|
|
9c503464c1 | ||
|
|
6b88e5376e | ||
|
|
2e5540904f | ||
|
|
c841990b49 | ||
|
|
72d5856838 | ||
|
|
d361e93895 | ||
|
|
9b45142cca | ||
|
|
72008d7de1 | ||
|
|
94b65fb760 | ||
|
|
2fb9777342 | ||
|
|
75d7c8e6de | ||
|
|
9ae74c999e | ||
|
|
921c8a2374 | ||
|
|
6a88a68320 | ||
|
|
a515ec33fb | ||
|
|
f2f6d02b2f | ||
|
|
9f3fb23a91 | ||
|
|
312a068bd3 | ||
|
|
3d5ee6e624 | ||
|
|
066ab35a17 | ||
|
|
318cef274e | ||
|
|
175cd3ba01 | ||
|
|
6ecedf91b3 | ||
|
|
b37865a676 | ||
|
|
918f737c94 | ||
|
|
ef286d3bd2 | ||
|
|
184a53005d |
7
pom.xml
7
pom.xml
@@ -35,6 +35,7 @@
|
|||||||
<logback.version>1.2.13</logback.version>
|
<logback.version>1.2.13</logback.version>
|
||||||
<spring-security.version>5.7.12</spring-security.version>
|
<spring-security.version>5.7.12</spring-security.version>
|
||||||
<spring-framework.version>5.3.39</spring-framework.version>
|
<spring-framework.version>5.3.39</spring-framework.version>
|
||||||
|
<rocketmq-spring.version>2.2.3</rocketmq-spring.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<!-- 依赖声明 -->
|
<!-- 依赖声明 -->
|
||||||
@@ -218,6 +219,12 @@
|
|||||||
<version>${ruoyi.version}</version>
|
<version>${ruoyi.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||||
|
<version>${rocketmq-spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
*/
|
*/
|
||||||
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
|
@EnableAsync
|
||||||
public class RuoYiApplication
|
public class RuoYiApplication
|
||||||
{
|
{
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.ruoyi.jarvis.wecom;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 wxSend 的企微应用文本主动推送(POST /wecom/active-push)。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class WxSendWeComPushClient {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WxSendWeComPushClient.class);
|
||||||
|
|
||||||
|
public static final String HEADER_PUSH_SECRET = "X-WxSend-WeCom-Push-Secret";
|
||||||
|
|
||||||
|
@Value("${jarvis.wecom.wxsend-base-url:}")
|
||||||
|
private String wxsendBaseUrl;
|
||||||
|
|
||||||
|
@Value("${jarvis.wecom.push-secret:}")
|
||||||
|
private String pushSecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在被动回复返回后延迟再发,保证企微侧先出现首条被动消息。
|
||||||
|
*/
|
||||||
|
public void scheduleActivePushes(String toUser, List<String> contents) {
|
||||||
|
if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(pushSecret)
|
||||||
|
|| !StringUtils.hasText(toUser) || contents == null || contents.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String userId = toUser.trim();
|
||||||
|
final List<String> list = new ArrayList<>(contents);
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(450);
|
||||||
|
String base = normalizeBase(wxsendBaseUrl);
|
||||||
|
String url = base + "/wecom/active-push";
|
||||||
|
for (String c : list) {
|
||||||
|
if (!StringUtils.hasText(c)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
postJson(url, userId, c.trim());
|
||||||
|
Thread.sleep(120);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("企微主动推送任务异常 userId={} msg={}", userId, e.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeBase(String base) {
|
||||||
|
String b = base.trim();
|
||||||
|
if (b.endsWith("/")) {
|
||||||
|
return b.substring(0, b.length() - 1);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postJson(String url, String toUser, String content) {
|
||||||
|
JSONObject body = new JSONObject();
|
||||||
|
body.put("toUser", toUser);
|
||||||
|
body.put("content", content);
|
||||||
|
byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
conn = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(60000);
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
conn.setRequestProperty(HEADER_PUSH_SECRET, pushSecret);
|
||||||
|
try (OutputStream os = conn.getOutputStream()) {
|
||||||
|
os.write(bytes);
|
||||||
|
}
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
|
||||||
|
String resp = readAll(is);
|
||||||
|
if (code < 200 || code >= 300) {
|
||||||
|
log.warn("wxSend active-push HTTP {} body={}", code, resp);
|
||||||
|
} else {
|
||||||
|
log.debug("wxSend active-push OK http={} resp={}", code, resp);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("wxSend active-push 请求失败 url={} err={}", url, e.toString());
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readAll(InputStream is) throws java.io.IOException {
|
||||||
|
if (is == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
byte[] buf = new byte[4096];
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int n;
|
||||||
|
while ((n = is.read(buf)) >= 0) {
|
||||||
|
sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +1,129 @@
|
|||||||
package com.ruoyi.web.controller.common;
|
package com.ruoyi.web.controller.common;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.core.domain.R;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.erp.request.IERPAccount;
|
||||||
|
import com.ruoyi.jarvis.service.IErpGoofishOrderService;
|
||||||
|
import com.ruoyi.jarvis.service.erp.ErpAccountResolver;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开放平台回调接收端
|
* 闲管家开放平台推送回调(请在开放平台填写真实 URL)
|
||||||
* 注意:/product/receive 与 /order/receive 为示例路径,请在开放平台配置时使用你自己的正式回调地址
|
* 订单:POST .../open/callback/order/receive?appid=×tamp=&sign=
|
||||||
|
* <p>
|
||||||
|
* 成功/失败体须与《订单推送通知》OpenAPI 一致:{@code result=success|fail} + {@code msg};
|
||||||
|
* 仅当 {@code result} 为 success 时平台认为接收成功(失败最多重试 3 次;建议业务异步处理、快速返回)。
|
||||||
*/
|
*/
|
||||||
|
@Anonymous
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/open/callback")
|
@RequestMapping("/open/callback")
|
||||||
public class OpenCallbackController extends BaseController {
|
public class OpenCallbackController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ErpAccountResolver erpAccountResolver;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpGoofishOrderService erpGoofishOrderService;
|
||||||
|
|
||||||
@PostMapping("/product/receive")
|
@PostMapping("/product/receive")
|
||||||
public JSONObject receiveProductCallback(
|
public JSONObject receiveProductCallback(
|
||||||
@RequestParam("appid") String appid,
|
@RequestParam("appid") String appid,
|
||||||
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
||||||
|
@RequestParam(value = "seller_id", required = false) Long sellerId,
|
||||||
@RequestParam("sign") String sign,
|
@RequestParam("sign") String sign,
|
||||||
@RequestBody JSONObject body
|
@RequestBody(required = false) String rawBody
|
||||||
) {
|
) {
|
||||||
if (!verifySign(appid, timestamp, sign, body)) {
|
String normalizedBody = normalizeJsonBody(rawBody);
|
||||||
JSONObject fail = new JSONObject();
|
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
||||||
fail.put("result", "fail");
|
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
|
||||||
fail.put("msg", "签名失败");
|
return failCallback("签名失败");
|
||||||
return fail;
|
|
||||||
}
|
}
|
||||||
JSONObject ok = new JSONObject();
|
return successCallback();
|
||||||
ok.put("result", "success");
|
|
||||||
ok.put("msg", "接收成功");
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/order/receive")
|
@PostMapping("/order/receive")
|
||||||
public JSONObject receiveOrderCallback(
|
public JSONObject receiveOrderCallback(
|
||||||
@RequestParam("appid") String appid,
|
@RequestParam("appid") String appid,
|
||||||
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
||||||
|
@RequestParam(value = "seller_id", required = false) Long sellerId,
|
||||||
@RequestParam("sign") String sign,
|
@RequestParam("sign") String sign,
|
||||||
@RequestBody JSONObject body
|
@RequestBody(required = false) String rawBody
|
||||||
) {
|
) {
|
||||||
if (!verifySign(appid, timestamp, sign, body)) {
|
String normalizedBody = normalizeJsonBody(rawBody);
|
||||||
JSONObject fail = new JSONObject();
|
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
||||||
fail.put("result", "fail");
|
if (account == null) {
|
||||||
fail.put("msg", "签名失败");
|
return failCallback("未找到启用的 AppKey 配置");
|
||||||
return fail;
|
|
||||||
}
|
}
|
||||||
|
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
|
||||||
|
return failCallback("签名失败");
|
||||||
|
}
|
||||||
|
JSONObject body;
|
||||||
|
try {
|
||||||
|
body = "{}".equals(normalizedBody) ? new JSONObject() : JSON.parseObject(normalizedBody);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return failCallback("请求体不是合法JSON");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
erpGoofishOrderService.publishOrProcessNotify(appid, timestamp, body);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return failCallback("入队异常");
|
||||||
|
}
|
||||||
|
return successCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与开放平台示例一致:签名字符串 = md5(appKey + "," + md5(body原文) + "," + timestamp + [,sellerId] + "," + appSecret)
|
||||||
|
* body 须与推送原文完全一致后做 MD5,不能用解析后再 toJSONString(字段顺序变化会导致验签失败)。
|
||||||
|
*/
|
||||||
|
private boolean verifyGoofishSign(IERPAccount account, Long timestamp, Long sellerId, String sign, String bodyExactForMd5) {
|
||||||
|
if (account == null || StringUtils.isEmpty(sign)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String jsonForMd5 = bodyExactForMd5 == null || bodyExactForMd5.isEmpty() ? "{}" : bodyExactForMd5;
|
||||||
|
String bodyMd5 = md5Hex(jsonForMd5);
|
||||||
|
long ts = timestamp == null ? 0L : timestamp;
|
||||||
|
String data;
|
||||||
|
if (sellerId != null) {
|
||||||
|
data = account.getApiKey() + "," + bodyMd5 + "," + ts + "," + sellerId + "," + account.getApiKeySecret();
|
||||||
|
} else {
|
||||||
|
data = account.getApiKey() + "," + bodyMd5 + "," + ts + "," + account.getApiKeySecret();
|
||||||
|
}
|
||||||
|
String local = md5Hex(data);
|
||||||
|
return StringUtils.equalsIgnoreCase(local, sign.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeJsonBody(String rawBody) {
|
||||||
|
if (rawBody == null) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
String t = rawBody.trim();
|
||||||
|
return t.isEmpty() ? "{}" : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 与《订单推送通知》notify_resp_ok 一致:result=success 时平台才停止重试 */
|
||||||
|
private static JSONObject successCallback() {
|
||||||
JSONObject ok = new JSONObject();
|
JSONObject ok = new JSONObject();
|
||||||
ok.put("result", "success");
|
ok.put("result", "success");
|
||||||
ok.put("msg", "接收成功");
|
ok.put("msg", "接收成功");
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifySign(String appid, Long timestamp, String sign, JSONObject body) {
|
/** 与 notify_resp_fail 一致:result=fail */
|
||||||
// TODO: 这里需要根据appid查出对应的 appKey/appSecret
|
private static JSONObject failCallback(String msg) {
|
||||||
// 为了示例,直接使用 ERPAccount.ACCOUNT_HUGE 的常量。生产请替换为从数据库/配置读取
|
JSONObject j = new JSONObject();
|
||||||
String appKey = "1016208368633221";
|
j.put("result", "fail");
|
||||||
String appSecret = "waLiRMgFcixLbcLjUSSwo370Hp1nBcBu";
|
j.put("msg", msg == null || msg.isEmpty() ? "处理失败" : msg);
|
||||||
|
return j;
|
||||||
String json = body == null ? "{}" : body.toJSONString();
|
|
||||||
String data = appKey + "," + md5(json) + "," + (timestamp == null ? 0 : timestamp) + "," + appSecret;
|
|
||||||
String local = md5(data);
|
|
||||||
return StringUtils.equalsIgnoreCase(local, sign);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String md5(String str) {
|
private String md5Hex(String str) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
|
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||||
@@ -82,5 +137,3 @@ public class OpenCallbackController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import com.ruoyi.erp.request.ERPAccount;
|
import com.ruoyi.erp.request.ERPAccount;
|
||||||
|
import com.ruoyi.erp.request.IERPAccount;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpOpenConfig;
|
||||||
|
import com.ruoyi.jarvis.service.IErpOpenConfigService;
|
||||||
|
import com.ruoyi.jarvis.service.erp.ErpAccountResolver;
|
||||||
import com.ruoyi.erp.request.ProductCreateRequest;
|
import com.ruoyi.erp.request.ProductCreateRequest;
|
||||||
import com.ruoyi.erp.request.ProductCategoryListQueryRequest;
|
import com.ruoyi.erp.request.ProductCategoryListQueryRequest;
|
||||||
import com.ruoyi.erp.request.ProductPropertyListQueryRequest;
|
import com.ruoyi.erp.request.ProductPropertyListQueryRequest;
|
||||||
@@ -20,6 +24,7 @@ import com.ruoyi.erp.request.ProductDownShelfRequest;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import javax.validation.constraints.*;
|
import javax.validation.constraints.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -37,12 +42,18 @@ public class ProductController extends BaseController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IOuterIdGeneratorService outerIdGeneratorService;
|
private IOuterIdGeneratorService outerIdGeneratorService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ErpAccountResolver erpAccountResolver;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpOpenConfigService erpOpenConfigService;
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
|
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
|
||||||
|
|
||||||
@PostMapping("/createByPromotion")
|
@PostMapping("/createByPromotion")
|
||||||
public R<?> createByPromotion(@RequestBody @Validated CreateProductFromPromotionRequest req) {
|
public R<?> createByPromotion(@RequestBody @Validated CreateProductFromPromotionRequest req) {
|
||||||
try {
|
try {
|
||||||
ERPAccount account = resolveAccount(req.getAppid());
|
IERPAccount account = resolveAccount(req.getAppid());
|
||||||
// 1) 组装 ERPShop
|
// 1) 组装 ERPShop
|
||||||
ERPShop erpShop = new ERPShop();
|
ERPShop erpShop = new ERPShop();
|
||||||
erpShop.setChannelCatid(req.getChannelCatId());
|
erpShop.setChannelCatid(req.getChannelCatId());
|
||||||
@@ -143,7 +154,7 @@ public class ProductController extends BaseController {
|
|||||||
@PostMapping("/publish")
|
@PostMapping("/publish")
|
||||||
public R<?> publish(@RequestBody @Validated PublishRequest req) {
|
public R<?> publish(@RequestBody @Validated PublishRequest req) {
|
||||||
try {
|
try {
|
||||||
ERPAccount account = resolveAccount(req.getAppid());
|
IERPAccount account = resolveAccount(req.getAppid());
|
||||||
ProductPublishRequest publishRequest = new ProductPublishRequest(account);
|
ProductPublishRequest publishRequest = new ProductPublishRequest(account);
|
||||||
publishRequest.setProductId(req.getProductId());
|
publishRequest.setProductId(req.getProductId());
|
||||||
publishRequest.setUserName(req.getUserName());
|
publishRequest.setUserName(req.getUserName());
|
||||||
@@ -344,10 +355,22 @@ public class ProductController extends BaseController {
|
|||||||
String name = firstNonBlank(row.getString("user_name"), row.getString("xy_name"), row.getString("username"), row.getString("nick"));
|
String name = firstNonBlank(row.getString("user_name"), row.getString("xy_name"), row.getString("username"), row.getString("nick"));
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
String label = name;
|
String label = name;
|
||||||
for (ERPAccount a : ERPAccount.values()) {
|
List<ErpOpenConfig> cfgs = erpOpenConfigService.selectEnabledOrderBySort();
|
||||||
if (name.equals(a.getXyName())) {
|
if (cfgs != null) {
|
||||||
label = name + "(" + a.getRemark() + ")";
|
for (ErpOpenConfig c : cfgs) {
|
||||||
break;
|
if (name.equals(c.getXyUserName())) {
|
||||||
|
String r = c.getRemark() != null ? c.getRemark() : c.getAppKey();
|
||||||
|
label = name + "(" + r + ")";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (label.equals(name)) {
|
||||||
|
for (ERPAccount a : ERPAccount.values()) {
|
||||||
|
if (name.equals(a.getXyName())) {
|
||||||
|
label = name + "(" + a.getRemark() + ")";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.add(new Option(name, label));
|
options.add(new Option(name, label));
|
||||||
@@ -376,20 +399,24 @@ public class ProductController extends BaseController {
|
|||||||
@GetMapping("/ERPAccount")
|
@GetMapping("/ERPAccount")
|
||||||
public R<?> erpAccounts() {
|
public R<?> erpAccounts() {
|
||||||
java.util.List<Option> list = new java.util.ArrayList<>();
|
java.util.List<Option> list = new java.util.ArrayList<>();
|
||||||
|
List<ErpOpenConfig> cfgs = erpOpenConfigService.selectEnabledOrderBySort();
|
||||||
|
if (cfgs != null) {
|
||||||
|
for (ErpOpenConfig c : cfgs) {
|
||||||
|
String label = StringUtils.isNotEmpty(c.getRemark()) ? c.getRemark() : c.getXyUserName();
|
||||||
|
if (StringUtils.isEmpty(label)) {
|
||||||
|
label = c.getAppKey();
|
||||||
|
}
|
||||||
|
list.add(new Option(c.getAppKey(), "【配置】" + label));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (ERPAccount a : ERPAccount.values()) {
|
for (ERPAccount a : ERPAccount.values()) {
|
||||||
// 仅显示备注作为 label,value 仍为 appid
|
list.add(new Option(a.getApiKey(), "【内置】" + a.getRemark()));
|
||||||
list.add(new Option(a.getApiKey(), a.getRemark()));
|
|
||||||
}
|
}
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ERPAccount resolveAccount(String appid) {
|
private IERPAccount resolveAccount(String appid) {
|
||||||
if (appid != null && !appid.isEmpty()) {
|
return erpAccountResolver.resolve(appid);
|
||||||
for (ERPAccount a : ERPAccount.values()) {
|
|
||||||
if (a.getApiKey().equals(appid)) return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ERPAccount.ACCOUNT_HUGE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -558,7 +585,7 @@ public class ProductController extends BaseController {
|
|||||||
@PostMapping("/downShelf")
|
@PostMapping("/downShelf")
|
||||||
public R<?> downShelf(@RequestBody @Validated DownShelfRequest req) {
|
public R<?> downShelf(@RequestBody @Validated DownShelfRequest req) {
|
||||||
try {
|
try {
|
||||||
ERPAccount account = resolveAccount(req.getAppid());
|
IERPAccount account = resolveAccount(req.getAppid());
|
||||||
ProductDownShelfRequest downShelfRequest = new ProductDownShelfRequest(account);
|
ProductDownShelfRequest downShelfRequest = new ProductDownShelfRequest(account);
|
||||||
downShelfRequest.setProductId(req.getProductId());
|
downShelfRequest.setProductId(req.getProductId());
|
||||||
String resp = downShelfRequest.getResponseBody();
|
String resp = downShelfRequest.getResponseBody();
|
||||||
@@ -576,7 +603,7 @@ public class ProductController extends BaseController {
|
|||||||
@PostMapping("/batchPublish")
|
@PostMapping("/batchPublish")
|
||||||
public R<?> batchPublish(@RequestBody @Validated BatchPublishRequest req) {
|
public R<?> batchPublish(@RequestBody @Validated BatchPublishRequest req) {
|
||||||
try {
|
try {
|
||||||
ERPAccount account = resolveAccount(req.getAppid());
|
IERPAccount account = resolveAccount(req.getAppid());
|
||||||
List<Long> productIds = req.getProductIds();
|
List<Long> productIds = req.getProductIds();
|
||||||
|
|
||||||
if (productIds == null || productIds.isEmpty()) {
|
if (productIds == null || productIds.isEmpty()) {
|
||||||
@@ -651,7 +678,7 @@ public class ProductController extends BaseController {
|
|||||||
@PostMapping("/batchDownShelf")
|
@PostMapping("/batchDownShelf")
|
||||||
public R<?> batchDownShelf(@RequestBody @Validated BatchDownShelfRequest req) {
|
public R<?> batchDownShelf(@RequestBody @Validated BatchDownShelfRequest req) {
|
||||||
try {
|
try {
|
||||||
ERPAccount account = resolveAccount(req.getAppid());
|
IERPAccount account = resolveAccount(req.getAppid());
|
||||||
List<Long> productIds = req.getProductIds();
|
List<Long> productIds = req.getProductIds();
|
||||||
|
|
||||||
if (productIds == null || productIds.isEmpty()) {
|
if (productIds == null || productIds.isEmpty()) {
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.jarvis.config.JarvisGoofishProperties;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery;
|
||||||
|
import com.ruoyi.jarvis.service.IErpGoofishOrderService;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/erpGoofishOrder")
|
||||||
|
public class ErpGoofishOrderController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpGoofishOrderService erpGoofishOrderService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JarvisGoofishProperties goofishProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单变更日志全表检索(跨订单排查),须配置在 /{id} 之前避免歧义;对应日志标记 [goofish-order-event]
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:list')")
|
||||||
|
@GetMapping("/eventLog/list")
|
||||||
|
public TableDataInfo eventLogList(ErpGoofishOrderEventLogQuery query) {
|
||||||
|
startPage();
|
||||||
|
List<ErpGoofishOrderEventLog> list = erpGoofishOrderService.selectEventLogList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(ErpGoofishOrder query) {
|
||||||
|
startPage();
|
||||||
|
List<ErpGoofishOrder> list = erpGoofishOrderService.selectList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:query')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable Long id) {
|
||||||
|
return AjaxResult.success(erpGoofishOrderService.selectById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:query')")
|
||||||
|
@GetMapping("/{id}/eventLogs")
|
||||||
|
public AjaxResult eventLogs(@PathVariable Long id) {
|
||||||
|
List<ErpGoofishOrderEventLog> list = erpGoofishOrderService.listEventLogsByOrderId(id);
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家拉单", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/pull/{appKey}")
|
||||||
|
public AjaxResult pullOne(@PathVariable String appKey, @RequestParam(value = "hours", required = false) Integer hours) {
|
||||||
|
int h = hours == null ? goofishProperties.getPullLookbackHours() : hours;
|
||||||
|
int n = erpGoofishOrderService.pullOrdersForAppKey(appKey, h);
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
data.put("processedItems", n);
|
||||||
|
data.put("lookbackHours", h);
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家全量拉单", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/pullAll")
|
||||||
|
public AjaxResult pullAll(@RequestParam(value = "hours", required = false) Integer hours) {
|
||||||
|
int h = hours == null ? goofishProperties.getPullLookbackHours() : hours;
|
||||||
|
int n = erpGoofishOrderService.pullAllEnabled(h);
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
data.put("processedItems", n);
|
||||||
|
data.put("lookbackHours", h);
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家历史全量拉单", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/pull/{appKey}/full")
|
||||||
|
public AjaxResult pullOneFull(@PathVariable String appKey) {
|
||||||
|
int n = erpGoofishOrderService.pullOrdersForAppKeyFullHistory(appKey);
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
data.put("processedItems", n);
|
||||||
|
data.put("pullFullHistoryDays", goofishProperties.getPullFullHistoryDays());
|
||||||
|
data.put("pullTimeChunkSeconds", goofishProperties.getPullTimeChunkSeconds());
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家全账号历史全量拉单", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/pullAll/full")
|
||||||
|
public AjaxResult pullAllFull() {
|
||||||
|
int n = erpGoofishOrderService.pullAllEnabledFullHistory();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
data.put("processedItems", n);
|
||||||
|
data.put("pullFullHistoryDays", goofishProperties.getPullFullHistoryDays());
|
||||||
|
data.put("pullTimeChunkSeconds", goofishProperties.getPullTimeChunkSeconds());
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家订单详情刷新", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/refreshDetail/{id}")
|
||||||
|
public AjaxResult refreshDetail(@PathVariable Long id) {
|
||||||
|
erpGoofishOrderService.refreshDetail(id);
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:edit')")
|
||||||
|
@Log(title = "闲管家重试发货", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/retryShip/{id}")
|
||||||
|
public AjaxResult retryShip(@PathVariable Long id) {
|
||||||
|
erpGoofishOrderService.retryShip(id);
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.erp.request.ExpressCompaniesQueryRequest;
|
||||||
|
import com.ruoyi.erp.request.IERPAccount;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpOpenConfig;
|
||||||
|
import com.ruoyi.jarvis.service.IErpOpenConfigService;
|
||||||
|
import com.ruoyi.jarvis.service.erp.ErpAccountResolver;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/erpOpenConfig")
|
||||||
|
public class ErpOpenConfigController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpOpenConfigService erpOpenConfigService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ErpAccountResolver erpAccountResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询闲管家快递公司列表(POST body 固定为 {},与签名规则一致)
|
||||||
|
*
|
||||||
|
* @param appKey 与配置中心一致;不传则按 {@link ErpAccountResolver#resolve(String)} 默认账号
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:list')")
|
||||||
|
@GetMapping("/expressCompanies")
|
||||||
|
public AjaxResult expressCompanies(@RequestParam(required = false) String appKey) {
|
||||||
|
try {
|
||||||
|
IERPAccount cred = erpAccountResolver.resolve(appKey);
|
||||||
|
ExpressCompaniesQueryRequest req = new ExpressCompaniesQueryRequest(cred);
|
||||||
|
String resp = req.getResponseBody();
|
||||||
|
JSONObject o = JSONObject.parseObject(resp);
|
||||||
|
if (o == null) {
|
||||||
|
return AjaxResult.error("开放平台返回空");
|
||||||
|
}
|
||||||
|
if (o.getIntValue("code") != 0) {
|
||||||
|
return AjaxResult.error(o.getString("msg") != null ? o.getString("msg") : resp);
|
||||||
|
}
|
||||||
|
return AjaxResult.success(o.get("data"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("调用快递公司接口失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(ErpOpenConfig query) {
|
||||||
|
startPage();
|
||||||
|
List<ErpOpenConfig> list = erpOpenConfigService.selectList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:query')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable Long id) {
|
||||||
|
return AjaxResult.success(erpOpenConfigService.selectById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:add')")
|
||||||
|
@Log(title = "闲管家应用配置", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult add(@RequestBody ErpOpenConfig row) {
|
||||||
|
return toAjax(erpOpenConfigService.insert(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:edit')")
|
||||||
|
@Log(title = "闲管家应用配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping
|
||||||
|
public AjaxResult edit(@RequestBody ErpOpenConfig row) {
|
||||||
|
return toAjax(erpOpenConfigService.update(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:erpOpenConfig:remove')")
|
||||||
|
@Log(title = "闲管家应用配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
|
int n = 0;
|
||||||
|
if (ids != null) {
|
||||||
|
for (Long id : ids) {
|
||||||
|
n += erpOpenConfigService.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toAjax(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.web.controller.jarvis;
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -17,7 +18,9 @@ import com.ruoyi.common.core.controller.BaseController;
|
|||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.jarvis.domain.ErpProduct;
|
import com.ruoyi.jarvis.domain.ErpProduct;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpProductExportRow;
|
||||||
import com.ruoyi.jarvis.service.IErpProductService;
|
import com.ruoyi.jarvis.service.IErpProductService;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
|
||||||
@@ -51,12 +54,14 @@ public class ErpProductController extends BaseController
|
|||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:export')")
|
@PreAuthorize("@ss.hasPermi('jarvis:erpProduct:export')")
|
||||||
@Log(title = "闲鱼商品", businessType = BusinessType.EXPORT)
|
@Log(title = "闲鱼商品", businessType = BusinessType.EXPORT)
|
||||||
@GetMapping("/export")
|
@PostMapping("/export")
|
||||||
public AjaxResult export(ErpProduct erpProduct)
|
public void export(HttpServletResponse response, ErpProduct erpProduct)
|
||||||
{
|
{
|
||||||
List<ErpProduct> list = erpProductService.selectErpProductList(erpProduct);
|
List<ErpProduct> list = erpProductService.selectErpProductList(erpProduct);
|
||||||
ExcelUtil<ErpProduct> util = new ExcelUtil<ErpProduct>(ErpProduct.class);
|
String batchAt = DateUtils.getTime();
|
||||||
return util.exportExcel(list, "闲鱼商品数据");
|
List<ErpProductExportRow> rows = ErpProductExportRow.fromList(list, batchAt);
|
||||||
|
ExcelUtil<ErpProductExportRow> util = new ExcelUtil<ErpProductExportRow>(ErpProductExportRow.class);
|
||||||
|
util.exportExcel(response, rows, "闲鱼商品_AI明细");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ public class InstructionController extends BaseController {
|
|||||||
public AjaxResult execute(@RequestBody Map<String, Object> body) {
|
public AjaxResult execute(@RequestBody Map<String, Object> body) {
|
||||||
String cmd = body != null ? (body.get("command") != null ? String.valueOf(body.get("command")) : null) : null;
|
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")));
|
boolean forceGenerate = body != null && body.get("forceGenerate") != null && Boolean.parseBoolean(String.valueOf(body.get("forceGenerate")));
|
||||||
// 控制台入口,传递 isFromConsole=true,跳过订单查询校验
|
// 控制台入口:全量统计视角(排除后台标记不参与统计的联盟),非单个企微成员
|
||||||
java.util.List<String> result = instructionService.execute(cmd, forceGenerate, true);
|
java.util.List<String> result = instructionService.execute(cmd, forceGenerate, true, null);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.ruoyi.common.utils.http.HttpUtils;
|
|||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
import com.ruoyi.jarvis.domain.OrderRows;
|
import com.ruoyi.jarvis.domain.OrderRows;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
import com.ruoyi.jarvis.service.IOrderRowsService;
|
import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||||
import com.ruoyi.jarvis.service.IGiftCouponService;
|
import com.ruoyi.jarvis.service.IGiftCouponService;
|
||||||
import com.ruoyi.jarvis.domain.GiftCoupon;
|
import com.ruoyi.jarvis.domain.GiftCoupon;
|
||||||
@@ -41,6 +42,7 @@ public class JDOrderController extends BaseController {
|
|||||||
private final IOrderRowsService orderRowsService;
|
private final IOrderRowsService orderRowsService;
|
||||||
private final IGiftCouponService giftCouponService;
|
private final IGiftCouponService giftCouponService;
|
||||||
private final ISysConfigService sysConfigService;
|
private final ISysConfigService sysConfigService;
|
||||||
|
private final ILogisticsService logisticsService;
|
||||||
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
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(
|
private static final java.util.regex.Pattern URL_DETECT_PATTERN = java.util.regex.Pattern.compile(
|
||||||
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
||||||
@@ -53,11 +55,13 @@ public class JDOrderController extends BaseController {
|
|||||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
||||||
IGiftCouponService giftCouponService, ISysConfigService sysConfigService) {
|
IGiftCouponService giftCouponService, ISysConfigService sysConfigService,
|
||||||
|
ILogisticsService logisticsService) {
|
||||||
this.jdOrderService = jdOrderService;
|
this.jdOrderService = jdOrderService;
|
||||||
this.orderRowsService = orderRowsService;
|
this.orderRowsService = orderRowsService;
|
||||||
this.giftCouponService = giftCouponService;
|
this.giftCouponService = giftCouponService;
|
||||||
this.sysConfigService = sysConfigService;
|
this.sysConfigService = sysConfigService;
|
||||||
|
this.logisticsService = logisticsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static String skey = "2192057370ef8140c201079969c956a3";
|
private final static String skey = "2192057370ef8140c201079969c956a3";
|
||||||
@@ -68,12 +72,6 @@ public class JDOrderController extends BaseController {
|
|||||||
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
|
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
|
||||||
private String jdApiPath;
|
private String jdApiPath;
|
||||||
|
|
||||||
@Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}")
|
|
||||||
private String logisticsBaseUrl;
|
|
||||||
|
|
||||||
@Value("${jarvis.server.logistics.fetch-path:/fetch_logistics}")
|
|
||||||
private String logisticsFetchPath;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取JD接口请求URL
|
* 获取JD接口请求URL
|
||||||
*/
|
*/
|
||||||
@@ -947,9 +945,7 @@ public class JDOrderController extends BaseController {
|
|||||||
logger.info("手动获取物流信息 - 订单ID: {}, 订单号: {}, 分销标识: {}, 物流链接: {}",
|
logger.info("手动获取物流信息 - 订单ID: {}, 订单号: {}, 分销标识: {}, 物流链接: {}",
|
||||||
orderId, order.getOrderId(), distributionMark, logisticsLink);
|
orderId, order.getOrderId(), distributionMark, logisticsLink);
|
||||||
|
|
||||||
// 构建外部接口URL
|
String externalUrl = logisticsService.buildFetchLogisticsRequestUrl(logisticsLink);
|
||||||
String externalUrl = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=" +
|
|
||||||
java.net.URLEncoder.encode(logisticsLink, "UTF-8");
|
|
||||||
|
|
||||||
logger.info("准备调用外部接口 - URL: {}", externalUrl);
|
logger.info("准备调用外部接口 - URL: {}", externalUrl);
|
||||||
|
|
||||||
@@ -1057,22 +1053,30 @@ public class JDOrderController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建配置键名
|
String trimmed = distributionMark.trim();
|
||||||
String configKey = CONFIG_KEY_PREFIX + distributionMark.trim();
|
String configKey = CONFIG_KEY_PREFIX + trimmed;
|
||||||
|
|
||||||
// 从系统配置中获取接收人列表
|
|
||||||
String configValue = sysConfigService.selectConfigByKey(configKey);
|
String configValue = sysConfigService.selectConfigByKey(configKey);
|
||||||
|
|
||||||
if (StringUtils.hasText(configValue)) {
|
if (StringUtils.hasText(configValue)) {
|
||||||
// 清理配置值(去除空格)
|
|
||||||
String touser = configValue.trim().replaceAll(",\\s+", ",");
|
String touser = configValue.trim().replaceAll(",\\s+", ",");
|
||||||
logger.info("从配置获取接收人列表 - 分销标识: {}, 配置键: {}, 接收人: {}",
|
logger.info("从配置获取接收人列表 - 分销标识: {}, 配置键: {}, 接收人: {}",
|
||||||
distributionMark, configKey, touser);
|
distributionMark, configKey, touser);
|
||||||
return touser;
|
return touser;
|
||||||
} else {
|
|
||||||
logger.debug("未找到接收人配置 - 分销标识: {}, 配置键: {}", distributionMark, configKey);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
if (trimmed.startsWith("F-") || "F".equals(trimmed)) {
|
||||||
|
if (!trimmed.equals("F")) {
|
||||||
|
String fallbackKey = CONFIG_KEY_PREFIX + "F";
|
||||||
|
String fallbackVal = sysConfigService.selectConfigByKey(fallbackKey);
|
||||||
|
if (StringUtils.hasText(fallbackVal)) {
|
||||||
|
String touser = fallbackVal.trim().replaceAll(",\\s+", ",");
|
||||||
|
logger.info("从配置获取接收人列表(F 系回退) - 分销标识: {}, 配置键: {}, 接收人: {}",
|
||||||
|
distributionMark, fallbackKey, touser);
|
||||||
|
return touser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("未找到接收人配置 - 分销标识: {}, 配置键: {}", distributionMark, configKey);
|
||||||
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("获取接收人配置失败 - 分销标识: {}, 错误: {}", distributionMark, e.getMessage(), e);
|
logger.error("获取接收人配置失败 - 分销标识: {}, 错误: {}", distributionMark, e.getMessage(), e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 金山文档 OAuth 回调(独立路径,避免前端路由拦截)
|
* 金山文档 OAuth 回调(独立路径,避免前端路由拦截)
|
||||||
* 回调地址示例:https://your-domain/kdocs-callback
|
* 回调地址示例:https://your-domain/kdocs-callback
|
||||||
@@ -30,22 +37,66 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
|
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<?> oauthCallbackGet(@RequestParam(value = "code", required = false) String code,
|
public ResponseEntity<?> oauthCallbackGet(HttpServletRequest request,
|
||||||
|
@RequestParam(value = "code", required = false) String code,
|
||||||
@RequestParam(value = "state", required = false) String state,
|
@RequestParam(value = "state", required = false) String state,
|
||||||
@RequestParam(value = "error", required = false) String error,
|
@RequestParam(value = "error", required = false) String error,
|
||||||
@RequestParam(value = "error_description", required = false) String errorDescription) {
|
@RequestParam(value = "error_description", required = false) String errorDescription) {
|
||||||
return handleOAuthCallback(code, state, error, errorDescription);
|
return handleOAuthCallback(request, code, state, error, errorDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<?> handleOAuthCallback(String code, String state, String error, String errorDescription) {
|
/**
|
||||||
|
* 部分开放平台校验可能使用 POST;JSON body 时需回显 challenge 等字段。
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> oauthCallbackPost(HttpServletRequest request) throws IOException {
|
||||||
|
String ct = StringUtils.defaultString(request.getContentType()).toLowerCase();
|
||||||
|
if (ct.contains("application/json")) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader r = request.getReader()) {
|
||||||
|
char[] buf = new char[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = r.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String raw = sb.toString();
|
||||||
|
if (StringUtils.isNotBlank(raw)) {
|
||||||
|
try {
|
||||||
|
JSONObject o = JSON.parseObject(raw);
|
||||||
|
if (o != null && o.containsKey("code")) {
|
||||||
|
Object cv = o.get("code");
|
||||||
|
if (cv != null) {
|
||||||
|
String c = String.valueOf(cv);
|
||||||
|
if (StringUtils.isNotBlank(c) && !"null".equals(c)) {
|
||||||
|
return handleOAuthCallback(request, c, o.getString("state"), o.getString("error"), o.getString("error_description"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("解析 OAuth POST JSON: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, raw);
|
||||||
|
}
|
||||||
|
String code = request.getParameter("code");
|
||||||
|
String state = request.getParameter("state");
|
||||||
|
String error = request.getParameter("error");
|
||||||
|
String errorDescription = request.getParameter("error_description");
|
||||||
|
return handleOAuthCallback(request, code, state, error, errorDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<?> handleOAuthCallback(HttpServletRequest request, String code, String state, String error, String errorDescription) {
|
||||||
try {
|
try {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
String msg = errorDescription != null ? errorDescription : error;
|
String msg = errorDescription != null ? errorDescription : error;
|
||||||
log.error("金山文档授权失败: {}", msg);
|
log.error("金山文档授权失败: {}", msg);
|
||||||
return htmlPage(false, "授权失败: " + msg, null);
|
return htmlPage(false, "授权失败: " + msg, null);
|
||||||
}
|
}
|
||||||
|
// 无 code:多为平台校验回调可达性,或用户直接打开本地址(非授权失败)
|
||||||
if (StringUtils.isBlank(code)) {
|
if (StringUtils.isBlank(code)) {
|
||||||
return htmlPage(false, "缺少授权码 code", null);
|
return callbackEndpointInfoPage(request);
|
||||||
}
|
}
|
||||||
log.info("金山文档授权回调 code 已收到 state={}", state);
|
log.info("金山文档授权回调 code 已收到 state={}", state);
|
||||||
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
|
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
|
||||||
@@ -63,7 +114,7 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
|
|
||||||
private ResponseEntity<String> htmlPage(boolean success, String message, KdocsTokenInfo tokenInfo) {
|
private ResponseEntity<String> htmlPage(boolean success, String message, KdocsTokenInfo tokenInfo) {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.TEXT_HTML);
|
headers.setContentType(MediaType.parseMediaType("text/html;charset=UTF-8"));
|
||||||
String esc = message.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r");
|
String esc = message.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r");
|
||||||
String uid = tokenInfo != null && tokenInfo.getUserId() != null ? tokenInfo.getUserId().replace("\\", "\\\\").replace("'", "\\'") : "";
|
String uid = tokenInfo != null && tokenInfo.getUserId() != null ? tokenInfo.getUserId().replace("\\", "\\\\").replace("'", "\\'") : "";
|
||||||
StringBuilder html = new StringBuilder();
|
StringBuilder html = new StringBuilder();
|
||||||
@@ -83,4 +134,11 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
html.append("</script></body></html>");
|
html.append("</script></body></html>");
|
||||||
return new ResponseEntity<>(html.toString(), headers, HttpStatus.OK);
|
return new ResponseEntity<>(html.toString(), headers, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无授权参数时的占位页:HTTP 200,避免被误判为「回调不可用」,也不向 opener 误发失败消息。
|
||||||
|
*/
|
||||||
|
private ResponseEntity<String> callbackEndpointInfoPage(HttpServletRequest request) {
|
||||||
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金山文档 ChallengeURLValidator 等校验会解析响应为 JSON;返回 HTML 会报 unmarshal challenge response json invalid。
|
||||||
|
* 无 OAuth code 时返回与开放平台风格接近的 JSON,并回显 query / JSON body 中的字段(如 challenge)。
|
||||||
|
*/
|
||||||
|
public final class KdocsCallbackProbeResponses {
|
||||||
|
|
||||||
|
private KdocsCallbackProbeResponses() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final MediaType JSON_UTF8 = MediaType.parseMediaType("application/json;charset=UTF-8");
|
||||||
|
|
||||||
|
private static final MediaType HTML_UTF8 = MediaType.parseMediaType("text/html;charset=UTF-8");
|
||||||
|
|
||||||
|
private static final String HTML_BODY = "<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='robots' content='noindex'>"
|
||||||
|
+ "<title>金山文档授权回调</title></head>"
|
||||||
|
+ "<body style='font-family:sans-serif;text-align:center;padding:40px;color:#333'>"
|
||||||
|
+ "<h2>金山文档授权回调</h2>"
|
||||||
|
+ "<p>此地址用于 OAuth 授权完成后的跳转,请勿直接收藏或打开。</p>"
|
||||||
|
+ "<p>请在系统中点击「连接金山文档」或「授权」后,由金山文档页面自动跳转到此处。</p>"
|
||||||
|
+ "</body></html>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器直接打开回调页时使用(Accept 偏 HTML)。
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<String> callbackReadyHtmlPage() {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(HTML_UTF8);
|
||||||
|
return new ResponseEntity<>(HTML_BODY, headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台 URL 校验:合法 JSON,兼容 WebOffice/开放平台常见 envelope,并回显校验参数。
|
||||||
|
*
|
||||||
|
* @param jsonBody POST application/json 时的原始 body,可为 null
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<String> callbackReadyJson(HttpServletRequest request, String jsonBody) {
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
|
||||||
|
Enumeration<String> names = request.getParameterNames();
|
||||||
|
while (names.hasMoreElements()) {
|
||||||
|
String n = names.nextElement();
|
||||||
|
data.put(n, request.getParameter(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeJsonPrimitivesIntoData(data, jsonBody);
|
||||||
|
|
||||||
|
JSONObject root = new JSONObject();
|
||||||
|
root.put("code", 0);
|
||||||
|
root.put("message", "");
|
||||||
|
root.put("result", "ok");
|
||||||
|
root.put("data", data);
|
||||||
|
|
||||||
|
if (data.containsKey("challenge")) {
|
||||||
|
root.put("challenge", data.get("challenge"));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(JSON_UTF8);
|
||||||
|
return new ResponseEntity<>(root.toJSONString(), headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mergeJsonPrimitivesIntoData(JSONObject data, String jsonBody) {
|
||||||
|
if (StringUtils.isBlank(jsonBody)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONObject in = JSON.parseObject(jsonBody);
|
||||||
|
if (in == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String k : in.keySet()) {
|
||||||
|
Object v = in.get(k);
|
||||||
|
if (v == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (v instanceof JSONObject || v instanceof JSONArray) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data.containsKey(k)) {
|
||||||
|
data.put(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 非 JSON 则忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反向代理后拼浏览器可访问的绝对 URL(OAuth 302 用)。
|
||||||
|
*/
|
||||||
|
public final class KdocsCallbackUrlBuilder {
|
||||||
|
|
||||||
|
private KdocsCallbackUrlBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String absoluteKdocsCallback(HttpServletRequest request, String queryString) {
|
||||||
|
String scheme = request.getHeader("X-Forwarded-Proto");
|
||||||
|
if (StringUtils.isBlank(scheme)) {
|
||||||
|
scheme = request.getScheme();
|
||||||
|
} else if (scheme.contains(",")) {
|
||||||
|
scheme = scheme.substring(0, scheme.indexOf(',')).trim();
|
||||||
|
}
|
||||||
|
String host = request.getHeader("Host");
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
int port = request.getServerPort();
|
||||||
|
host = request.getServerName();
|
||||||
|
if (port != 80 && port != 443) {
|
||||||
|
host = host + ":" + port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(scheme).append("://").append(host).append("/kdocs-callback");
|
||||||
|
if (StringUtils.isNotBlank(queryString)) {
|
||||||
|
sb.append('?').append(queryString);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -178,6 +178,8 @@ public class OrderRowsController extends BaseController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建统计数据。
|
* 构建统计数据。
|
||||||
|
* <p>汇总口径:{@code totalCommission} 为预估佣金合计,排除取消单(validCode=3);{@code totalActualFee} 为实际佣金,仅统计已完成(validCode=17);
|
||||||
|
* {@code estimatePaidPending} 为已付款待收货(validCode=16)的预估佣金,与分组 {@code paid} 一致。</p>
|
||||||
* @param forList true=与列表同数据源(不排除 isCount=0),保证总订单数与分页一致;false=独立统计(排除 isCount=0)
|
* @param forList true=与列表同数据源(不排除 isCount=0),保证总订单数与分页一致;false=独立统计(排除 isCount=0)
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> buildStatistics(OrderRows orderRows, Date beginTime, Date endTime, boolean forList) {
|
private Map<String, Object> buildStatistics(OrderRows orderRows, Date beginTime, Date endTime, boolean forList) {
|
||||||
@@ -209,7 +211,7 @@ public class OrderRowsController extends BaseController
|
|||||||
groupStats.put("cancel", createGroupStat("取消", "cancel"));
|
groupStats.put("cancel", createGroupStat("取消", "cancel"));
|
||||||
groupStats.put("invalid", createGroupStat("无效", "invalid"));
|
groupStats.put("invalid", createGroupStat("无效", "invalid"));
|
||||||
groupStats.put("pending", createGroupStat("待付款", "pending"));
|
groupStats.put("pending", createGroupStat("待付款", "pending"));
|
||||||
groupStats.put("paid", createGroupStat("已付款", "paid"));
|
groupStats.put("paid", createGroupStat("已付款(待结算)", "paid"));
|
||||||
groupStats.put("finished", createGroupStat("已完成", "finished"));
|
groupStats.put("finished", createGroupStat("已完成", "finished"));
|
||||||
groupStats.put("deposit", createGroupStat("已付定金", "deposit"));
|
groupStats.put("deposit", createGroupStat("已付定金", "deposit"));
|
||||||
groupStats.put("illegal", createGroupStat("违规", "illegal"));
|
groupStats.put("illegal", createGroupStat("违规", "illegal"));
|
||||||
@@ -263,8 +265,14 @@ public class OrderRowsController extends BaseController
|
|||||||
actualFeeAmount = row.getActualFee() != null ? row.getActualFee() : 0;
|
actualFeeAmount = row.getActualFee() != null ? row.getActualFee() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCommission += commissionAmount;
|
// 顶部「预估佣金」汇总:排除取消单(validCode=3),其余状态累加单条 commissionAmount
|
||||||
totalActualFee += actualFeeAmount;
|
if (!"3".equals(validCode)) {
|
||||||
|
totalCommission += commissionAmount;
|
||||||
|
}
|
||||||
|
// 顶部「实际佣金」汇总:仅已完成(validCode=17),与联盟「已结算」口径一致
|
||||||
|
if ("17".equals(validCode)) {
|
||||||
|
totalActualFee += actualFeeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
if (validCode != null) {
|
if (validCode != null) {
|
||||||
for (Map.Entry<String, List<String>> group : groups.entrySet()) {
|
for (Map.Entry<String, List<String>> group : groups.entrySet()) {
|
||||||
@@ -290,6 +298,8 @@ public class OrderRowsController extends BaseController
|
|||||||
result.put("totalCosPrice", totalCosPrice);
|
result.put("totalCosPrice", totalCosPrice);
|
||||||
result.put("totalCommission", totalCommission);
|
result.put("totalCommission", totalCommission);
|
||||||
result.put("totalActualFee", totalActualFee);
|
result.put("totalActualFee", totalActualFee);
|
||||||
|
// 已付款待结算:与分组 paid 的预估佣金口径一致,便于独立展示卡片
|
||||||
|
result.put("estimatePaidPending", (Double) groupStats.get("paid").get("commission"));
|
||||||
result.put("totalSkuNum", totalSkuNum);
|
result.put("totalSkuNum", totalSkuNum);
|
||||||
result.put("violationOrders", violationOrders);
|
result.put("violationOrders", violationOrders);
|
||||||
result.put("violationCommission", violationCommission);
|
result.put("violationCommission", violationCommission);
|
||||||
|
|||||||
@@ -156,6 +156,139 @@ public class SocialMediaController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出多套大模型接入配置及当前激活 id
|
||||||
|
*/
|
||||||
|
@GetMapping("/llm-config")
|
||||||
|
public AjaxResult listLlmProfiles()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.listLlmProfiles();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("列出大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单套配置(编辑)
|
||||||
|
*/
|
||||||
|
@GetMapping("/llm-config/profiles/{id}")
|
||||||
|
public AjaxResult getLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.getLlmProfile(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增一套配置
|
||||||
|
*/
|
||||||
|
@Log(title = "新增大模型接入配置", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping("/llm-config/profiles")
|
||||||
|
public AjaxResult createLlmProfile(@RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.createLlmProfile(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("新增大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一套配置
|
||||||
|
*/
|
||||||
|
@Log(title = "更新大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/llm-config/profiles/{id}")
|
||||||
|
public AjaxResult updateLlmProfile(@PathVariable("id") String id, @RequestBody Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.updateLlmProfile(id, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("更新大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一套配置
|
||||||
|
*/
|
||||||
|
@Log(title = "删除大模型接入配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/llm-config/profiles/{id}")
|
||||||
|
public AjaxResult deleteLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.deleteLlmProfile(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 激活指定配置为当前使用
|
||||||
|
*/
|
||||||
|
@Log(title = "激活大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/llm-config/active/{id}")
|
||||||
|
public AjaxResult setActiveLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.setActiveLlmProfile(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("激活大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消激活(Jarvis 使用 yml 默认 Ollama)
|
||||||
|
*/
|
||||||
|
@Log(title = "取消激活大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
|
@DeleteMapping("/llm-config/active")
|
||||||
|
public AjaxResult clearActiveLlmProfile()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.clearActiveLlmProfile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("取消激活大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有套及旧版单键
|
||||||
|
*/
|
||||||
|
@Log(title = "清空大模型接入配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/llm-config")
|
||||||
|
public AjaxResult resetAllLlmConfig()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.resetAllLlmConfig();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("清空大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("清除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大模型连通测试(转发 Jarvis,默认问题 1+1)
|
||||||
|
*/
|
||||||
|
@Log(title = "大模型连通测试", businessType = BusinessType.OTHER)
|
||||||
|
@PostMapping("/llm-config/test")
|
||||||
|
public AjaxResult testLlmProfile(@RequestBody(required = false) Map<String, Object> request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.testLlmProfile(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("大模型连通测试失败", e);
|
||||||
|
return AjaxResult.error("测试失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闲鱼文案(手动):根据标题+可选型号生成代下单、教你下单文案,不依赖JD接口
|
* 闲鱼文案(手动):根据标题+可选型号生成代下单、教你下单文案,不依赖JD接口
|
||||||
*/
|
*/
|
||||||
@@ -164,9 +297,7 @@ public class SocialMediaController extends BaseController
|
|||||||
public AjaxResult generateXianyuWenan(@RequestBody Map<String, Object> request)
|
public AjaxResult generateXianyuWenan(@RequestBody Map<String, Object> request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
String title = (String) request.get("title");
|
Map<String, Object> result = socialMediaService.generateXianyuWenan(request);
|
||||||
String remark = (String) request.get("remark");
|
|
||||||
Map<String, Object> result = socialMediaService.generateXianyuWenan(title, remark);
|
|
||||||
if (Boolean.TRUE.equals(result.get("success"))) {
|
if (Boolean.TRUE.equals(result.get("success"))) {
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.ruoyi.common.core.redis.RedisCache;
|
|||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
import com.ruoyi.jarvis.service.ITencentDocService;
|
import com.ruoyi.jarvis.service.ITencentDocService;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
|
import com.ruoyi.jarvis.util.TencentDocDataParser;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -54,6 +55,9 @@ public class TencentDocController extends BaseController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private com.ruoyi.jarvis.service.ITencentDocDelayedPushService delayedPushService;
|
private com.ruoyi.jarvis.service.ITencentDocDelayedPushService delayedPushService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient wxSendGoofishNotifyClient;
|
||||||
|
|
||||||
/** 单次请求最大行数(腾讯文档 API:行数≤1000) */
|
/** 单次请求最大行数(腾讯文档 API:行数≤1000) */
|
||||||
private static final int API_MAX_ROWS_PER_REQUEST = 200;
|
private static final int API_MAX_ROWS_PER_REQUEST = 200;
|
||||||
/** 用 rowTotal 时接口实际单次只能读 200 行 */
|
/** 用 rowTotal 时接口实际单次只能读 200 行 */
|
||||||
@@ -595,7 +599,8 @@ public class TencentDocController extends BaseController {
|
|||||||
if (cell.containsKey("cellValue")) {
|
if (cell.containsKey("cellValue")) {
|
||||||
String cellText = cell.getJSONObject("cellValue").getString("text");
|
String cellText = cell.getJSONObject("cellValue").getString("text");
|
||||||
if (cellText != null) {
|
if (cellText != null) {
|
||||||
if (cellText.contains("单号")) {
|
// 「物流单号」也含「单号」,须排除,否则会误把物流列当成单号列
|
||||||
|
if (cellText.contains("单号") && !cellText.contains("物流")) {
|
||||||
orderNoColumn = i;
|
orderNoColumn = i;
|
||||||
} else if (cellText.contains("物流")) {
|
} else if (cellText.contains("物流")) {
|
||||||
logisticsColumn = i;
|
logisticsColumn = i;
|
||||||
@@ -607,7 +612,7 @@ public class TencentDocController extends BaseController {
|
|||||||
if (orderNoColumn == -1 || logisticsColumn == -1) {
|
if (orderNoColumn == -1 || logisticsColumn == -1) {
|
||||||
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
logOperation(null, fileId, sheetId, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
|
||||||
"FAILED", "未找到'单号'或'物流'列");
|
"FAILED", "未找到'单号'或'物流'列");
|
||||||
return AjaxResult.error("未找到'单号'或'物流'列,请检查表头配置");
|
return AjaxResult.error("未找到「单号/客户单号/第三方单号」或「物流」列,请检查表头配置");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("表头解析完成 - 单号列: {}, 物流列: {}", orderNoColumn, logisticsColumn);
|
log.info("表头解析完成 - 单号列: {}, 物流列: {}", orderNoColumn, logisticsColumn);
|
||||||
@@ -895,6 +900,90 @@ public class TencentDocController extends BaseController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步中出现报错时,将摘要推送到企业微信(wxSend 闲鱼应用通道)
|
||||||
|
*/
|
||||||
|
private void pushTencentDocRowErrorsToWeCom(String batchId, String fileId, String sheetId,
|
||||||
|
int filledCount, int skippedCount, int errorCount,
|
||||||
|
List<Map<String, Object>> errorLogs) {
|
||||||
|
if (errorCount <= 0 && (errorLogs == null || errorLogs.isEmpty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("【腾讯文档推送】同步存在报错\n");
|
||||||
|
if (batchId != null && !batchId.isEmpty()) {
|
||||||
|
sb.append("批次: ").append(batchId).append("\n");
|
||||||
|
}
|
||||||
|
if (fileId != null && !fileId.isEmpty()) {
|
||||||
|
sb.append("fileId: ").append(fileId).append("\n");
|
||||||
|
}
|
||||||
|
if (sheetId != null && !sheetId.isEmpty()) {
|
||||||
|
sb.append("sheetId: ").append(sheetId).append("\n");
|
||||||
|
}
|
||||||
|
sb.append(String.format("成功队列: %d, 跳过: %d, 错误: %d\n", filledCount, skippedCount, errorCount));
|
||||||
|
if (errorLogs != null && !errorLogs.isEmpty()) {
|
||||||
|
int max = Math.min(15, errorLogs.size());
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
Map<String, Object> el = errorLogs.get(i);
|
||||||
|
Object on = el != null ? el.get("orderNo") : null;
|
||||||
|
Object row = el != null ? el.get("row") : null;
|
||||||
|
Object em = el != null ? el.get("errorMessage") : null;
|
||||||
|
Object et = el != null ? el.get("errorType") : null;
|
||||||
|
sb.append(String.format("%d. 单号:%s 行:%s", i + 1,
|
||||||
|
on != null ? on : "-", row != null ? row : "-"));
|
||||||
|
if (et != null && String.valueOf(et).length() > 0) {
|
||||||
|
sb.append(" ").append(et);
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
if (em != null) {
|
||||||
|
String msg = String.valueOf(em);
|
||||||
|
if (msg.length() > 120) {
|
||||||
|
msg = msg.substring(0, 119) + "…";
|
||||||
|
}
|
||||||
|
sb.append(" ").append(msg).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorLogs.size() > max) {
|
||||||
|
sb.append("… 共 ").append(errorLogs.size()).append(" 条错误\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wxSendGoofishNotifyClient.pushGoofishAgentText(null, "", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并同一行的腾讯文档填充任务(物流更新与仅补京东单号合并为一次 batchUpdate)
|
||||||
|
*/
|
||||||
|
private void mergeTencentDocRowFillUpdate(JSONObject target, JSONObject extra) {
|
||||||
|
if (extra.containsKey("logisticsLink")) {
|
||||||
|
target.put("logisticsLink", extra.getString("logisticsLink"));
|
||||||
|
target.remove("isJdOrderIdOnlyUpdate");
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(extra.getBoolean("isLinkUpdated"))) {
|
||||||
|
target.put("isLinkUpdated", true);
|
||||||
|
if (extra.containsKey("oldLogisticsLink")) {
|
||||||
|
target.put("oldLogisticsLink", extra.getString("oldLogisticsLink"));
|
||||||
|
}
|
||||||
|
if (extra.containsKey("newLogisticsLink")) {
|
||||||
|
target.put("newLogisticsLink", extra.getString("newLogisticsLink"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extra.containsKey("phone")) {
|
||||||
|
target.put("phone", extra.getString("phone"));
|
||||||
|
}
|
||||||
|
if (extra.containsKey("phoneColumn")) {
|
||||||
|
target.put("phoneColumn", extra.getInteger("phoneColumn"));
|
||||||
|
}
|
||||||
|
if (extra.containsKey("jdOrderId")) {
|
||||||
|
target.put("jdOrderId", extra.getString("jdOrderId"));
|
||||||
|
}
|
||||||
|
if (extra.containsKey("jdOrderIdColumn")) {
|
||||||
|
target.put("jdOrderIdColumn", extra.getInteger("jdOrderIdColumn"));
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(extra.getBoolean("isJdOrderIdOnlyUpdate")) && !target.containsKey("logisticsLink")) {
|
||||||
|
target.put("isJdOrderIdOnlyUpdate", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量同步物流链接 - 读取表格数据,根据单号查询订单系统中的物流链接,并填充到表格
|
* 批量同步物流链接 - 读取表格数据,根据单号查询订单系统中的物流链接,并填充到表格
|
||||||
* 优化:记录上次处理的最大行数,每次从最大行数-100开始读取,避免重复处理历史数据
|
* 优化:记录上次处理的最大行数,每次从最大行数-100开始读取,避免重复处理历史数据
|
||||||
@@ -903,6 +992,7 @@ public class TencentDocController extends BaseController {
|
|||||||
@Anonymous
|
@Anonymous
|
||||||
@PostMapping("/fillLogisticsByOrderNo")
|
@PostMapping("/fillLogisticsByOrderNo")
|
||||||
public AjaxResult fillLogisticsByOrderNo(@RequestBody Map<String, Object> params) {
|
public AjaxResult fillLogisticsByOrderNo(@RequestBody Map<String, Object> params) {
|
||||||
|
String batchId = null;
|
||||||
try {
|
try {
|
||||||
// 直接尝试刷新token(如果失败,说明需要首次授权)
|
// 直接尝试刷新token(如果失败,说明需要首次授权)
|
||||||
String accessToken;
|
String accessToken;
|
||||||
@@ -920,7 +1010,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 从参数获取批次ID(如果是批量调用会传入)
|
// 从参数获取批次ID(如果是批量调用会传入)
|
||||||
String batchId = params.get("batchId") != null ? String.valueOf(params.get("batchId")) : null;
|
batchId = params.get("batchId") != null ? String.valueOf(params.get("batchId")) : null;
|
||||||
|
|
||||||
// 从参数或配置中获取文档信息
|
// 从参数或配置中获取文档信息
|
||||||
String fileId = (String) params.get("fileId");
|
String fileId = (String) params.get("fileId");
|
||||||
@@ -983,7 +1073,8 @@ public class TencentDocController extends BaseController {
|
|||||||
// 读取表格数据(先读取表头行用于识别列位置)
|
// 读取表格数据(先读取表头行用于识别列位置)
|
||||||
// 根据官方文档,使用 A1 表示法(Excel格式)
|
// 根据官方文档,使用 A1 表示法(Excel格式)
|
||||||
// 参考:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
// 参考:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
||||||
String headerRange = String.format("A%d:Z%d", headerRow, headerRow); // 例如:A2:Z2
|
// 与数据区列宽一致,避免「京东下单订单号」在 Z 列之后时无法识别表头
|
||||||
|
String headerRange = String.format("A%d:%s%d", headerRow, DATA_RANGE_COL_END, headerRow);
|
||||||
log.info("读取表头 - 行号: {}, range: {}", headerRow, headerRange);
|
log.info("读取表头 - 行号: {}, range: {}", headerRow, headerRange);
|
||||||
|
|
||||||
JSONObject headerData = null;
|
JSONObject headerData = null;
|
||||||
@@ -1007,7 +1098,8 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动识别列位置(从表头中识别)
|
// 自动识别列位置(从表头中识别)
|
||||||
Integer orderNoColumn = null; // "单号"列
|
Integer orderNoColumn = null; // 第三方「单号」列(排除京东下单订单号、物流单号)
|
||||||
|
Integer jdPlaceOrderNoColumn = null; // 「京东下单订单号」列,对应本地 orderId
|
||||||
Integer logisticsLinkColumn = null; // "物流单号"列
|
Integer logisticsLinkColumn = null; // "物流单号"列
|
||||||
Integer remarkColumn = null; // "备注"列
|
Integer remarkColumn = null; // "备注"列
|
||||||
Integer arrangedColumn = null; // "是否安排"列
|
Integer arrangedColumn = null; // "是否安排"列
|
||||||
@@ -1019,59 +1111,65 @@ public class TencentDocController extends BaseController {
|
|||||||
return AjaxResult.error("无法识别表头,表头数据为空");
|
return AjaxResult.error("无法识别表头,表头数据为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找所有相关列
|
// 列名须与表格完全一致(仅忽略首尾空白、不间断空格等):备注、是否安排、物流单号、下单电话、标记、京东下单订单号;另需「单号」「客户单号」或「第三方单号」之一
|
||||||
log.info("开始识别表头列,共 {} 列", headerRowData.size());
|
log.info("开始识别表头列(完全匹配列名),共 {} 列", headerRowData.size());
|
||||||
for (int i = 0; i < headerRowData.size(); i++) {
|
for (int i = 0; i < headerRowData.size(); i++) {
|
||||||
String cellValue = headerRowData.getString(i);
|
String cellValue = headerRowData.getString(i);
|
||||||
if (cellValue != null) {
|
if (cellValue == null) {
|
||||||
String cellValueTrim = cellValue.trim();
|
continue;
|
||||||
log.debug("列 {} 内容: [{}]", i, cellValueTrim);
|
}
|
||||||
|
String norm = TencentDocDataParser.normalizeTencentDocHeader(cellValue);
|
||||||
|
log.debug("列 {} 原始: [{}] 规范化: [{}]", i, cellValue.trim(), norm);
|
||||||
|
|
||||||
// 识别"单号"列
|
if (jdPlaceOrderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "京东下单订单号")) {
|
||||||
if (orderNoColumn == null && cellValueTrim.contains("单号")) {
|
jdPlaceOrderNoColumn = i;
|
||||||
orderNoColumn = i;
|
log.info("✓ 列名完全匹配「京东下单订单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i);
|
}
|
||||||
}
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "单号")) {
|
||||||
|
orderNoColumn = i;
|
||||||
// 识别"物流单号"或"物流链接"列
|
log.info("✓ 列名完全匹配「单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) {
|
}
|
||||||
logisticsLinkColumn = i;
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "客户单号")) {
|
||||||
log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i);
|
orderNoColumn = i;
|
||||||
}
|
log.info("\u2713 列名完全匹配「客户单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
// 识别"备注"列(可选)
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "第三方单号")) {
|
||||||
if (remarkColumn == null && cellValueTrim.contains("备注")) {
|
orderNoColumn = i;
|
||||||
remarkColumn = i;
|
log.info("✓ 列名完全匹配「第三方单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
log.info("✓ 识别到 '备注' 列:第 {} 列(索引{})", i + 1, i);
|
}
|
||||||
}
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流单号")) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
// 识别"是否安排"列(可选)
|
log.info("✓ 列名完全匹配「物流单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
if (arrangedColumn == null && (cellValueTrim.contains("是否安排") || cellValueTrim.contains("安排"))) {
|
}
|
||||||
arrangedColumn = i;
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流链接")) {
|
||||||
log.info("✓ 识别到 '是否安排' 列:第 {} 列(索引{})", i + 1, i);
|
logisticsLinkColumn = i;
|
||||||
}
|
log.warn("✓ 列名匹配「物流链接」(建议改为「物流单号」):第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
// 识别"标记"列(可选)
|
if (remarkColumn == null && TencentDocDataParser.headerEquals(cellValue, "备注")) {
|
||||||
if (markColumn == null && cellValueTrim.contains("标记")) {
|
remarkColumn = i;
|
||||||
markColumn = i;
|
log.info("✓ 列名完全匹配「备注」:第 {} 列(索引{})", i + 1, i);
|
||||||
log.info("✓ 识别到 '标记' 列:第 {} 列(索引{})", i + 1, i);
|
}
|
||||||
}
|
if (arrangedColumn == null && TencentDocDataParser.headerEquals(cellValue, "是否安排")) {
|
||||||
|
arrangedColumn = i;
|
||||||
// 识别"下单电话"列(可选)
|
log.info("✓ 列名完全匹配「是否安排」:第 {} 列(索引{})", i + 1, i);
|
||||||
if (phoneColumn == null && (cellValueTrim.contains("下单电话"))) {
|
}
|
||||||
phoneColumn = i;
|
if (markColumn == null && TencentDocDataParser.headerEquals(cellValue, "标记")) {
|
||||||
log.info("✓ 识别到 '下单电话' 列:第 {} 列(索引{}),列名: [{}]", i + 1, i, cellValueTrim);
|
markColumn = i;
|
||||||
}
|
log.info("✓ 列名完全匹配「标记」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
if (phoneColumn == null && TencentDocDataParser.headerEquals(cellValue, "下单电话")) {
|
||||||
|
phoneColumn = i;
|
||||||
|
log.info("✓ 列名完全匹配「下单电话」:第 {} 列(索引{})", i + 1, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("表头列识别完成");
|
log.info("表头列识别完成");
|
||||||
|
|
||||||
// 检查必需的列是否都已识别
|
// 检查必需的列是否都已识别
|
||||||
if (orderNoColumn == null) {
|
if (orderNoColumn == null) {
|
||||||
return AjaxResult.error("无法找到'单号'列,请检查表头是否包含'单号'字段");
|
return AjaxResult.error("无法找到列名完全为「单号」「客户单号」或「第三方单号」的列(请与表格列名一致,勿加空格或后缀)");
|
||||||
}
|
}
|
||||||
if (logisticsLinkColumn == null) {
|
if (logisticsLinkColumn == null) {
|
||||||
return AjaxResult.error("无法找到'物流单号'或'物流链接'列,请检查表头");
|
return AjaxResult.error("无法找到列名完全为「物流单号」的列(兼容「物流链接」);请与表格列名一致");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提示可选列的识别情况
|
// 提示可选列的识别情况
|
||||||
@@ -1087,9 +1185,12 @@ public class TencentDocController extends BaseController {
|
|||||||
if (phoneColumn == null) {
|
if (phoneColumn == null) {
|
||||||
log.warn("未找到'下单电话'列,将跳过该字段的更新");
|
log.warn("未找到'下单电话'列,将跳过该字段的更新");
|
||||||
}
|
}
|
||||||
|
if (jdPlaceOrderNoColumn == null) {
|
||||||
|
log.warn("未找到'京东下单订单号'列,将跳过该字段的同步(请在表头增加该列以写入京东 orderId)");
|
||||||
|
}
|
||||||
|
|
||||||
log.info("列位置识别完成 - 单号: {}, 物流单号: {}, 备注: {}, 是否安排: {}, 标记: {}, 下单电话: {}",
|
log.info("列位置识别完成 - 单号: {}, 京东下单订单号: {}, 物流单号: {}, 备注: {}, 是否安排: {}, 标记: {}, 下单电话: {}",
|
||||||
orderNoColumn, logisticsLinkColumn, remarkColumn, arrangedColumn, markColumn, phoneColumn);
|
orderNoColumn, jdPlaceOrderNoColumn, logisticsLinkColumn, remarkColumn, arrangedColumn, markColumn, phoneColumn);
|
||||||
|
|
||||||
// 读取数据行:接口实际只能读 200 行,严格限制单次行数,失败时逐步缩小范围重试
|
// 读取数据行:接口实际只能读 200 行,严格限制单次行数,失败时逐步缩小范围重试
|
||||||
// 腾讯文档 get_range 的 range 为「结束行不包含」:要读到 endRow 含最后一行,须传 endRow+1
|
// 腾讯文档 get_range 的 range 为「结束行不包含」:要读到 endRow 含最后一行,须传 endRow+1
|
||||||
@@ -1229,7 +1330,9 @@ public class TencentDocController extends BaseController {
|
|||||||
errorCount++;
|
errorCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row == null || row.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) {
|
// 仅要求能读到第三方单号、物流列;京东列常在更右侧且稀疏行可能未返回尾部空列,不能要求 row 长度覆盖京东列
|
||||||
|
int minDataColIdx = Math.max(orderNoColumn, logisticsLinkColumn);
|
||||||
|
if (row == null || row.size() <= minDataColIdx) {
|
||||||
continue; // 跳过空行或列数不足的行
|
continue; // 跳过空行或列数不足的行
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1263,6 +1366,16 @@ public class TencentDocController extends BaseController {
|
|||||||
if (existingOrder != null) {
|
if (existingOrder != null) {
|
||||||
String dbLogisticsLink = existingOrder.getLogisticsLink();
|
String dbLogisticsLink = existingOrder.getLogisticsLink();
|
||||||
String trimmedDbLink = (dbLogisticsLink != null) ? dbLogisticsLink.trim() : "";
|
String trimmedDbLink = (dbLogisticsLink != null) ? dbLogisticsLink.trim() : "";
|
||||||
|
String dbJdOrderId = existingOrder.getOrderId() != null ? existingOrder.getOrderId().trim() : "";
|
||||||
|
String docJdOrderId = "";
|
||||||
|
if (jdPlaceOrderNoColumn != null && row.size() > jdPlaceOrderNoColumn) {
|
||||||
|
try {
|
||||||
|
String cj = row.getString(jdPlaceOrderNoColumn);
|
||||||
|
docJdOrderId = cj != null ? cj.trim() : "";
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.debug("读取京东下单订单号列异常 行 {}: {}", excelRow, ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 比对物流链接,如果不一致则需要更新
|
// 比对物流链接,如果不一致则需要更新
|
||||||
if (!trimmedDbLink.isEmpty() && !trimmedExistingLink.equals(trimmedDbLink)) {
|
if (!trimmedDbLink.isEmpty() && !trimmedExistingLink.equals(trimmedDbLink)) {
|
||||||
@@ -1279,6 +1392,11 @@ public class TencentDocController extends BaseController {
|
|||||||
update.put("isLinkUpdated", true); // 标记为物流链接更新
|
update.put("isLinkUpdated", true); // 标记为物流链接更新
|
||||||
update.put("oldLogisticsLink", trimmedExistingLink); // 旧链接
|
update.put("oldLogisticsLink", trimmedExistingLink); // 旧链接
|
||||||
update.put("newLogisticsLink", trimmedDbLink); // 新链接
|
update.put("newLogisticsLink", trimmedDbLink); // 新链接
|
||||||
|
// 物流变更:与京东单号一并同步(单号随物流变,覆盖文档原京东单号)
|
||||||
|
if (jdPlaceOrderNoColumn != null && !dbJdOrderId.isEmpty()) {
|
||||||
|
update.put("jdOrderId", dbJdOrderId);
|
||||||
|
update.put("jdOrderIdColumn", jdPlaceOrderNoColumn);
|
||||||
|
}
|
||||||
updates.add(update);
|
updates.add(update);
|
||||||
|
|
||||||
filledCount++; // 统计为填充数量
|
filledCount++; // 统计为填充数量
|
||||||
@@ -1293,6 +1411,17 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
log.info("========== 物流链接不一致,已加入更新队列 - 单号: {}, 行号: {}, 旧链接: {}, 新链接: {} ==========",
|
log.info("========== 物流链接不一致,已加入更新队列 - 单号: {}, 行号: {}, 旧链接: {}, 新链接: {} ==========",
|
||||||
orderNo, excelRow, trimmedExistingLink, trimmedDbLink);
|
orderNo, excelRow, trimmedExistingLink, trimmedDbLink);
|
||||||
|
} else if (docJdOrderId.isEmpty() && !dbJdOrderId.isEmpty() && jdPlaceOrderNoColumn != null) {
|
||||||
|
// 文档已有物流、京东单号列为空:仅补写京东单号
|
||||||
|
JSONObject jdUpdate = new JSONObject();
|
||||||
|
jdUpdate.put("row", excelRow);
|
||||||
|
jdUpdate.put("orderNo", orderNo);
|
||||||
|
jdUpdate.put("isJdOrderIdOnlyUpdate", true);
|
||||||
|
jdUpdate.put("jdOrderId", dbJdOrderId);
|
||||||
|
jdUpdate.put("jdOrderIdColumn", jdPlaceOrderNoColumn);
|
||||||
|
updates.add(jdUpdate);
|
||||||
|
filledCount++;
|
||||||
|
log.info("========== 文档有物流无京东单号,补写京东下单订单号 - 第三方单号: {}, 行: {} ==========", orderNo, excelRow);
|
||||||
} else {
|
} else {
|
||||||
// 物流链接一致或数据库中没有链接,只需同步订单状态
|
// 物流链接一致或数据库中没有链接,只需同步订单状态
|
||||||
if (existingOrder.getTencentDocPushed() == null || existingOrder.getTencentDocPushed() == 0) {
|
if (existingOrder.getTencentDocPushed() == null || existingOrder.getTencentDocPushed() == 0) {
|
||||||
@@ -1330,7 +1459,7 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 根据第三方单号查询订单
|
// 根据第三方单号查询订单(与文档「单号/客户单号/第三方单号」列单元格一致)
|
||||||
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo);
|
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
@@ -1382,6 +1511,12 @@ public class TencentDocController extends BaseController {
|
|||||||
update.put("phoneColumn", phoneColumn);
|
update.put("phoneColumn", phoneColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文档无物流:与物流一并写入京东单号(无物流无单号 → 一起写入)
|
||||||
|
if (jdPlaceOrderNoColumn != null && order.getOrderId() != null && !order.getOrderId().trim().isEmpty()) {
|
||||||
|
update.put("jdOrderId", order.getOrderId().trim());
|
||||||
|
update.put("jdOrderIdColumn", jdPlaceOrderNoColumn);
|
||||||
|
}
|
||||||
|
|
||||||
// 注意:不再保存order对象,写入成功后会重新查询以确保数据最新
|
// 注意:不再保存order对象,写入成功后会重新查询以确保数据最新
|
||||||
updates.add(update);
|
updates.add(update);
|
||||||
|
|
||||||
@@ -1420,12 +1555,17 @@ public class TencentDocController extends BaseController {
|
|||||||
// 获取今天的日期,格式:yyMMdd(如:251105)
|
// 获取今天的日期,格式:yyMMdd(如:251105)
|
||||||
String today = new java.text.SimpleDateFormat("yyMMdd").format(new java.util.Date());
|
String today = new java.text.SimpleDateFormat("yyMMdd").format(new java.util.Date());
|
||||||
|
|
||||||
// 将更新按行分组,批量写入
|
// 将更新按行分组,批量写入(同一行可能同时有物流与京东单号任务,需合并)
|
||||||
Map<Integer, JSONObject> rowUpdates = new java.util.HashMap<>();
|
Map<Integer, JSONObject> rowUpdates = new java.util.LinkedHashMap<>();
|
||||||
for (int i = 0; i < updates.size(); i++) {
|
for (int i = 0; i < updates.size(); i++) {
|
||||||
JSONObject update = updates.getJSONObject(i);
|
JSONObject update = updates.getJSONObject(i);
|
||||||
int row = update.getIntValue("row");
|
int row = update.getIntValue("row");
|
||||||
rowUpdates.put(row, update);
|
JSONObject existing = rowUpdates.get(row);
|
||||||
|
if (existing == null) {
|
||||||
|
rowUpdates.put(row, update);
|
||||||
|
} else {
|
||||||
|
mergeTencentDocRowFillUpdate(existing, update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量写入(每行单独写入,同时更新多个字段)
|
// 批量写入(每行单独写入,同时更新多个字段)
|
||||||
@@ -1437,11 +1577,94 @@ public class TencentDocController extends BaseController {
|
|||||||
try {
|
try {
|
||||||
int row = entry.getKey();
|
int row = entry.getKey();
|
||||||
JSONObject update = entry.getValue();
|
JSONObject update = entry.getValue();
|
||||||
String logisticsLink = update.getString("logisticsLink");
|
|
||||||
String expectedOrderNo = update.getString("orderNo");
|
String expectedOrderNo = update.getString("orderNo");
|
||||||
|
|
||||||
// 重新读取该行数据,验证单号和物流链接列
|
// 文档已有物流、仅补写京东下单订单号(有物流无京东单号)
|
||||||
String verifyRange = String.format("A%d:Z%d", row, row);
|
if (Boolean.TRUE.equals(update.getBoolean("isJdOrderIdOnlyUpdate"))) {
|
||||||
|
Integer jdCol = update.getInteger("jdOrderIdColumn");
|
||||||
|
String jdId = update.getString("jdOrderId");
|
||||||
|
if (jdCol == null || jdId == null || jdId.trim().isEmpty()) {
|
||||||
|
log.warn("仅补京东单号任务参数不全 - 行 {}", row);
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jdId = jdId.trim();
|
||||||
|
String jdVerifyRange = String.format("A%d:%s%d", row, DATA_RANGE_COL_END, row);
|
||||||
|
JSONObject jdVerifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, jdVerifyRange);
|
||||||
|
if (jdVerifyData == null || !jdVerifyData.containsKey("values")) {
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JSONArray jdVerifyRows = jdVerifyData.getJSONArray("values");
|
||||||
|
if (jdVerifyRows == null || jdVerifyRows.isEmpty()) {
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JSONArray jdVerifyRow = jdVerifyRows.getJSONArray(0);
|
||||||
|
int jdBaseNeedIdx = Math.max(orderNoColumn, logisticsLinkColumn);
|
||||||
|
if (jdVerifyRow == null || jdVerifyRow.size() <= jdBaseNeedIdx) {
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String jdCurOrderNo = jdVerifyRow.getString(orderNoColumn);
|
||||||
|
if (jdCurOrderNo == null || !jdCurOrderNo.trim().equals(expectedOrderNo)) {
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String jdDocLogistics = jdVerifyRow.getString(logisticsLinkColumn);
|
||||||
|
if (jdDocLogistics == null || jdDocLogistics.trim().isEmpty()) {
|
||||||
|
log.info("跳过仅补京东单号 - 行 {} 物流列为空", row);
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String docJd = "";
|
||||||
|
if (jdVerifyRow.size() > jdCol) {
|
||||||
|
try {
|
||||||
|
String sj = jdVerifyRow.getString(jdCol);
|
||||||
|
docJd = sj != null ? sj.trim() : "";
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!docJd.isEmpty()) {
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JSONArray jdRequests = new JSONArray();
|
||||||
|
jdRequests.add(buildUpdateCellRequest(sheetId, row - 1, jdCol, jdId, false));
|
||||||
|
JSONObject jdBatch = new JSONObject();
|
||||||
|
jdBatch.put("requests", jdRequests);
|
||||||
|
tencentDocService.batchUpdate(accessToken, fileId, jdBatch);
|
||||||
|
currentBatchSuccessUpdates++;
|
||||||
|
if (row > currentBatchMaxSuccessRow) {
|
||||||
|
currentBatchMaxSuccessRow = row;
|
||||||
|
}
|
||||||
|
log.info("✓ 已补写京东下单订单号 - 第三方单号: {}, 行: {}, 值: {}", expectedOrderNo, row, jdId);
|
||||||
|
logOperation(batchId, fileId, sheetId, "BATCH_SYNC", expectedOrderNo, row, jdId,
|
||||||
|
"SUCCESS", "补写京东下单订单号");
|
||||||
|
Map<String, Object> jdSuccessLog = new java.util.HashMap<>();
|
||||||
|
jdSuccessLog.put("orderNo", expectedOrderNo);
|
||||||
|
jdSuccessLog.put("row", row);
|
||||||
|
jdSuccessLog.put("jdOrderId", jdId);
|
||||||
|
jdSuccessLog.put("updateType", "JD_ORDER_ID");
|
||||||
|
successLogs.add(jdSuccessLog);
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String logisticsLink = update.getString("logisticsLink");
|
||||||
|
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||||
|
log.warn("跳过写入 - 行 {} 缺少物流链接", row);
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新读取该行(列宽与批量读取一致,含 Z 列之后的京东单号列)
|
||||||
|
String verifyRange = String.format("A%d:%s%d", row, DATA_RANGE_COL_END, row);
|
||||||
JSONObject verifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, verifyRange);
|
JSONObject verifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, verifyRange);
|
||||||
|
|
||||||
if (verifyData == null || !verifyData.containsKey("values")) {
|
if (verifyData == null || !verifyData.containsKey("values")) {
|
||||||
@@ -1476,8 +1699,9 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
JSONArray verifyRow = verifyRows.getJSONArray(0);
|
JSONArray verifyRow = verifyRows.getJSONArray(0);
|
||||||
if (verifyRow == null || verifyRow.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) {
|
int verifyMinColIdx = Math.max(orderNoColumn, logisticsLinkColumn);
|
||||||
log.warn("验证失败 - 行 {} 列数不足", row);
|
if (verifyRow == null || verifyRow.size() <= verifyMinColIdx) {
|
||||||
|
log.warn("验证失败 - 行 {} 列数不足(需覆盖单号列与物流列)", row);
|
||||||
errorCount++;
|
errorCount++;
|
||||||
|
|
||||||
// 记录错误详情
|
// 记录错误详情
|
||||||
@@ -1567,6 +1791,37 @@ public class TencentDocController extends BaseController {
|
|||||||
log.info("✓ 物流链接更新,保留原标记日期 - 单号: {}, 行: {}", expectedOrderNo, row);
|
log.info("✓ 物流链接更新,保留原标记日期 - 单号: {}, 行: {}", expectedOrderNo, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 物流变更:与京东单号一并覆盖写入;首次填物流:仅文档京东列为空时写入
|
||||||
|
boolean wroteJdOrderIdCell = false;
|
||||||
|
String jdIdUpdForLog = null;
|
||||||
|
Integer jdColUpd = update.getInteger("jdOrderIdColumn");
|
||||||
|
String jdIdUpd = update.getString("jdOrderId");
|
||||||
|
if (jdColUpd != null && jdIdUpd != null && !jdIdUpd.trim().isEmpty()) {
|
||||||
|
jdIdUpd = jdIdUpd.trim();
|
||||||
|
String docJdVal = "";
|
||||||
|
if (verifyRow.size() > jdColUpd) {
|
||||||
|
try {
|
||||||
|
String sj = verifyRow.getString(jdColUpd);
|
||||||
|
docJdVal = sj != null ? sj.trim() : "";
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean shouldWriteJd = Boolean.TRUE.equals(isLinkUpdated) || docJdVal.isEmpty();
|
||||||
|
if (shouldWriteJd) {
|
||||||
|
requests.add(buildUpdateCellRequest(sheetId, row - 1, jdColUpd, jdIdUpd, false));
|
||||||
|
wroteJdOrderIdCell = true;
|
||||||
|
jdIdUpdForLog = jdIdUpd;
|
||||||
|
if (Boolean.TRUE.equals(isLinkUpdated)) {
|
||||||
|
log.info("✓ 同步京东下单订单号(随物流变更) - 第三方单号: {}, 行: {}, 值: {}", expectedOrderNo, row, jdIdUpd);
|
||||||
|
} else {
|
||||||
|
log.info("✓ 写入京东下单订单号(与物流一并) - 第三方单号: {}, 行: {}, 值: {}", expectedOrderNo, row, jdIdUpd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("✓ 文档已有京东下单订单号,跳过写入 - 第三方单号: {}, 行: {}, 现有: [{}]", expectedOrderNo, row, docJdVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 构建完整的 batchUpdate 请求体
|
// 构建完整的 batchUpdate 请求体
|
||||||
JSONObject batchUpdateBody = new JSONObject();
|
JSONObject batchUpdateBody = new JSONObject();
|
||||||
batchUpdateBody.put("requests", requests);
|
batchUpdateBody.put("requests", requests);
|
||||||
@@ -1622,6 +1877,9 @@ public class TencentDocController extends BaseController {
|
|||||||
if (orderToUpdate != null && orderToUpdate.getModelNumber() != null) {
|
if (orderToUpdate != null && orderToUpdate.getModelNumber() != null) {
|
||||||
successLog.put("modelNumber", orderToUpdate.getModelNumber());
|
successLog.put("modelNumber", orderToUpdate.getModelNumber());
|
||||||
}
|
}
|
||||||
|
if (wroteJdOrderIdCell && jdIdUpdForLog != null) {
|
||||||
|
successLog.put("jdOrderId", jdIdUpdForLog);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否为物流链接更新(复用之前的变量)
|
// 检查是否为物流链接更新(复用之前的变量)
|
||||||
if (Boolean.TRUE.equals(isLinkUpdated)) {
|
if (Boolean.TRUE.equals(isLinkUpdated)) {
|
||||||
@@ -1694,6 +1952,7 @@ public class TencentDocController extends BaseController {
|
|||||||
result.put("skippedCount", skippedCount);
|
result.put("skippedCount", skippedCount);
|
||||||
result.put("errorCount", errorCount);
|
result.put("errorCount", errorCount);
|
||||||
result.put("orderNoColumn", orderNoColumn);
|
result.put("orderNoColumn", orderNoColumn);
|
||||||
|
result.put("jdPlaceOrderNoColumn", jdPlaceOrderNoColumn);
|
||||||
result.put("logisticsLinkColumn", logisticsLinkColumn);
|
result.put("logisticsLinkColumn", logisticsLinkColumn);
|
||||||
result.put("skipPushedOrders", skipPushedOrders); // 是否跳过已推送订单
|
result.put("skipPushedOrders", skipPushedOrders); // 是否跳过已推送订单
|
||||||
|
|
||||||
@@ -1720,7 +1979,7 @@ public class TencentDocController extends BaseController {
|
|||||||
if (errorCount > 0 && successUpdates == 0) {
|
if (errorCount > 0 && successUpdates == 0) {
|
||||||
status = "FAILED";
|
status = "FAILED";
|
||||||
} else if (errorCount > 0) {
|
} else if (errorCount > 0) {
|
||||||
status = "PARTIAL_SUCCESS";
|
status = "PARTIAL";
|
||||||
}
|
}
|
||||||
batchPushService.updateBatchPushRecord(batchId, status, successUpdates, skippedCount, errorCount,
|
batchPushService.updateBatchPushRecord(batchId, status, successUpdates, skippedCount, errorCount,
|
||||||
message, null);
|
message, null);
|
||||||
@@ -1748,7 +2007,21 @@ public class TencentDocController extends BaseController {
|
|||||||
return AjaxResult.success("填充物流链接完成", result);
|
return AjaxResult.success("填充物流链接完成", result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("填充物流链接失败", e);
|
log.error("填充物流链接失败", e);
|
||||||
return AjaxResult.error("填充物流链接失败: " + e.getMessage());
|
String errMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
|
||||||
|
if (batchId != null && !batchId.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, errMsg);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("异常后更新批量推送记录失败 batchId={}", batchId, ex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
wxSendGoofishNotifyClient.pushGoofishAgentText(null, "",
|
||||||
|
"【腾讯文档推送】批量同步异常\n批次: " + batchId + "\n" + errMsg);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("腾讯文档推送异常企微通知失败: {}", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.error("填充物流链接失败: " + errMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1893,6 +2166,10 @@ public class TencentDocController extends BaseController {
|
|||||||
String fileId = tencentDocConfig.getFileId();
|
String fileId = tencentDocConfig.getFileId();
|
||||||
String sheetId = tencentDocConfig.getSheetId();
|
String sheetId = tencentDocConfig.getSheetId();
|
||||||
|
|
||||||
|
if (fileId != null && !fileId.trim().isEmpty()) {
|
||||||
|
batchPushService.reconcileStaleRunningRecords(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileId != null && sheetId != null) {
|
if (fileId != null && sheetId != null) {
|
||||||
com.ruoyi.jarvis.domain.TencentDocBatchPushRecord lastSuccess =
|
com.ruoyi.jarvis.domain.TencentDocBatchPushRecord lastSuccess =
|
||||||
batchPushService.getLastSuccessRecord(fileId, sheetId);
|
batchPushService.getLastSuccessRecord(fileId, sheetId);
|
||||||
@@ -2032,23 +2309,33 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
for (int i = 0; i < headerRowData.size(); i++) {
|
for (int i = 0; i < headerRowData.size(); i++) {
|
||||||
String cellValue = headerRowData.getString(i);
|
String cellValue = headerRowData.getString(i);
|
||||||
if (cellValue != null) {
|
if (cellValue == null) {
|
||||||
String cellValueTrim = cellValue.trim();
|
continue;
|
||||||
|
}
|
||||||
if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) {
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "单号")) {
|
||||||
orderNoColumn = i;
|
orderNoColumn = i;
|
||||||
log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i);
|
log.info("✓ 列名完全匹配「单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
}
|
}
|
||||||
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "客户单号")) {
|
||||||
if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) {
|
orderNoColumn = i;
|
||||||
logisticsLinkColumn = i;
|
log.info("\u2713 列名完全匹配「客户单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i);
|
}
|
||||||
}
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "第三方单号")) {
|
||||||
|
orderNoColumn = i;
|
||||||
|
log.info("✓ 列名完全匹配「第三方单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流单号")) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
|
log.info("✓ 列名完全匹配「物流单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流链接")) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
|
log.info("✓ 列名匹配「物流链接」:第 {} 列(索引{})", i + 1, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orderNoColumn == null || logisticsLinkColumn == null) {
|
if (orderNoColumn == null || logisticsLinkColumn == null) {
|
||||||
return AjaxResult.error("无法识别表头列,请确保表头包含'单号'和'物流单号'列");
|
return AjaxResult.error("无法识别表头列,请确保存在列名完全为「单号」「客户单号」或「第三方单号」,以及「物流单号」(或「物流链接」)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计结果
|
// 统计结果
|
||||||
@@ -2330,23 +2617,33 @@ public class TencentDocController extends BaseController {
|
|||||||
|
|
||||||
for (int i = 0; i < headerRowData.size(); i++) {
|
for (int i = 0; i < headerRowData.size(); i++) {
|
||||||
String cellValue = headerRowData.getString(i);
|
String cellValue = headerRowData.getString(i);
|
||||||
if (cellValue != null) {
|
if (cellValue == null) {
|
||||||
String cellValueTrim = cellValue.trim();
|
continue;
|
||||||
|
}
|
||||||
if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) {
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "单号")) {
|
||||||
orderNoColumn = i;
|
orderNoColumn = i;
|
||||||
log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i);
|
log.info("✓ 列名完全匹配「单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
}
|
}
|
||||||
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "客户单号")) {
|
||||||
if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) {
|
orderNoColumn = i;
|
||||||
logisticsLinkColumn = i;
|
log.info("\u2713 列名完全匹配「客户单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i);
|
}
|
||||||
}
|
if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellValue, "第三方单号")) {
|
||||||
|
orderNoColumn = i;
|
||||||
|
log.info("✓ 列名完全匹配「第三方单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流单号")) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
|
log.info("✓ 列名完全匹配「物流单号」:第 {} 列(索引{})", i + 1, i);
|
||||||
|
}
|
||||||
|
if (logisticsLinkColumn == null && TencentDocDataParser.headerEquals(cellValue, "物流链接")) {
|
||||||
|
logisticsLinkColumn = i;
|
||||||
|
log.info("✓ 列名匹配「物流链接」:第 {} 列(索引{})", i + 1, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orderNoColumn == null || logisticsLinkColumn == null) {
|
if (orderNoColumn == null || logisticsLinkColumn == null) {
|
||||||
return AjaxResult.error("无法识别表头列,请确保表头包含'单号'和'物流单号'列");
|
return AjaxResult.error("无法识别表头列,请确保存在列名完全为「单号」「客户单号」或「第三方单号」,以及「物流单号」(或「物流链接」)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计结果
|
// 统计结果
|
||||||
@@ -2438,11 +2735,10 @@ public class TencentDocController extends BaseController {
|
|||||||
String cleanedLogisticsLink = cleanLogisticsLink(logisticsLinkFromDoc);
|
String cleanedLogisticsLink = cleanLogisticsLink(logisticsLinkFromDoc);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 通过第三方单号查找本地订单
|
// 通过第三方单号查找本地订单;找不到再按内部单号(remark)
|
||||||
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNoFromDoc.trim());
|
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNoFromDoc.trim());
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
// 如果通过第三方单号找不到,尝试通过内部单号(remark)查找
|
|
||||||
order = jdOrderService.selectJDOrderByRemark(orderNoFromDoc.trim());
|
order = jdOrderService.selectJDOrderByRemark(orderNoFromDoc.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2868,6 +3164,14 @@ public class TencentDocController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorCount > 0 || (errorLogs != null && !errorLogs.isEmpty())) {
|
||||||
|
try {
|
||||||
|
pushTencentDocRowErrorsToWeCom(batchId, fileId, sheetId, filledCount, skippedCount, errorCount, errorLogs);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("腾讯文档报错企微推送失败: {}", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 构建请求体
|
// 构建请求体
|
||||||
JSONObject requestBody = new JSONObject();
|
JSONObject requestBody = new JSONObject();
|
||||||
requestBody.put("title", "腾讯文档同步成功");
|
requestBody.put("title", "腾讯文档同步成功");
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
|
||||||
|
import com.ruoyi.jarvis.service.IWeComInboundService;
|
||||||
|
import com.ruoyi.jarvis.service.IWeComInboundTraceService;
|
||||||
|
import com.ruoyi.jarvis.wecom.WxSendWeComPushClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wxSend 企微回调桥接:HTTPS + 共享密钥,无登录态
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/wecom")
|
||||||
|
public class WeComInboundController {
|
||||||
|
|
||||||
|
public static final String HEADER_SECRET = "X-Jarvis-WeCom-Secret";
|
||||||
|
|
||||||
|
@Value("${jarvis.wecom.inbound-secret:}")
|
||||||
|
private String inboundSecret;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWeComInboundService weComInboundService;
|
||||||
|
@Resource
|
||||||
|
private IWeComInboundTraceService weComInboundTraceService;
|
||||||
|
@Resource
|
||||||
|
private WxSendWeComPushClient wxSendWeComPushClient;
|
||||||
|
|
||||||
|
@PostMapping("/inbound")
|
||||||
|
public AjaxResult inbound(
|
||||||
|
@RequestHeader(value = HEADER_SECRET, required = false) String secret,
|
||||||
|
@RequestBody WeComInboundRequest body) {
|
||||||
|
if (!StringUtils.hasText(inboundSecret) || !inboundSecret.equals(secret)) {
|
||||||
|
return AjaxResult.error("拒绝访问");
|
||||||
|
}
|
||||||
|
WeComInboundRequest req = body != null ? body : new WeComInboundRequest();
|
||||||
|
WeComInboundResult result = weComInboundService.handleInbound(req);
|
||||||
|
weComInboundTraceService.recordInbound(req, result.toTraceFullText());
|
||||||
|
Map<String, Object> data = new HashMap<>(4);
|
||||||
|
data.put("reply", result.getPassiveReply());
|
||||||
|
data.put("activePushCount", result.getActivePushContents().size());
|
||||||
|
wxSendWeComPushClient.scheduleActivePushes(req.getFromUserName(), result.getActivePushContents());
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.jarvis.domain.WeComInboundTrace;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComTestDataCleanRequest;
|
||||||
|
import com.ruoyi.jarvis.service.IWeComInboundTraceService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微 inbound 消息追踪查询
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/wecom/inboundTrace")
|
||||||
|
public class WeComInboundTraceController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IWeComInboundTraceService weComInboundTraceService;
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(WeComInboundTrace query) {
|
||||||
|
startPage();
|
||||||
|
List<WeComInboundTrace> list = weComInboundTraceService.selectWeComInboundTraceList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:list')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable Long id) {
|
||||||
|
return success(weComInboundTraceService.selectWeComInboundTraceById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:remove')")
|
||||||
|
@Log(title = "企微消息跟踪", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(weComInboundTraceService.deleteWeComInboundTraceByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理联调/测试数据:追踪表 + 可选 Redis 企微会话与 adhoc 队列
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:remove')")
|
||||||
|
@Log(title = "企微消息测试数据清理", businessType = BusinessType.CLEAN)
|
||||||
|
@PostMapping("/cleanTestData")
|
||||||
|
public AjaxResult cleanTestData(@RequestBody(required = false) WeComTestDataCleanRequest body) {
|
||||||
|
return success(weComInboundTraceService.cleanTestData(body));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
||||||
|
import com.ruoyi.jarvis.mapper.WeComShareLinkLogisticsJobMapper;
|
||||||
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
|
import com.ruoyi.jarvis.service.IWeComShareLinkLogisticsJobService;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
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 javax.annotation.Resource;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jarvis/wecom/shareLinkLogisticsJob")
|
||||||
|
public class WeComShareLinkLogisticsJobController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWeComShareLinkLogisticsJobService weComShareLinkLogisticsJobService;
|
||||||
|
@Resource
|
||||||
|
private ILogisticsService logisticsService;
|
||||||
|
@Resource
|
||||||
|
private WeComShareLinkLogisticsJobMapper weComShareLinkLogisticsJobMapper;
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(WeComShareLinkLogisticsJob query) {
|
||||||
|
startPage();
|
||||||
|
List<WeComShareLinkLogisticsJob> list = weComShareLinkLogisticsJobService.selectList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@GetMapping("/{jobKey}")
|
||||||
|
public AjaxResult getInfo(@PathVariable("jobKey") String jobKey) {
|
||||||
|
return success(weComShareLinkLogisticsJobService.selectByJobKey(jobKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:import')")
|
||||||
|
@PostMapping("/backfillFromInboundTrace")
|
||||||
|
public AjaxResult backfillFromInboundTrace() {
|
||||||
|
Map<String, Object> r = weComShareLinkLogisticsJobService.backfillImportedFromInboundTrace();
|
||||||
|
return success(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与订单列表「获取物流」一致:立即请求物流接口,有运单则推送分享链模板,并回写任务行。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@PostMapping("/fetchShareLinkManually")
|
||||||
|
public AjaxResult fetchShareLinkManually(@RequestBody Map<String, Object> body) {
|
||||||
|
if (body == null || body.get("jobKey") == null) {
|
||||||
|
return AjaxResult.error("jobKey 不能为空");
|
||||||
|
}
|
||||||
|
String jobKey = body.get("jobKey").toString().trim();
|
||||||
|
if (!StringUtils.hasText(jobKey)) {
|
||||||
|
return AjaxResult.error("jobKey 不能为空");
|
||||||
|
}
|
||||||
|
WeComShareLinkLogisticsJob job = weComShareLinkLogisticsJobService.selectByJobKey(jobKey);
|
||||||
|
if (job == null) {
|
||||||
|
return AjaxResult.error("任务不存在");
|
||||||
|
}
|
||||||
|
if ("CANCELLED".equalsIgnoreCase(job.getStatus())) {
|
||||||
|
return AjaxResult.error("任务已取消扫描,请先恢复或新建任务");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(job.getTrackingUrl())) {
|
||||||
|
return AjaxResult.error("该任务无物流短链");
|
||||||
|
}
|
||||||
|
String remark = job.getUserRemark() != null ? job.getUserRemark() : "";
|
||||||
|
String touser = job.getTouserPush() != null ? job.getTouserPush() : "";
|
||||||
|
Map<String, Object> data = logisticsService.adminFetchShareLinkLogisticsDebug(
|
||||||
|
job.getTrackingUrl(), remark, touser);
|
||||||
|
data.put("jobKey", jobKey);
|
||||||
|
|
||||||
|
int successAttempts = job.getScanAttempts() == null ? 1 : job.getScanAttempts() + 1;
|
||||||
|
String adhocNote = data.get("adhocNote") != null ? data.get("adhocNote").toString() : "";
|
||||||
|
String note = "manual:" + adhocNote;
|
||||||
|
if (Boolean.TRUE.equals(data.get("terminalSuccess"))) {
|
||||||
|
String wb = data.get("waybillNo") != null ? data.get("waybillNo").toString() : null;
|
||||||
|
weComShareLinkLogisticsJobMapper.updateByJobKey(jobKey, "PUSHED", note, successAttempts,
|
||||||
|
StringUtils.hasText(wb) ? wb : null);
|
||||||
|
} else {
|
||||||
|
/* 失败仍走自动队列时,不得垫高 scan_attempts,否则 Redis attempts 与定时 drain 上限错位,未超限也会被放弃 */
|
||||||
|
weComShareLinkLogisticsJobMapper.updateByJobKey(jobKey, "WAITING", note, job.getScanAttempts(), null);
|
||||||
|
WeComShareLinkLogisticsJob refreshed = weComShareLinkLogisticsJobService.selectByJobKey(jobKey);
|
||||||
|
if (refreshed != null) {
|
||||||
|
logisticsService.pushShareLinkJobToRedis(refreshed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动执行一轮与定时任务相同的 Redis 待队列弹出(条数上限同 adhoc-pending-batch-size)。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@PostMapping("/drainPendingQueueOnce")
|
||||||
|
public AjaxResult drainPendingQueueOnce() {
|
||||||
|
int n = logisticsService.drainPendingShareLinkQueue();
|
||||||
|
Map<String, Object> r = new LinkedHashMap<>();
|
||||||
|
r.put("processedFromQueue", n);
|
||||||
|
r.put("hint", "为单次弹栈处理条数;每项内部仍可能因未出单重新入队");
|
||||||
|
return AjaxResult.success(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单取消等:标记为 CANCELLED,不再被定时对账入队;队列弹出时也会跳过物流请求与推送。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@PostMapping("/cancel")
|
||||||
|
public AjaxResult cancel(@RequestBody Map<String, Object> body) {
|
||||||
|
if (body == null || body.get("jobKey") == null) {
|
||||||
|
return AjaxResult.error("jobKey 不能为空");
|
||||||
|
}
|
||||||
|
String jobKey = body.get("jobKey").toString().trim();
|
||||||
|
if (!StringUtils.hasText(jobKey)) {
|
||||||
|
return AjaxResult.error("jobKey 不能为空");
|
||||||
|
}
|
||||||
|
WeComShareLinkLogisticsJob job = weComShareLinkLogisticsJobService.selectByJobKey(jobKey);
|
||||||
|
if (job == null) {
|
||||||
|
return AjaxResult.error("任务不存在");
|
||||||
|
}
|
||||||
|
if ("CANCELLED".equalsIgnoreCase(job.getStatus())) {
|
||||||
|
return AjaxResult.success("已是取消状态");
|
||||||
|
}
|
||||||
|
String extra = body.get("lastNote") != null ? body.get("lastNote").toString().trim() : "";
|
||||||
|
String note = "manual_cancel";
|
||||||
|
if (StringUtils.hasText(extra)) {
|
||||||
|
note = note + "|" + extra;
|
||||||
|
}
|
||||||
|
if (note.length() > 500) {
|
||||||
|
note = note.substring(0, 500) + "…";
|
||||||
|
}
|
||||||
|
weComShareLinkLogisticsJobMapper.updateByJobKey(jobKey, "CANCELLED", note, null, null);
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物理删除任务行(Redis 中已存在的同 jobKey 队列项仍可能被弹出,但会因库中无行而跳过扫描)。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('jarvis:wecom:shareLinkLog:list')")
|
||||||
|
@DeleteMapping("/{jobKey}")
|
||||||
|
public AjaxResult remove(@PathVariable("jobKey") String jobKey) {
|
||||||
|
if (!StringUtils.hasText(jobKey)) {
|
||||||
|
return AjaxResult.error("jobKey 不能为空");
|
||||||
|
}
|
||||||
|
weComShareLinkLogisticsJobMapper.deleteByJobKey(jobKey.trim());
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧回调 /wps365-callback:平台校验多为「GET、无 code」须直接 200;真实授权带 code/error 时再 302 到 /kdocs-callback。
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@RestController
|
||||||
|
public class Wps365ToKdocsCallbackRedirectController {
|
||||||
|
|
||||||
|
@Anonymous
|
||||||
|
@GetMapping("/wps365-callback")
|
||||||
|
public ResponseEntity<?> wps365Get(HttpServletRequest request,
|
||||||
|
@RequestParam(value = "code", required = false) String code,
|
||||||
|
@RequestParam(value = "error", required = false) String error) {
|
||||||
|
return handleWps365(request, code, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分校验或代理可能使用 POST。
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@PostMapping("/wps365-callback")
|
||||||
|
public ResponseEntity<?> wps365Post(HttpServletRequest request,
|
||||||
|
@RequestParam(value = "code", required = false) String code,
|
||||||
|
@RequestParam(value = "error", required = false) String error) {
|
||||||
|
return handleWps365(request, code, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<?> handleWps365(HttpServletRequest request, String code, String error) {
|
||||||
|
if (StringUtils.isBlank(code) && StringUtils.isBlank(error)) {
|
||||||
|
String jsonBody = readJsonBodyIfPost(request);
|
||||||
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, jsonBody);
|
||||||
|
}
|
||||||
|
String q = request.getQueryString();
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setLocation(URI.create(KdocsCallbackUrlBuilder.absoluteKdocsCallback(request, q)));
|
||||||
|
return new ResponseEntity<>(null, headers, HttpStatus.FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readJsonBodyIfPost(HttpServletRequest request) {
|
||||||
|
if (!"POST".equalsIgnoreCase(request.getMethod())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String ct = StringUtils.defaultString(request.getContentType()).toLowerCase();
|
||||||
|
if (!ct.contains("application/json")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader r = request.getReader()) {
|
||||||
|
char[] buf = new char[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = r.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
@@ -12,6 +13,7 @@ import com.ruoyi.common.utils.http.HttpUtils;
|
|||||||
import com.ruoyi.framework.web.domain.Server;
|
import com.ruoyi.framework.web.domain.Server;
|
||||||
import com.ruoyi.jarvis.service.ILogisticsService;
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
import com.ruoyi.jarvis.service.IWxSendService;
|
import com.ruoyi.jarvis.service.IWxSendService;
|
||||||
|
import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -32,6 +34,9 @@ public class ServerController
|
|||||||
@Resource
|
@Resource
|
||||||
private IWxSendService wxSendService;
|
private IWxSendService wxSendService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WxSendGoofishNotifyClient wxSendGoofishNotifyClient;
|
||||||
|
|
||||||
/** Ollama 服务地址,用于健康检查 */
|
/** Ollama 服务地址,用于健康检查 */
|
||||||
@Value("${jarvis.ollama.base-url:http://192.168.8.34:11434}")
|
@Value("${jarvis.ollama.base-url:http://192.168.8.34:11434}")
|
||||||
private String ollamaBaseUrl;
|
private String ollamaBaseUrl;
|
||||||
@@ -72,23 +77,23 @@ public class ServerController
|
|||||||
healthMap.put("logistics", logisticsMap);
|
healthMap.put("logistics", logisticsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 微信推送服务健康检测
|
// 微信推送:不在此自动下发消息,仅展示配置地址;真实检测见 POST /monitor/server/health/wx-send-test
|
||||||
try {
|
Map<String, Object> wxSendMap = new HashMap<>();
|
||||||
IWxSendService.HealthCheckResult wxSendHealth = wxSendService.checkHealth();
|
wxSendMap.put("manualOnly", true);
|
||||||
Map<String, Object> wxSendMap = new HashMap<>();
|
wxSendMap.put("healthy", null);
|
||||||
wxSendMap.put("healthy", wxSendHealth.isHealthy());
|
wxSendMap.put("status", "未检测");
|
||||||
wxSendMap.put("status", wxSendHealth.getStatus());
|
wxSendMap.put("message", "点击「测试」将发送一条健康检查消息(会真实推送到微信)");
|
||||||
wxSendMap.put("message", wxSendHealth.getMessage());
|
wxSendMap.put("serviceUrl", wxSendService.getHealthCheckServiceUrl());
|
||||||
wxSendMap.put("serviceUrl", wxSendHealth.getServiceUrl());
|
healthMap.put("wxSend", wxSendMap);
|
||||||
healthMap.put("wxSend", wxSendMap);
|
|
||||||
} catch (Exception e) {
|
// 企微闲鱼通知:仅展示接口地址;真实检测见 POST /monitor/server/health/goofish-notify-test
|
||||||
Map<String, Object> wxSendMap = new HashMap<>();
|
Map<String, Object> goofishMap = new HashMap<>();
|
||||||
wxSendMap.put("healthy", false);
|
goofishMap.put("manualOnly", true);
|
||||||
wxSendMap.put("status", "异常");
|
goofishMap.put("healthy", null);
|
||||||
wxSendMap.put("message", "健康检测异常: " + e.getMessage());
|
goofishMap.put("status", "未检测");
|
||||||
wxSendMap.put("serviceUrl", "");
|
goofishMap.put("message", "点击「测试」将经 wxSend 向企微闲鱼应用发送一条测试文本");
|
||||||
healthMap.put("wxSend", wxSendMap);
|
goofishMap.put("serviceUrl", wxSendGoofishNotifyClient.getGoofishPushEndpointDisplay());
|
||||||
}
|
healthMap.put("goofishNotify", goofishMap);
|
||||||
|
|
||||||
// Ollama 服务健康检测(调试用)
|
// Ollama 服务健康检测(调试用)
|
||||||
try {
|
try {
|
||||||
@@ -116,6 +121,54 @@ public class ServerController
|
|||||||
return AjaxResult.success(healthMap);
|
return AjaxResult.success(healthMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动测试微信推送(会真实下发一条消息)
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('monitor:server:list')")
|
||||||
|
@PostMapping("/health/wx-send-test")
|
||||||
|
public AjaxResult testWxSendHealth() {
|
||||||
|
try {
|
||||||
|
IWxSendService.HealthCheckResult r = wxSendService.checkHealth();
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("manualOnly", true);
|
||||||
|
m.put("healthy", r.isHealthy());
|
||||||
|
m.put("status", r.getStatus());
|
||||||
|
m.put("message", r.getMessage());
|
||||||
|
m.put("serviceUrl", r.getServiceUrl());
|
||||||
|
return AjaxResult.success(m);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("manualOnly", true);
|
||||||
|
m.put("healthy", false);
|
||||||
|
m.put("status", "异常");
|
||||||
|
m.put("message", "检测异常: " + e.getMessage());
|
||||||
|
m.put("serviceUrl", wxSendService.getHealthCheckServiceUrl());
|
||||||
|
return AjaxResult.success(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动测试企微闲鱼通知(经 wxSend POST /wx/send/goofish,与 /send/pdd 相同 vanToken + title/text/touser)
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('monitor:server:list')")
|
||||||
|
@PostMapping("/health/goofish-notify-test")
|
||||||
|
public AjaxResult testGoofishNotify() {
|
||||||
|
String err = wxSendGoofishNotifyClient.testGoofishNotify();
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("manualOnly", true);
|
||||||
|
m.put("serviceUrl", wxSendGoofishNotifyClient.getGoofishPushEndpointDisplay());
|
||||||
|
if (err == null) {
|
||||||
|
m.put("healthy", true);
|
||||||
|
m.put("status", "正常");
|
||||||
|
m.put("message", "闲鱼通知测试消息已发送");
|
||||||
|
} else {
|
||||||
|
m.put("healthy", false);
|
||||||
|
m.put("status", "异常");
|
||||||
|
m.put("message", err);
|
||||||
|
}
|
||||||
|
return AjaxResult.success(m);
|
||||||
|
}
|
||||||
|
|
||||||
private void putOllamaUnhealthy(Map<String, Object> healthMap, String url, String message) {
|
private void putOllamaUnhealthy(Map<String, Object> healthMap, String url, String message) {
|
||||||
Map<String, Object> ollamaMap = new HashMap<>();
|
Map<String, Object> ollamaMap = new HashMap<>();
|
||||||
ollamaMap.put("healthy", false);
|
ollamaMap.put("healthy", false);
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
package com.ruoyi.web.controller.system;
|
package com.ruoyi.web.controller.system;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
|
||||||
import com.ruoyi.jarvis.domain.OrderRows;
|
import com.ruoyi.jarvis.domain.OrderRows;
|
||||||
|
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
|
||||||
|
import com.ruoyi.jarvis.service.impl.GroupRebateExcelImportService;
|
||||||
import com.ruoyi.jarvis.service.IOrderRowsService;
|
import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
@@ -16,6 +26,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
|||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO;
|
import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption;
|
||||||
|
import com.ruoyi.jarvis.service.IJDOrderProfitService;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
import com.ruoyi.jarvis.service.IInstructionService;
|
import com.ruoyi.jarvis.service.IInstructionService;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
@@ -32,13 +44,23 @@ public class JDOrderListController extends BaseController
|
|||||||
{
|
{
|
||||||
|
|
||||||
private final IJDOrderService jdOrderService;
|
private final IJDOrderService jdOrderService;
|
||||||
|
private final IJDOrderProfitService jdOrderProfitService;
|
||||||
private final IOrderRowsService orderRowsService;
|
private final IOrderRowsService orderRowsService;
|
||||||
private final IInstructionService instructionService;
|
private final IInstructionService instructionService;
|
||||||
|
private final GroupRebateExcelImportService groupRebateExcelImportService;
|
||||||
|
private final IGroupRebateExcelUploadService groupRebateExcelUploadService;
|
||||||
|
|
||||||
public JDOrderListController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService, IInstructionService instructionService) {
|
public JDOrderListController(IJDOrderService jdOrderService, IJDOrderProfitService jdOrderProfitService,
|
||||||
|
IOrderRowsService orderRowsService,
|
||||||
|
IInstructionService instructionService,
|
||||||
|
GroupRebateExcelImportService groupRebateExcelImportService,
|
||||||
|
IGroupRebateExcelUploadService groupRebateExcelUploadService) {
|
||||||
this.jdOrderService = jdOrderService;
|
this.jdOrderService = jdOrderService;
|
||||||
|
this.jdOrderProfitService = jdOrderProfitService;
|
||||||
this.orderRowsService = orderRowsService;
|
this.orderRowsService = orderRowsService;
|
||||||
this.instructionService = instructionService;
|
this.instructionService = instructionService;
|
||||||
|
this.groupRebateExcelImportService = groupRebateExcelImportService;
|
||||||
|
this.groupRebateExcelUploadService = groupRebateExcelUploadService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +97,17 @@ public class JDOrderListController extends BaseController
|
|||||||
query.getParams().put("orderSearch", orderSearch.trim());
|
query.getParams().put("orderSearch", orderSearch.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String rebateRemarkAbnormal = request.getParameter("rebateRemarkHasAbnormal");
|
||||||
|
if (rebateRemarkAbnormal != null && !rebateRemarkAbnormal.isEmpty()) {
|
||||||
|
query.setRebateRemarkHasAbnormal(Integer.valueOf(rebateRemarkAbnormal));
|
||||||
|
}
|
||||||
|
if ("true".equalsIgnoreCase(request.getParameter("hasRebateRemark"))) {
|
||||||
|
query.getParams().put("hasRebateRemark", true);
|
||||||
|
}
|
||||||
|
if ("true".equalsIgnoreCase(request.getParameter("rebateWithoutUploadLink"))) {
|
||||||
|
query.getParams().put("rebateWithoutUploadLink", true);
|
||||||
|
}
|
||||||
|
|
||||||
java.util.List<JDOrder> list;
|
java.util.List<JDOrder> list;
|
||||||
if (orderBy != null && !orderBy.isEmpty()) {
|
if (orderBy != null && !orderBy.isEmpty()) {
|
||||||
// 设置排序参数
|
// 设置排序参数
|
||||||
@@ -125,6 +158,97 @@ public class JDOrderListController extends BaseController
|
|||||||
return dataTable;
|
return dataTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷录单页:型号下拉数据;每型号取 jd_order 主键最大的一条的付款与后返(通常即最近落库单)
|
||||||
|
*/
|
||||||
|
@GetMapping("/quickRecord/modelOptions")
|
||||||
|
public AjaxResult quickRecordModelOptions() {
|
||||||
|
List<QuickRecordModelOption> options = jdOrderService.selectQuickRecordModelOptions();
|
||||||
|
return AjaxResult.success(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入跟团返现类 Excel:按「单号/订单号」匹配系统订单,将「是否返现」「总共返现」等写入后返备注(可多次导入累加);文件落盘并记上传记录。
|
||||||
|
*/
|
||||||
|
@Log(title = "JD订单后返表导入", businessType = BusinessType.IMPORT)
|
||||||
|
@PostMapping("/importGroupRebateExcel")
|
||||||
|
public AjaxResult importGroupRebateExcel(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value = "documentTitle", required = false) String documentTitle) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> data = groupRebateExcelImportService.importExcel(file, documentTitle);
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("导入失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量导入后返表:请求内按顺序处理多个文件,前端仅需一次提交;结果概要返回,明细见上传记录。
|
||||||
|
*/
|
||||||
|
@Log(title = "JD订单后返表批量导入", businessType = BusinessType.IMPORT)
|
||||||
|
@PostMapping("/importGroupRebateExcelBatch")
|
||||||
|
public AjaxResult importGroupRebateExcelBatch(@RequestParam("files") MultipartFile[] files,
|
||||||
|
@RequestParam(value = "documentTitle", required = false) String documentTitle) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> data = groupRebateExcelImportService.importExcelBatch(files, documentTitle);
|
||||||
|
if (Boolean.FALSE.equals(data.get("success"))) {
|
||||||
|
return AjaxResult.error((String) data.get("message"));
|
||||||
|
}
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("批量导入失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后返表上传记录(分页)
|
||||||
|
*/
|
||||||
|
@GetMapping("/groupRebateUpload/list")
|
||||||
|
public TableDataInfo groupRebateUploadList(GroupRebateExcelUpload query, HttpServletRequest request) {
|
||||||
|
startPage();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
List<GroupRebateExcelUpload> list = groupRebateExcelUploadService.selectGroupRebateExcelUploadList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一条后返表上传记录,并撤销写入订单的后返备注(依赖 uploadRecordId / affectedOrderIds;历史导入可能仅删记录)
|
||||||
|
*/
|
||||||
|
@Log(title = "后返表上传记录", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/groupRebateUpload/{id}")
|
||||||
|
public AjaxResult deleteGroupRebateUpload(@PathVariable("id") Long id) {
|
||||||
|
return AjaxResult.success(groupRebateExcelImportService.deleteUploadRecord(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新下载已上传的后返表原件(与通用 download 一致,使用 POST + blob)
|
||||||
|
*/
|
||||||
|
@Log(title = "后返表上传记录下载", businessType = BusinessType.EXPORT)
|
||||||
|
@PostMapping("/groupRebateUpload/download/{id}")
|
||||||
|
public void downloadGroupRebateUpload(@PathVariable("id") Long id, HttpServletResponse response) throws Exception {
|
||||||
|
GroupRebateExcelUpload rec = groupRebateExcelUploadService.selectGroupRebateExcelUploadById(id);
|
||||||
|
if (rec == null || StringUtils.isEmpty(rec.getFilePath())) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = GroupRebateExcelImportService.resolveDiskFile(rec.getFilePath());
|
||||||
|
if (!file.isFile()) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downloadName = StringUtils.isNotEmpty(rec.getOriginalFilename()) ? rec.getOriginalFilename() : file.getName();
|
||||||
|
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||||
|
FileUtils.setAttachmentResponseHeader(response, downloadName);
|
||||||
|
FileUtils.writeBytes(file.getAbsolutePath(), response.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出JD订单列表
|
* 导出JD订单列表
|
||||||
*/
|
*/
|
||||||
@@ -174,6 +298,8 @@ public class JDOrderListController extends BaseController
|
|||||||
@PutMapping
|
@PutMapping
|
||||||
public AjaxResult edit(@RequestBody JDOrder jdOrder)
|
public AjaxResult edit(@RequestBody JDOrder jdOrder)
|
||||||
{
|
{
|
||||||
|
jdOrderProfitService.recalculate(jdOrder);
|
||||||
|
jdOrder.getParams().put("applyProfitFields", Boolean.TRUE);
|
||||||
return toAjax(jdOrderService.updateJDOrder(jdOrder));
|
return toAjax(jdOrderService.updateJDOrder(jdOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,9 +391,75 @@ public class JDOrderListController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一次性批量更新历史订单:将赔付金额大于0的订单标记为后返到账
|
* 列表刷新后:对利润未手动的订单按规则重算,仅结果变化时落库(不解除售价/利润锁定)
|
||||||
* 此方法只应执行一次,用于处理历史数据
|
|
||||||
*/
|
*/
|
||||||
|
@Log(title = "JD订单同步自动利润", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/tools/sync-auto-profit")
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public AjaxResult syncAutoProfit(@RequestBody(required = false) Map<String, Object> body) {
|
||||||
|
if (body == null || !body.containsKey("ids")) {
|
||||||
|
return AjaxResult.error("请传入 ids 数组");
|
||||||
|
}
|
||||||
|
Object raw = body.get("ids");
|
||||||
|
if (!(raw instanceof List)) {
|
||||||
|
return AjaxResult.error("ids 须为数组");
|
||||||
|
}
|
||||||
|
List<?> idList = (List<?>) raw;
|
||||||
|
if (idList.isEmpty()) {
|
||||||
|
Map<String, Object> empty = new HashMap<>(2);
|
||||||
|
empty.put("updated", 0);
|
||||||
|
return AjaxResult.success(empty);
|
||||||
|
}
|
||||||
|
List<Long> ids = new ArrayList<>(idList.size());
|
||||||
|
for (Object o : idList) {
|
||||||
|
if (o == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ids.add(((Number) o).longValue());
|
||||||
|
}
|
||||||
|
int n = jdOrderProfitService.syncAutoProfitIfChanged(ids);
|
||||||
|
Map<String, Object> data = new HashMap<>(2);
|
||||||
|
data.put("updated", n);
|
||||||
|
return AjaxResult.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 ID 批量重算售价(自动从型号配置回填)与利润(清除手动锁定后按规则计算)
|
||||||
|
*/
|
||||||
|
@Log(title = "JD订单批量重算利润", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/tools/recalc-profit")
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public AjaxResult recalcProfitBatch(@RequestBody(required = false) Map<String, Object> body) {
|
||||||
|
if (body == null || !body.containsKey("ids")) {
|
||||||
|
return AjaxResult.error("请传入 ids 数组");
|
||||||
|
}
|
||||||
|
Object raw = body.get("ids");
|
||||||
|
if (!(raw instanceof List)) {
|
||||||
|
return AjaxResult.error("ids 须为数组");
|
||||||
|
}
|
||||||
|
List<?> idList = (List<?>) raw;
|
||||||
|
if (idList.isEmpty()) {
|
||||||
|
return AjaxResult.error("ids 不能为空");
|
||||||
|
}
|
||||||
|
int affected = 0;
|
||||||
|
for (Object o : idList) {
|
||||||
|
if (o == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long id = ((Number) o).longValue();
|
||||||
|
JDOrder order = jdOrderService.selectJDOrderById(id);
|
||||||
|
if (order == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
order.setProfitManual(0);
|
||||||
|
order.setSellingPriceManual(0);
|
||||||
|
jdOrderProfitService.recalculate(order);
|
||||||
|
order.getParams().put("applyProfitFields", Boolean.TRUE);
|
||||||
|
affected += jdOrderService.updateJDOrder(order);
|
||||||
|
}
|
||||||
|
return AjaxResult.success("已更新 " + affected + " 条订单的售价/利润字段");
|
||||||
|
}
|
||||||
|
|
||||||
@Log(title = "批量标记后返到账", businessType = BusinessType.UPDATE)
|
@Log(title = "批量标记后返到账", businessType = BusinessType.UPDATE)
|
||||||
@RequestMapping(value = "/tools/batch-mark-rebate-received", method = {RequestMethod.POST, RequestMethod.GET})
|
@RequestMapping(value = "/tools/batch-mark-rebate-received", method = {RequestMethod.POST, RequestMethod.GET})
|
||||||
public AjaxResult batchMarkRebateReceivedForCompensation() {
|
public AjaxResult batchMarkRebateReceivedForCompensation() {
|
||||||
@@ -309,6 +501,17 @@ public class JDOrderListController extends BaseController
|
|||||||
query.getParams().put("orderSearch", orderSearch.trim());
|
query.getParams().put("orderSearch", orderSearch.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String rebateRemarkAbnormal = request.getParameter("rebateRemarkHasAbnormal");
|
||||||
|
if (rebateRemarkAbnormal != null && !rebateRemarkAbnormal.isEmpty()) {
|
||||||
|
query.setRebateRemarkHasAbnormal(Integer.valueOf(rebateRemarkAbnormal));
|
||||||
|
}
|
||||||
|
if ("true".equalsIgnoreCase(request.getParameter("hasRebateRemark"))) {
|
||||||
|
query.getParams().put("hasRebateRemark", true);
|
||||||
|
}
|
||||||
|
if ("true".equalsIgnoreCase(request.getParameter("rebateWithoutUploadLink"))) {
|
||||||
|
query.getParams().put("rebateWithoutUploadLink", true);
|
||||||
|
}
|
||||||
|
|
||||||
// 处理其他查询参数
|
// 处理其他查询参数
|
||||||
if (query.getRemark() != null && !query.getRemark().trim().isEmpty()) {
|
if (query.getRemark() != null && !query.getRemark().trim().isEmpty()) {
|
||||||
query.setRemark(query.getRemark().trim());
|
query.setRemark(query.getRemark().trim());
|
||||||
@@ -319,6 +522,9 @@ public class JDOrderListController extends BaseController
|
|||||||
if (query.getModelNumber() != null && !query.getModelNumber().trim().isEmpty()) {
|
if (query.getModelNumber() != null && !query.getModelNumber().trim().isEmpty()) {
|
||||||
query.setModelNumber(query.getModelNumber().trim());
|
query.setModelNumber(query.getModelNumber().trim());
|
||||||
}
|
}
|
||||||
|
if (query.getModelNumberExclude() != null && !query.getModelNumberExclude().trim().isEmpty()) {
|
||||||
|
query.setModelNumberExclude(query.getModelNumberExclude().trim());
|
||||||
|
}
|
||||||
if (query.getBuyer() != null && !query.getBuyer().trim().isEmpty()) {
|
if (query.getBuyer() != null && !query.getBuyer().trim().isEmpty()) {
|
||||||
query.setBuyer(query.getBuyer().trim());
|
query.setBuyer(query.getBuyer().trim());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,11 +200,51 @@ jarvis:
|
|||||||
# 物流接口服务地址
|
# 物流接口服务地址
|
||||||
logistics:
|
logistics:
|
||||||
base-url: http://192.168.8.88:5001
|
base-url: http://192.168.8.88:5001
|
||||||
|
# 同机多进程多端口时配置逗号分隔列表;非空时仅按下列地址轮询,不再使用 base-url
|
||||||
|
base-urls: http://192.168.8.88:5001,http://192.168.8.88:5002,http://192.168.8.88:5003
|
||||||
fetch-path: /fetch_logistics
|
fetch-path: /fetch_logistics
|
||||||
health-path: /health
|
health-path: /health
|
||||||
|
# 每次定时任务最多处理多少条企微分享链待队列(RPUSH 入队、LPOP 出队)
|
||||||
|
adhoc-pending-batch-size: 50
|
||||||
|
# 物流扫描(LogisticsScanTask):轮询 JD 单拉运单 + drain 分享链队列
|
||||||
|
scan:
|
||||||
|
cron: "0 */20 * * * ?"
|
||||||
|
order-delay-ms: 250
|
||||||
|
# 0=不限制;例如 40 可控制单轮最长耗时(余下下轮再扫)
|
||||||
|
max-orders-per-round: 0
|
||||||
# 获取评论接口服务地址(后端转发,避免前端跨域)
|
# 获取评论接口服务地址(后端转发,避免前端跨域)
|
||||||
fetch-comments:
|
fetch-comments:
|
||||||
base-url: http://192.168.8.60:5008
|
base-url: http://192.168.8.60:5008
|
||||||
|
# 企微经 wxSend 调用本接口时校验(须与 wxSend 配置一致)
|
||||||
|
wecom:
|
||||||
|
inbound-secret: jarvis_wecom_bridge_change_me
|
||||||
|
# wxSend 根地址(无尾斜杠),用于 F 录单等第二条起主动推送;与 wxSend server.port 一致
|
||||||
|
wxsend-base-url: http://127.0.0.1:36699
|
||||||
|
# 须与 wxSend jarvis.wecom.push-secret 一致(Header X-WxSend-WeCom-Push-Secret)
|
||||||
|
push-secret: jarvis_wecom_push_change_me
|
||||||
|
# 与 /wx/send/pdd、/wx/send/goofish 请求头 vanToken 一致(wxSend TokenUtil)
|
||||||
|
wxsend-van-token: super_token_b62190c26
|
||||||
|
# 接收企微通知的成员 UserID,多个逗号或 |;留空则不推送
|
||||||
|
goofish-notify-touser: "LinPinFan"
|
||||||
|
# 多轮会话:与 JDUtil interaction_state 类似,TTL 与空闲超时(分钟)
|
||||||
|
session-ttl-minutes: 30
|
||||||
|
session-idle-timeout-minutes: 30
|
||||||
|
session-sweep-ms: 60000
|
||||||
|
# 企微「开」/「慢开」+ 手机号:POST body 含 text(手机号)与 bot;响应 reply_text 被动回复用户
|
||||||
|
phone-forward:
|
||||||
|
enabled: true
|
||||||
|
base-url: http://192.168.8.60:18080
|
||||||
|
path: /v1/forward
|
||||||
|
connect-timeout-ms: 8000
|
||||||
|
# wait_reply 时服务端会等多条 Bot 回复,宜适当加大
|
||||||
|
read-timeout-ms: 120000
|
||||||
|
wait-reply: true
|
||||||
|
# 多台企微线程同时触发时串行调用 tg_bridge;排队超过该毫秒则提示「正忙」(0 表示一直等到上一条结束)
|
||||||
|
lock-acquire-timeout-ms: 180000
|
||||||
|
# 连续失败后熔断,不再发起 HTTP(与 tg_bridge 侧熔断互不替代)
|
||||||
|
circuit-failure-threshold: 5
|
||||||
|
circuit-open-ms: 120000
|
||||||
|
# reply_take_nth:仅「开」用 2;「慢开」由 tg_bridge reply_adaptive_skip_middle_ad 在 2/3 条间自适应
|
||||||
# Ollama 大模型服务(监控健康度调试用)
|
# Ollama 大模型服务(监控健康度调试用)
|
||||||
ollama:
|
ollama:
|
||||||
base-url: http://192.168.8.34:11434
|
base-url: http://192.168.8.34:11434
|
||||||
@@ -231,5 +271,32 @@ tencent:
|
|||||||
# 刷新Token地址(用于通过refresh_token刷新access_token)
|
# 刷新Token地址(用于通过refresh_token刷新access_token)
|
||||||
refresh-token-url: https://docs.qq.com/oauth/v2/token
|
refresh-token-url: https://docs.qq.com/oauth/v2/token
|
||||||
|
|
||||||
|
# 闲管家订单:RocketMQ(配置后订单推送走 MQ;不配则走线程池异步)
|
||||||
|
#rocketmq:
|
||||||
|
# name-server: 127.0.0.1:9876
|
||||||
|
# producer:
|
||||||
|
# group: jarvis-goofish-producer
|
||||||
|
# send-message-timeout: 3000
|
||||||
|
|
||||||
|
jarvis:
|
||||||
|
goofish-order:
|
||||||
|
mq-topic: jarvis-goofish-erp-order
|
||||||
|
consumer-group: jarvis-goofish-order-consumer
|
||||||
|
pull-lookback-hours: 72
|
||||||
|
pull-cron: "0 * * * * ?"
|
||||||
|
auto-ship-cron: "0 2/10 * * * ?"
|
||||||
|
# 订单列表:每页条数(最大 100)
|
||||||
|
pull-page-size: 100
|
||||||
|
# 每授权单次最大页数(最大 100;与 page_size 乘积勿超 10000)
|
||||||
|
pull-max-pages-per-shop: 100
|
||||||
|
# 全量拉单按 update_time 分段(秒),默认 7 天(且不超过 pull-max-update-time-range-seconds)
|
||||||
|
pull-time-chunk-seconds: 604800
|
||||||
|
# 单次列表请求 update_time 最大跨度(秒),须满足平台「6个月内」;默认 180 天
|
||||||
|
pull-max-update-time-range-seconds: 15552000
|
||||||
|
# 全量拉单起点:距今多少天(默认约 3 年)
|
||||||
|
pull-full-history-days: 1095
|
||||||
|
# true=仅拉 auto-ship-order-statuses(省调用,其它状态依赖推送);false=时间窗内全状态(推荐,与本地对齐)
|
||||||
|
pull-list-only-auto-ship-statuses: false
|
||||||
|
auto-ship-batch-size: 20
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -200,15 +200,42 @@ jarvis:
|
|||||||
# 物流接口服务地址
|
# 物流接口服务地址
|
||||||
logistics:
|
logistics:
|
||||||
base-url: http://127.0.0.1:5001
|
base-url: http://127.0.0.1:5001
|
||||||
|
# 同机多进程多端口时配置逗号分隔列表;非空时仅按下列地址轮询,不再使用 base-url
|
||||||
|
base-urls: http://127.0.0.1:5001,http://127.0.0.1:5002,http://127.0.0.1:5003
|
||||||
fetch-path: /fetch_logistics
|
fetch-path: /fetch_logistics
|
||||||
health-path: /health
|
health-path: /health
|
||||||
|
adhoc-pending-batch-size: 50
|
||||||
|
scan:
|
||||||
|
cron: "0 */20 * * * ?"
|
||||||
|
order-delay-ms: 250
|
||||||
|
max-orders-per-round: 0
|
||||||
# 获取评论接口服务地址(后端转发)
|
# 获取评论接口服务地址(后端转发)
|
||||||
fetch-comments:
|
fetch-comments:
|
||||||
base-url: http://192.168.8.60:5008
|
base-url: http://192.168.8.60:5008
|
||||||
|
wecom:
|
||||||
|
inbound-secret: jarvis_wecom_bridge_change_me
|
||||||
|
wxsend-base-url: https://wxts.van333.cn
|
||||||
|
push-secret: jarvis_wecom_push_change_me
|
||||||
|
wxsend-van-token: super_token_b62190c26
|
||||||
|
goofish-notify-touser: "LinPingFan"
|
||||||
|
session-ttl-minutes: 30
|
||||||
|
session-idle-timeout-minutes: 30
|
||||||
|
session-sweep-ms: 60000
|
||||||
|
phone-forward:
|
||||||
|
enabled: true
|
||||||
|
base-url: http://192.168.8.60:18080
|
||||||
|
path: /v1/forward
|
||||||
|
connect-timeout-ms: 8000
|
||||||
|
read-timeout-ms: 120000
|
||||||
|
wait-reply: true
|
||||||
|
lock-acquire-timeout-ms: 180000
|
||||||
|
circuit-failure-threshold: 5
|
||||||
|
circuit-open-ms: 120000
|
||||||
|
# 「开」取第 2 条;「慢开」由桥接自适应第 2/3 条
|
||||||
# Ollama 大模型服务(监控健康度调试用)
|
# Ollama 大模型服务(监控健康度调试用)
|
||||||
ollama:
|
ollama:
|
||||||
base-url: http://192.168.8.34:11434
|
base-url: http://192.168.8.34:11434
|
||||||
model: qwen3.5:9b
|
model: qwen3.5:9b-32k
|
||||||
# 腾讯文档开放平台配置
|
# 腾讯文档开放平台配置
|
||||||
# 文档地址:https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html
|
# 文档地址:https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html
|
||||||
tencent:
|
tencent:
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ tencent:
|
|||||||
kdocs:
|
kdocs:
|
||||||
api-host: https://developer.kdocs.cn
|
api-host: https://developer.kdocs.cn
|
||||||
# 在开发者后台创建应用后填写 app_id / app_key
|
# 在开发者后台创建应用后填写 app_id / app_key
|
||||||
app-id: ""
|
app-id: AK20260114NNQJKV
|
||||||
app-key: ""
|
app-key: 4c58bc1642e5e8fa731f75af9370496a
|
||||||
# 与后台登记的回调一致,建议使用独立路径(勿被前端路由拦截)
|
# 与后台登记的回调一致,建议使用独立路径(勿被前端路由拦截)
|
||||||
redirect-uri: https://jarvis.van333.cn/kdocs-callback
|
redirect-uri: https://jarvis.van333.cn/kdocs-callback
|
||||||
# 逗号分隔,须与应用申请权限一致:https://developer.kdocs.cn/server/guide/permission.html
|
# 逗号分隔,须与应用申请权限一致:https://developer.kdocs.cn/server/guide/permission.html
|
||||||
|
|||||||
114
ruoyi-admin/src/main/resources/sql/erp_goofish_init.sql
Normal file
114
ruoyi-admin/src/main/resources/sql/erp_goofish_init.sql
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
-- 闲管家开放平台:应用配置中心 + ERP 订单落库(执行前请备份)
|
||||||
|
-- 说明:菜单需在「系统管理-菜单管理」中自行新增,组件路径示例:
|
||||||
|
-- 配置中心 system/goofish/erpOpenConfig/index
|
||||||
|
-- 订单跟踪 system/goofish/erpGoofishOrder/index
|
||||||
|
-- 变更日志(跨单排查,对接 GET /jarvis/erpGoofishOrder/eventLog/list)system/goofish/erpGoofishEventLog/index
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_open_config (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
app_key varchar(64) NOT NULL COMMENT '开放平台 AppKey(appid)',
|
||||||
|
app_secret varchar(128) NOT NULL COMMENT '开放平台 AppSecret',
|
||||||
|
xy_user_name varchar(128) DEFAULT NULL COMMENT '默认闲鱼会员名(展示)',
|
||||||
|
express_code varchar(64) DEFAULT NULL COMMENT '发货用快递公司编码(日日顺物流在官方列表中为 rrs,以「查询快递公司」接口为准)',
|
||||||
|
express_name varchar(64) DEFAULT NULL COMMENT '快递公司名称(展示)',
|
||||||
|
status char(1) NOT NULL DEFAULT '0' COMMENT '0正常 1停用',
|
||||||
|
order_num int(11) NOT NULL DEFAULT 0 COMMENT '排序(小优先)',
|
||||||
|
create_by varchar(64) DEFAULT '',
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
update_by varchar(64) DEFAULT '',
|
||||||
|
update_time datetime DEFAULT NULL,
|
||||||
|
remark varchar(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_app_key (app_key)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家开放平台应用配置';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_goofish_order (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
app_key varchar(64) NOT NULL COMMENT 'AppKey',
|
||||||
|
seller_id bigint(20) DEFAULT NULL COMMENT '商家ID',
|
||||||
|
user_name varchar(128) DEFAULT NULL COMMENT '闲鱼会员名',
|
||||||
|
order_no varchar(64) NOT NULL COMMENT '闲鱼订单号',
|
||||||
|
order_type int(11) DEFAULT NULL COMMENT '订单类型',
|
||||||
|
order_status int(11) DEFAULT NULL COMMENT '订单状态(推送/列表)',
|
||||||
|
refund_status int(11) DEFAULT NULL COMMENT '退款状态',
|
||||||
|
modify_time bigint(20) DEFAULT NULL COMMENT '订单更新时间(秒)',
|
||||||
|
product_id bigint(20) DEFAULT NULL COMMENT '管家商品ID',
|
||||||
|
item_id bigint(20) DEFAULT NULL COMMENT '闲鱼商品ID',
|
||||||
|
goods_title varchar(512) DEFAULT NULL COMMENT '商品标题(详情 goods.title)',
|
||||||
|
goods_image_url varchar(1024) DEFAULT NULL COMMENT '商品主图 URL(goods.images 首图)',
|
||||||
|
buyer_nick varchar(256) DEFAULT NULL COMMENT '买家昵称(详情 buyer_nick)',
|
||||||
|
pay_amount bigint(20) DEFAULT NULL COMMENT '实付金额(分) pay_amount',
|
||||||
|
detail_waybill_no varchar(128) DEFAULT NULL COMMENT '闲管家详情回传运单号 waybill_no',
|
||||||
|
detail_express_code varchar(64) DEFAULT NULL COMMENT '详情快递编码 express_code',
|
||||||
|
detail_express_name varchar(128) DEFAULT NULL COMMENT '详情快递名称 express_name',
|
||||||
|
receiver_name varchar(128) DEFAULT NULL COMMENT '收货人(详情有则落库)',
|
||||||
|
receiver_mobile varchar(64) DEFAULT NULL COMMENT '收货手机',
|
||||||
|
receiver_address varchar(1000) DEFAULT NULL COMMENT '收货详细地址(address)',
|
||||||
|
receiver_region varchar(256) DEFAULT NULL COMMENT '省市区街道等拼接展示',
|
||||||
|
recv_prov_name varchar(64) DEFAULT NULL COMMENT 'prov_name',
|
||||||
|
recv_city_name varchar(64) DEFAULT NULL COMMENT 'city_name',
|
||||||
|
recv_area_name varchar(64) DEFAULT NULL COMMENT 'area_name',
|
||||||
|
recv_town_name varchar(128) DEFAULT NULL COMMENT 'town_name',
|
||||||
|
detail_json longtext COMMENT '订单详情接口全量 JSON',
|
||||||
|
last_notify_json longtext COMMENT '最近一次推送原文 JSON',
|
||||||
|
jd_order_id bigint(20) DEFAULT NULL COMMENT '关联 jd_order.id(第三方单号=闲鱼单号)',
|
||||||
|
local_waybill_no varchar(128) DEFAULT NULL COMMENT '本地物流扫描得到的运单号',
|
||||||
|
ship_status tinyint(4) NOT NULL DEFAULT '0' COMMENT '0未发货 1已调用发货成功 2发货失败',
|
||||||
|
ship_error varchar(500) DEFAULT NULL COMMENT '发货失败原因',
|
||||||
|
ship_time datetime DEFAULT NULL COMMENT '发货调用成功时间',
|
||||||
|
ship_express_code varchar(64) DEFAULT NULL COMMENT '实际发货使用的快递编码',
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
update_time datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_app_order (app_key, order_no),
|
||||||
|
KEY idx_jd_order (jd_order_id),
|
||||||
|
KEY idx_order_status (order_status),
|
||||||
|
KEY idx_modify_time (modify_time)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家 ERP 订单(全量跟踪)';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_goofish_order_event_log (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
order_id bigint(20) NOT NULL COMMENT 'erp_goofish_order.id',
|
||||||
|
app_key varchar(64) DEFAULT NULL,
|
||||||
|
order_no varchar(64) NOT NULL,
|
||||||
|
event_type varchar(32) NOT NULL COMMENT 'ORDER_SYNC/LOGISTICS_SYNC/SHIP',
|
||||||
|
source varchar(64) NULL COMMENT 'NOTIFY/LIST/DETAIL_REFRESH 等',
|
||||||
|
message varchar(1024) NOT NULL,
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_goofish_evt_order (order_id),
|
||||||
|
KEY idx_goofish_evt_time (create_time)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家订单状态/物流/发货变更日志';
|
||||||
|
|
||||||
|
-- 可选:从旧枚举迁入两条示例(密钥请上线后立即修改)
|
||||||
|
-- INSERT INTO erp_open_config (app_key,app_secret,xy_user_name,remark,express_code,express_name,status,order_num)
|
||||||
|
-- VALUES ('1016208368633221','***','余生请多关照66','海尔胡歌',NULL,'日日顺','0',1);
|
||||||
|
|
||||||
|
-- 若依菜单(在「菜单管理」手工添加时参考)
|
||||||
|
-- 父菜单:系统管理下新增目录「闲管家ERP」
|
||||||
|
-- 子菜单1:组件 system/goofish/erpOpenConfig/index 权限前缀 jarvis:erpOpenConfig
|
||||||
|
-- 子菜单2:组件 system/goofish/erpGoofishOrder/index 权限前缀 jarvis:erpGoofishOrder
|
||||||
|
-- 子菜单3:组件 system/goofish/erpGoofishEventLog/index 权限沿用 jarvis:erpGoofishOrder:list 即可(仅列表查询)
|
||||||
|
-- 路由地址建议与订单页同级,如订单为 …/erpGoofishOrder 则本页 …/erpGoofishEventLog(订单页「变更日志排查」按钮依赖此规则)
|
||||||
|
-- 按钮权限示例:
|
||||||
|
-- jarvis:erpOpenConfig:list,query,add,edit,remove
|
||||||
|
-- jarvis:erpGoofishOrder:list,query,edit
|
||||||
|
|
||||||
|
-- —— 已建表升级:详情摘要字段(若列已存在会报错,可逐条执行并忽略)——
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN goods_title varchar(512) NULL COMMENT '商品标题' AFTER item_id;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN goods_image_url varchar(1024) NULL COMMENT '商品主图URL' AFTER goods_title;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN buyer_nick varchar(256) NULL COMMENT '买家昵称' AFTER goods_image_url;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN pay_amount bigint(20) NULL COMMENT '实付金额(分)' AFTER buyer_nick;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN detail_waybill_no varchar(128) NULL COMMENT '详情运单号' AFTER pay_amount;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN detail_express_code varchar(64) NULL COMMENT '详情快递编码' AFTER detail_waybill_no;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN detail_express_name varchar(128) NULL COMMENT '详情快递名称' AFTER detail_express_code;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN receiver_name varchar(128) NULL COMMENT '收货人' AFTER detail_express_name;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN receiver_mobile varchar(64) NULL COMMENT '收货手机' AFTER receiver_name;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN receiver_address varchar(1000) NULL COMMENT '收货地址' AFTER receiver_mobile;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN receiver_region varchar(256) NULL COMMENT '省市区' AFTER receiver_address;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN recv_prov_name varchar(64) NULL COMMENT 'prov_name' AFTER receiver_region;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN recv_city_name varchar(64) NULL COMMENT 'city_name' AFTER recv_prov_name;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN recv_area_name varchar(64) NULL COMMENT 'area_name' AFTER recv_city_name;
|
||||||
|
-- ALTER TABLE erp_goofish_order ADD COLUMN recv_town_name varchar(128) NULL COMMENT 'town_name' AFTER recv_area_name;
|
||||||
|
|
||||||
|
-- 已建库一键升级(可重复执行、自动判存):请使用同目录 erp_goofish_upgrade.sql
|
||||||
212
ruoyi-admin/src/main/resources/sql/erp_goofish_upgrade.sql
Normal file
212
ruoyi-admin/src/main/resources/sql/erp_goofish_upgrade.sql
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- 闲管家 ERP:erp_open_config / erp_goofish_order 一键升级脚本
|
||||||
|
-- =============================================================================
|
||||||
|
-- 用法:
|
||||||
|
-- 1. 先备份数据库;连接目标库后执行(或在文件开头加 USE your_database;)
|
||||||
|
-- 2. 可重复执行:已存在的列/索引会自动跳过
|
||||||
|
-- 3. ADD COLUMN 一律不指定 AFTER,避免旧表缺中间列时升级失败(新列落在表尾,不影响业务)
|
||||||
|
-- 环境:MySQL 5.7+ / 8.x(MariaDB 未逐项验证)
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
SET @schema := DATABASE();
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 1) 表不存在时创建完整结构(与 erp_goofish_init.sql 一致)
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_open_config (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
app_key varchar(64) NOT NULL COMMENT '开放平台 AppKey(appid)',
|
||||||
|
app_secret varchar(128) NOT NULL COMMENT '开放平台 AppSecret',
|
||||||
|
xy_user_name varchar(128) DEFAULT NULL COMMENT '默认闲鱼会员名(展示)',
|
||||||
|
express_code varchar(64) DEFAULT NULL COMMENT '发货用快递公司编码(日日顺物流在官方列表中为 rrs)',
|
||||||
|
express_name varchar(64) DEFAULT NULL COMMENT '快递公司名称(展示)',
|
||||||
|
status char(1) NOT NULL DEFAULT '0' COMMENT '0正常 1停用',
|
||||||
|
order_num int(11) NOT NULL DEFAULT 0 COMMENT '排序(小优先)',
|
||||||
|
create_by varchar(64) DEFAULT '',
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
update_by varchar(64) DEFAULT '',
|
||||||
|
update_time datetime DEFAULT NULL,
|
||||||
|
remark varchar(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_app_key (app_key)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家开放平台应用配置';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_goofish_order (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
app_key varchar(64) NOT NULL COMMENT 'AppKey',
|
||||||
|
seller_id bigint(20) DEFAULT NULL COMMENT '商家ID',
|
||||||
|
user_name varchar(128) DEFAULT NULL COMMENT '闲鱼会员名',
|
||||||
|
order_no varchar(64) NOT NULL COMMENT '闲鱼订单号',
|
||||||
|
order_type int(11) DEFAULT NULL COMMENT '订单类型',
|
||||||
|
order_status int(11) DEFAULT NULL COMMENT '订单状态(推送/列表)',
|
||||||
|
refund_status int(11) DEFAULT NULL COMMENT '退款状态',
|
||||||
|
modify_time bigint(20) DEFAULT NULL COMMENT '订单更新时间(秒)',
|
||||||
|
product_id bigint(20) DEFAULT NULL COMMENT '管家商品ID',
|
||||||
|
item_id bigint(20) DEFAULT NULL COMMENT '闲鱼商品ID',
|
||||||
|
goods_title varchar(512) DEFAULT NULL COMMENT '商品标题(详情 goods.title)',
|
||||||
|
goods_image_url varchar(1024) DEFAULT NULL COMMENT '商品主图 URL(goods.images 首图)',
|
||||||
|
buyer_nick varchar(256) DEFAULT NULL COMMENT '买家昵称(详情 buyer_nick)',
|
||||||
|
pay_amount bigint(20) DEFAULT NULL COMMENT '实付金额(分) pay_amount',
|
||||||
|
detail_waybill_no varchar(128) DEFAULT NULL COMMENT '闲管家详情回传运单号 waybill_no',
|
||||||
|
detail_express_code varchar(64) DEFAULT NULL COMMENT '详情快递编码 express_code',
|
||||||
|
detail_express_name varchar(128) DEFAULT NULL COMMENT '详情快递名称 express_name',
|
||||||
|
receiver_name varchar(128) DEFAULT NULL COMMENT '收货人(详情有则落库)',
|
||||||
|
receiver_mobile varchar(64) DEFAULT NULL COMMENT '收货手机',
|
||||||
|
receiver_address varchar(1000) DEFAULT NULL COMMENT '收货详细地址(address)',
|
||||||
|
receiver_region varchar(256) DEFAULT NULL COMMENT '省市区街道等拼接展示',
|
||||||
|
recv_prov_name varchar(64) DEFAULT NULL COMMENT 'prov_name',
|
||||||
|
recv_city_name varchar(64) DEFAULT NULL COMMENT 'city_name',
|
||||||
|
recv_area_name varchar(64) DEFAULT NULL COMMENT 'area_name',
|
||||||
|
recv_town_name varchar(128) DEFAULT NULL COMMENT 'town_name',
|
||||||
|
detail_json longtext COMMENT '订单详情接口全量 JSON',
|
||||||
|
last_notify_json longtext COMMENT '最近一次推送原文 JSON',
|
||||||
|
jd_order_id bigint(20) DEFAULT NULL COMMENT '关联 jd_order.id(第三方单号=闲鱼单号)',
|
||||||
|
local_waybill_no varchar(128) DEFAULT NULL COMMENT '本地物流扫描得到的运单号',
|
||||||
|
ship_status tinyint(4) NOT NULL DEFAULT '0' COMMENT '0未发货 1已调用发货成功 2发货失败',
|
||||||
|
ship_error varchar(500) DEFAULT NULL COMMENT '发货失败原因',
|
||||||
|
ship_time datetime DEFAULT NULL COMMENT '发货调用成功时间',
|
||||||
|
ship_express_code varchar(64) DEFAULT NULL COMMENT '实际发货使用的快递编码',
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
update_time datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_app_order (app_key, order_no),
|
||||||
|
KEY idx_jd_order (jd_order_id),
|
||||||
|
KEY idx_order_status (order_status),
|
||||||
|
KEY idx_modify_time (modify_time)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家 ERP 订单(全量跟踪)';
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 2) 存储过程:列不存在则 ADD
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
DROP PROCEDURE IF EXISTS jarvis_erp_goofish_add_column;
|
||||||
|
DROP PROCEDURE IF EXISTS jarvis_erp_goofish_add_index;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
|
||||||
|
CREATE PROCEDURE jarvis_erp_goofish_add_column(
|
||||||
|
IN p_table VARCHAR(64),
|
||||||
|
IN p_column VARCHAR(64),
|
||||||
|
IN p_ddl TEXT
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
DECLARE col_exists INT DEFAULT 0;
|
||||||
|
SELECT COUNT(*) INTO col_exists
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @schema
|
||||||
|
AND TABLE_NAME = p_table
|
||||||
|
AND COLUMN_NAME = p_column;
|
||||||
|
IF col_exists = 0 THEN
|
||||||
|
SET @stmt := CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_ddl);
|
||||||
|
PREPARE ps FROM @stmt;
|
||||||
|
EXECUTE ps;
|
||||||
|
DEALLOCATE PREPARE ps;
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
CREATE PROCEDURE jarvis_erp_goofish_add_index(
|
||||||
|
IN p_table VARCHAR(64),
|
||||||
|
IN p_index VARCHAR(64),
|
||||||
|
IN p_columns VARCHAR(200)
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
DECLARE idx_exists INT DEFAULT 0;
|
||||||
|
SELECT COUNT(*) INTO idx_exists
|
||||||
|
FROM information_schema.statistics
|
||||||
|
WHERE TABLE_SCHEMA = @schema
|
||||||
|
AND TABLE_NAME = p_table
|
||||||
|
AND INDEX_NAME = p_index;
|
||||||
|
IF idx_exists = 0 THEN
|
||||||
|
SET @stmt := CONCAT('ALTER TABLE `', p_table, '` ADD INDEX `', p_index, '` (', p_columns, ')');
|
||||||
|
PREPARE ps FROM @stmt;
|
||||||
|
EXECUTE ps;
|
||||||
|
DEALLOCATE PREPARE ps;
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 3) erp_goofish_order:补齐缺列(与当前 Java 实体 / erp_goofish_init.sql 一致)
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'seller_id', 'bigint(20) NULL COMMENT ''商家ID''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'user_name', 'varchar(128) NULL COMMENT ''闲鱼会员名''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'order_type', 'int(11) NULL COMMENT ''订单类型''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'order_status', 'int(11) NULL COMMENT ''订单状态(推送/列表)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'refund_status', 'int(11) NULL COMMENT ''退款状态''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'modify_time', 'bigint(20) NULL COMMENT ''订单更新时间(秒)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'product_id', 'bigint(20) NULL COMMENT ''管家商品ID''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'item_id', 'bigint(20) NULL COMMENT ''闲鱼商品ID''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'goods_title', 'varchar(512) NULL COMMENT ''商品标题(详情 goods.title)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'goods_image_url', 'varchar(1024) NULL COMMENT ''商品主图 URL(goods.images 首图)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'buyer_nick', 'varchar(256) NULL COMMENT ''买家昵称(详情 buyer_nick)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'pay_amount', 'bigint(20) NULL COMMENT ''实付金额(分) pay_amount''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'detail_waybill_no', 'varchar(128) NULL COMMENT ''闲管家详情回传运单号 waybill_no''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'detail_express_code', 'varchar(64) NULL COMMENT ''详情快递编码 express_code''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'detail_express_name', 'varchar(128) NULL COMMENT ''详情快递名称 express_name''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'receiver_name', 'varchar(128) NULL COMMENT ''收货人(详情有则落库)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'receiver_mobile', 'varchar(64) NULL COMMENT ''收货手机''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'receiver_address', 'varchar(1000) NULL COMMENT ''收货详细地址(address)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'receiver_region', 'varchar(256) NULL COMMENT ''省市区街道等拼接展示''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'recv_prov_name', 'varchar(64) NULL COMMENT ''prov_name''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'recv_city_name', 'varchar(64) NULL COMMENT ''city_name''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'recv_area_name', 'varchar(64) NULL COMMENT ''area_name''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'recv_town_name', 'varchar(128) NULL COMMENT ''town_name''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'detail_json', 'longtext NULL COMMENT ''订单详情接口全量 JSON''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'last_notify_json', 'longtext NULL COMMENT ''最近一次推送原文 JSON''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'jd_order_id', 'bigint(20) NULL COMMENT ''关联 jd_order.id(第三方单号=闲鱼单号)''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'local_waybill_no', 'varchar(128) NULL COMMENT ''本地物流扫描得到的运单号''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'ship_status', 'tinyint(4) NOT NULL DEFAULT 0 COMMENT ''0未发货 1已调用发货成功 2发货失败''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'ship_error', 'varchar(500) NULL COMMENT ''发货失败原因''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'ship_time', 'datetime NULL COMMENT ''发货调用成功时间''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'ship_express_code', 'varchar(64) NULL COMMENT ''实际发货使用的快递编码''');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'create_time', 'datetime NULL');
|
||||||
|
CALL jarvis_erp_goofish_add_column('erp_goofish_order', 'update_time', 'datetime NULL');
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 4) 索引(手工建表可能缺)
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
CALL jarvis_erp_goofish_add_index('erp_goofish_order', 'idx_jd_order', '`jd_order_id`');
|
||||||
|
CALL jarvis_erp_goofish_add_index('erp_goofish_order', 'idx_order_status', '`order_status`');
|
||||||
|
CALL jarvis_erp_goofish_add_index('erp_goofish_order', 'idx_modify_time', '`modify_time`');
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 5) 唯一键 uk_app_order
|
||||||
|
-- 若已存在同名约束则跳过;若 (app_key,order_no) 有重复行会报错,需先清洗数据。
|
||||||
|
-- 若已通过其它名称建了 (app_key,order_no) 唯一索引,请勿重复执行本节(可能报 Duplicate key)。
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
SET @uk := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.table_constraints
|
||||||
|
WHERE table_schema = @schema AND table_name = 'erp_goofish_order'
|
||||||
|
AND constraint_name = 'uk_app_order' AND constraint_type = 'UNIQUE'
|
||||||
|
);
|
||||||
|
SET @sql_uk := IF(@uk = 0,
|
||||||
|
'ALTER TABLE erp_goofish_order ADD UNIQUE KEY uk_app_order (app_key, order_no)',
|
||||||
|
'SELECT ''uk_app_order 已存在,跳过'' AS note');
|
||||||
|
PREPARE puk FROM @sql_uk;
|
||||||
|
EXECUTE puk;
|
||||||
|
DEALLOCATE PREPARE puk;
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 5.5) 订单变更事件日志表
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS erp_goofish_order_event_log (
|
||||||
|
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
order_id bigint(20) NOT NULL COMMENT 'erp_goofish_order.id',
|
||||||
|
app_key varchar(64) DEFAULT NULL,
|
||||||
|
order_no varchar(64) NOT NULL,
|
||||||
|
event_type varchar(32) NOT NULL COMMENT 'ORDER_SYNC/LOGISTICS_SYNC/SHIP',
|
||||||
|
source varchar(64) NULL COMMENT 'NOTIFY/LIST/DETAIL_REFRESH 等',
|
||||||
|
message varchar(1024) NOT NULL,
|
||||||
|
create_time datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_goofish_evt_order (order_id),
|
||||||
|
KEY idx_goofish_evt_time (create_time)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲管家订单状态/物流/发货变更日志';
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 6) 清理存储过程
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
DROP PROCEDURE IF EXISTS jarvis_erp_goofish_add_column;
|
||||||
|
DROP PROCEDURE IF EXISTS jarvis_erp_goofish_add_index;
|
||||||
|
|
||||||
|
SELECT 'erp_goofish_upgrade.sql 执行结束' AS message;
|
||||||
@@ -118,6 +118,12 @@ public class SecurityConfig
|
|||||||
.antMatchers("/jarvis/tendoc/oauth/callback").permitAll()
|
.antMatchers("/jarvis/tendoc/oauth/callback").permitAll()
|
||||||
// 腾讯文档OAuth回调接口(备用路径),允许匿名访问
|
// 腾讯文档OAuth回调接口(备用路径),允许匿名访问
|
||||||
.antMatchers("/tendoc-callback").permitAll()
|
.antMatchers("/tendoc-callback").permitAll()
|
||||||
|
// 金山文档 OAuth 回调(与 @Anonymous 双保险,避免未扫描进白名单时 401)
|
||||||
|
.antMatchers("/kdocs-callback").permitAll()
|
||||||
|
// 旧 WPS 回调路径:重定向到新路径,便于后台仍登记旧 URL 时可用
|
||||||
|
.antMatchers("/wps365-callback").permitAll()
|
||||||
|
// 企微消息经 wxSend 转发的桥接(依赖请求头共享密钥)
|
||||||
|
.antMatchers("/jarvis/wecom/inbound").permitAll()
|
||||||
// 静态资源,可匿名访问
|
// 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 授权列表查询请求(示例类)
|
* 授权列表查询请求(示例类)
|
||||||
*/
|
*/
|
||||||
public class AuthorizeListQueryRequest extends ERPRequestBase {
|
public class AuthorizeListQueryRequest extends ERPRequestBase {
|
||||||
public AuthorizeListQueryRequest(ERPAccount erpAccount) {
|
public AuthorizeListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/user/authorize/list", erpAccount);
|
super("https://open.goofish.pro/api/open/user/authorize/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import lombok.Getter;
|
|||||||
* @description:ERP账户枚举类
|
* @description:ERP账户枚举类
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
public enum ERPAccount {
|
public enum ERPAccount implements IERPAccount {
|
||||||
// 胡歌1016208368633221
|
// 胡歌1016208368633221
|
||||||
ACCOUNT_HUGE("1016208368633221", "waLiRMgFcixLbcLjUSSwo370Hp1nBcBu","余生请多关照66","海尔胡歌"),
|
ACCOUNT_HUGE("1016208368633221", "waLiRMgFcixLbcLjUSSwo370Hp1nBcBu","余生请多关照66","海尔胡歌"),
|
||||||
// 刘强东anotherApiKey
|
// 刘强东anotherApiKey
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ public abstract class ERPRequestBase {
|
|||||||
|
|
||||||
protected String url;
|
protected String url;
|
||||||
protected String sign;
|
protected String sign;
|
||||||
protected ERPAccount erpAccount;
|
protected IERPAccount erpAccount;
|
||||||
@Setter
|
@Setter
|
||||||
protected JSONObject requestBody;
|
protected JSONObject requestBody;
|
||||||
protected long timestamp; // 统一时间戳字段
|
protected long timestamp; // 统一时间戳字段
|
||||||
|
|
||||||
public ERPRequestBase(String url, ERPAccount erpAccount) {
|
public ERPRequestBase(String url, IERPAccount erpAccount) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.erpAccount = erpAccount;
|
this.erpAccount = erpAccount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package com.ruoyi.erp.request;
|
package com.ruoyi.erp.request;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询快递公司请求
|
* 查询快递公司请求
|
||||||
*
|
* <p>
|
||||||
* 对应接口:POST /api/open/express/companies
|
* 对应接口:POST /api/open/express/companies<br>
|
||||||
|
* 闲管家规定:无业务参数时应对 <strong>空 JSON 对象</strong> 做 {@code md5("{}")},且 POST 原文必须与之一致;
|
||||||
|
* Apifox 调试时请勿留空 body,须填 {@code {}},否则签名与平台不一致会拉不到数据。
|
||||||
*/
|
*/
|
||||||
public class ExpressCompaniesQueryRequest extends ERPRequestBase {
|
public class ExpressCompaniesQueryRequest extends ERPRequestBase {
|
||||||
public ExpressCompaniesQueryRequest(ERPAccount erpAccount) {
|
public ExpressCompaniesQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/express/companies", erpAccount);
|
super("https://open.goofish.pro/api/open/express/companies", erpAccount);
|
||||||
|
this.requestBody = new JSONObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.erp.request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家开放平台凭证({@link ERPAccount} 或库表配置行均可实现本接口)
|
||||||
|
*/
|
||||||
|
public interface IERPAccount {
|
||||||
|
|
||||||
|
String getApiKey();
|
||||||
|
|
||||||
|
String getApiKeySecret();
|
||||||
|
|
||||||
|
/** 闲鱼会员名(授权维度展示用,可为空) */
|
||||||
|
String getXyName();
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/order/detail
|
* 对应接口:POST /api/open/order/detail
|
||||||
*/
|
*/
|
||||||
public class OrderDetailQueryRequest extends ERPRequestBase {
|
public class OrderDetailQueryRequest extends ERPRequestBase {
|
||||||
public OrderDetailQueryRequest(ERPAccount erpAccount) {
|
public OrderDetailQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/order/detail", erpAccount);
|
super("https://open.goofish.pro/api/open/order/detail", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/order/kam/list
|
* 对应接口:POST /api/open/order/kam/list
|
||||||
*/
|
*/
|
||||||
public class OrderKamListQueryRequest extends ERPRequestBase {
|
public class OrderKamListQueryRequest extends ERPRequestBase {
|
||||||
public OrderKamListQueryRequest(ERPAccount erpAccount) {
|
public OrderKamListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/order/kam/list", erpAccount);
|
super("https://open.goofish.pro/api/open/order/kam/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/order/list
|
* 对应接口:POST /api/open/order/list
|
||||||
*/
|
*/
|
||||||
public class OrderListQueryRequest extends ERPRequestBase {
|
public class OrderListQueryRequest extends ERPRequestBase {
|
||||||
public OrderListQueryRequest(ERPAccount erpAccount) {
|
public OrderListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/order/list", erpAccount);
|
super("https://open.goofish.pro/api/open/order/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/order/modify/price
|
* 对应接口:POST /api/open/order/modify/price
|
||||||
*/
|
*/
|
||||||
public class OrderModifyPriceRequest extends ERPRequestBase {
|
public class OrderModifyPriceRequest extends ERPRequestBase {
|
||||||
public OrderModifyPriceRequest(ERPAccount erpAccount) {
|
public OrderModifyPriceRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/order/modify/price", erpAccount);
|
super("https://open.goofish.pro/api/open/order/modify/price", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/order/ship
|
* 对应接口:POST /api/open/order/ship
|
||||||
*/
|
*/
|
||||||
public class OrderShipRequest extends ERPRequestBase {
|
public class OrderShipRequest extends ERPRequestBase {
|
||||||
public OrderShipRequest(ERPAccount erpAccount) {
|
public OrderShipRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/order/ship", erpAccount);
|
super("https://open.goofish.pro/api/open/order/ship", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
* 限制:每批次最多50个商品
|
* 限制:每批次最多50个商品
|
||||||
*/
|
*/
|
||||||
public class ProductBatchCreateRequest extends ERPRequestBase {
|
public class ProductBatchCreateRequest extends ERPRequestBase {
|
||||||
public ProductBatchCreateRequest(ERPAccount erpAccount) {
|
public ProductBatchCreateRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/batchCreate", erpAccount);
|
super("https://open.goofish.pro/api/open/product/batchCreate", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* - flash_sale_type(选填):闲鱼特卖类型
|
* - flash_sale_type(选填):闲鱼特卖类型
|
||||||
*/
|
*/
|
||||||
public class ProductCategoryListQueryRequest extends ERPRequestBase {
|
public class ProductCategoryListQueryRequest extends ERPRequestBase {
|
||||||
public ProductCategoryListQueryRequest(ERPAccount erpAccount) {
|
public ProductCategoryListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/category/list", erpAccount);
|
super("https://open.goofish.pro/api/open/product/category/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class ProductCreateRequest extends ERPRequestBase {
|
|||||||
private final JSONArray publishShop = new JSONArray();
|
private final JSONArray publishShop = new JSONArray();
|
||||||
private final JSONArray skuItems = new JSONArray();
|
private final JSONArray skuItems = new JSONArray();
|
||||||
|
|
||||||
public ProductCreateRequest(ERPAccount erpAccount) {
|
public ProductCreateRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/create", erpAccount);
|
super("https://open.goofish.pro/api/open/product/create", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/product/delete
|
* 对应接口:POST /api/open/product/delete
|
||||||
*/
|
*/
|
||||||
public class ProductDeleteRequest extends ERPRequestBase {
|
public class ProductDeleteRequest extends ERPRequestBase {
|
||||||
public ProductDeleteRequest(ERPAccount erpAccount) {
|
public ProductDeleteRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/delete", erpAccount);
|
super("https://open.goofish.pro/api/open/product/delete", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* - product_id(必填):管家商品ID
|
* - product_id(必填):管家商品ID
|
||||||
*/
|
*/
|
||||||
public class ProductDetailQueryRequest extends ERPRequestBase {
|
public class ProductDetailQueryRequest extends ERPRequestBase {
|
||||||
public ProductDetailQueryRequest(ERPAccount erpAccount) {
|
public ProductDetailQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/detail", erpAccount);
|
super("https://open.goofish.pro/api/open/product/detail", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/product/downShelf
|
* 对应接口:POST /api/open/product/downShelf
|
||||||
*/
|
*/
|
||||||
public class ProductDownShelfRequest extends ERPRequestBase {
|
public class ProductDownShelfRequest extends ERPRequestBase {
|
||||||
public ProductDownShelfRequest(ERPAccount erpAccount) {
|
public ProductDownShelfRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/downShelf", erpAccount);
|
super("https://open.goofish.pro/api/open/product/downShelf", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class ProductEditRequest extends ERPRequestBase {
|
|||||||
private final JSONArray publishShop = new JSONArray();
|
private final JSONArray publishShop = new JSONArray();
|
||||||
private final JSONArray skuItems = new JSONArray();
|
private final JSONArray skuItems = new JSONArray();
|
||||||
|
|
||||||
public ProductEditRequest(ERPAccount erpAccount) {
|
public ProductEditRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/edit", erpAccount);
|
super("https://open.goofish.pro/api/open/product/edit", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/product/edit/stock
|
* 对应接口:POST /api/open/product/edit/stock
|
||||||
*/
|
*/
|
||||||
public class ProductEditStockRequest extends ERPRequestBase {
|
public class ProductEditStockRequest extends ERPRequestBase {
|
||||||
public ProductEditStockRequest(ERPAccount erpAccount) {
|
public ProductEditStockRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/edit/stock", erpAccount);
|
super("https://open.goofish.pro/api/open/product/edit/stock", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.ruoyi.erp.request;
|
|||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
public class ProductListQueryRequest extends ERPRequestBase {
|
public class ProductListQueryRequest extends ERPRequestBase {
|
||||||
public ProductListQueryRequest(ERPAccount erpAccount) {
|
public ProductListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/list", erpAccount);
|
super("https://open.goofish.pro/api/open/product/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* - sub_property_id(选填):属性值ID(用于二级属性查询)
|
* - sub_property_id(选填):属性值ID(用于二级属性查询)
|
||||||
*/
|
*/
|
||||||
public class ProductPropertyListQueryRequest extends ERPRequestBase {
|
public class ProductPropertyListQueryRequest extends ERPRequestBase {
|
||||||
public ProductPropertyListQueryRequest(ERPAccount erpAccount) {
|
public ProductPropertyListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/pv/list", erpAccount);
|
super("https://open.goofish.pro/api/open/product/pv/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
* 对应接口:POST /api/open/product/publish
|
* 对应接口:POST /api/open/product/publish
|
||||||
*/
|
*/
|
||||||
public class ProductPublishRequest extends ERPRequestBase {
|
public class ProductPublishRequest extends ERPRequestBase {
|
||||||
public ProductPublishRequest(ERPAccount erpAccount) {
|
public ProductPublishRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/publish", erpAccount);
|
super("https://open.goofish.pro/api/open/product/publish", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.Collection;
|
|||||||
* - product_id(必填):管家商品ID数组,最多100个
|
* - product_id(必填):管家商品ID数组,最多100个
|
||||||
*/
|
*/
|
||||||
public class ProductSkuListQueryRequest extends ERPRequestBase {
|
public class ProductSkuListQueryRequest extends ERPRequestBase {
|
||||||
public ProductSkuListQueryRequest(ERPAccount erpAccount) {
|
public ProductSkuListQueryRequest(IERPAccount erpAccount) {
|
||||||
super("https://open.goofish.pro/api/open/product/sku/list", erpAccount);
|
super("https://open.goofish.pro/api/open/product/sku/list", erpAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.jarvis.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class GoofishAsyncConfig {
|
||||||
|
|
||||||
|
@Bean("goofishTaskExecutor")
|
||||||
|
public Executor goofishTaskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
// 仅用于未配置 RocketMQ 时 HTTP 回调路径的 @Async;过小易在拉单/回调并发时排队拖慢企微通知
|
||||||
|
executor.setCorePoolSize(4);
|
||||||
|
executor.setMaxPoolSize(16);
|
||||||
|
executor.setQueueCapacity(500);
|
||||||
|
executor.setThreadNamePrefix("goofish-");
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.ruoyi.jarvis.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家订单:MQ 主题、定时拉单与自动发货调度
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "jarvis.goofish-order")
|
||||||
|
public class JarvisGoofishProperties {
|
||||||
|
|
||||||
|
/** RocketMQ Topic(需配置 rocketmq.name-server 后生效) */
|
||||||
|
private String mqTopic = "jarvis-goofish-erp-order";
|
||||||
|
|
||||||
|
private String consumerGroup = "jarvis-goofish-order-consumer";
|
||||||
|
|
||||||
|
/** 回溯拉单小时数(定时/增量) */
|
||||||
|
private int pullLookbackHours = 72;
|
||||||
|
|
||||||
|
/** 拉单定时 cron */
|
||||||
|
private String pullCron = "0 * * * * ?";
|
||||||
|
|
||||||
|
/** 同步运单 + 自动发货 cron */
|
||||||
|
private String autoShipCron = "0 2/10 * * * ?";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单列表 page_size(开放平台最大 100)
|
||||||
|
*/
|
||||||
|
private int pullPageSize = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单次拉单每授权最大页数(开放平台 page_no 最大 100;page_no×page_size 勿超过 10000)
|
||||||
|
*/
|
||||||
|
private int pullMaxPagesPerShop = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量/长历史拉单时,按 update_time 切片的窗口长度(秒),避免单窗内订单量过大触发平台限制
|
||||||
|
*/
|
||||||
|
private int pullTimeChunkSeconds = 604800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单列表单次请求中 update_time 区间最大跨度(秒)。开放平台返回「只能查询时间范围6个月内的数据」时须≤此值;默认约 180 天留余量
|
||||||
|
*/
|
||||||
|
private int pullMaxUpdateTimeRangeSeconds = 15552000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量拉单从「当前时间」往前推多少天作为起点(仅 full 接口;可自行改大)
|
||||||
|
*/
|
||||||
|
private int pullFullHistoryDays = 1095;
|
||||||
|
|
||||||
|
private int autoShipBatchSize = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许触发闲鱼开放平台「发货」的本地 order_status(逗号分隔,与推送/列表一致)。默认 12 通常表示待发货。
|
||||||
|
*/
|
||||||
|
private String autoShipOrderStatuses = "12";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为 true 时定时/增量列表拉单仅按 {@link #autoShipOrderStatuses} 过滤(减少调用量,但会漏掉其它状态)。
|
||||||
|
* 默认 false:按时间窗拉全状态,与本地 upsert 对齐,推送仍可用于更低延迟。
|
||||||
|
*/
|
||||||
|
private boolean pullListOnlyAutoShipStatuses = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未在 erp_open_config 配置 express_code 时,自动发货使用的默认快递公司编码(官方列表中日日顺多为 rrs)。
|
||||||
|
*/
|
||||||
|
private String defaultShipExpressCode = "rrs";
|
||||||
|
|
||||||
|
/** 与 defaultShipExpressCode 配套的展示名称 */
|
||||||
|
private String defaultShipExpressName = "日日顺";
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家 ERP 订单(推送 + 拉单 + 详情全量)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ErpGoofishOrder {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String appKey;
|
||||||
|
private Long sellerId;
|
||||||
|
private String userName;
|
||||||
|
private String orderNo;
|
||||||
|
private Integer orderType;
|
||||||
|
private Integer orderStatus;
|
||||||
|
private Integer refundStatus;
|
||||||
|
private Long modifyTime;
|
||||||
|
private Long productId;
|
||||||
|
private Long itemId;
|
||||||
|
/** 详情 goods.title */
|
||||||
|
private String goodsTitle;
|
||||||
|
/** goods.images 首张或其它单图字段 */
|
||||||
|
private String goodsImageUrl;
|
||||||
|
private String buyerNick;
|
||||||
|
/** 开放平台 pay_amount,单位:分 */
|
||||||
|
private Long payAmount;
|
||||||
|
/** 闲管家详情 waybill_no */
|
||||||
|
private String detailWaybillNo;
|
||||||
|
private String detailExpressCode;
|
||||||
|
private String detailExpressName;
|
||||||
|
private String receiverName;
|
||||||
|
private String receiverMobile;
|
||||||
|
private String receiverAddress;
|
||||||
|
/** 省市区拼接 */
|
||||||
|
private String receiverRegion;
|
||||||
|
/** 待发货等状态下开放平台返回的分级地址(与 prov_name/city_name/area_name/town_name 一致) */
|
||||||
|
private String recvProvName;
|
||||||
|
private String recvCityName;
|
||||||
|
private String recvAreaName;
|
||||||
|
private String recvTownName;
|
||||||
|
private String detailJson;
|
||||||
|
private String lastNotifyJson;
|
||||||
|
private Long jdOrderId;
|
||||||
|
private String localWaybillNo;
|
||||||
|
/** 0未发货 1成功 2失败 */
|
||||||
|
private Integer shipStatus;
|
||||||
|
private String shipError;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date shipTime;
|
||||||
|
private String shipExpressCode;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/** 联查:第三方单号(jd_order) */
|
||||||
|
private String jdThirdPartyOrderNo;
|
||||||
|
/** 联查:内部备注单号 */
|
||||||
|
private String jdRemark;
|
||||||
|
/** 联查:本地京东单收件地址 jd_order.address(闲鱼详情常不返回明文地址) */
|
||||||
|
private String jdAddress;
|
||||||
|
|
||||||
|
// --------- 以下为列表查询扩展条件(不参与 insert/update) ---------
|
||||||
|
/** 运单关键字:命中详情运单号或本地运单号(模糊) */
|
||||||
|
private String waybillKeyword;
|
||||||
|
/** 开放平台 modify_time 下限(Unix 秒,含边界) */
|
||||||
|
private Long modifyTimeBegin;
|
||||||
|
/** 开放平台 modify_time 上限(Unix 秒,含边界) */
|
||||||
|
private Long modifyTimeEnd;
|
||||||
|
/** 是否已关联京东单:1 已关联 jd_order_id 非空;0 未关联;null 不限 */
|
||||||
|
private Integer jdLinkFilter;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家订单变更日志:状态刷新、物流变动、发货结果等
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ErpGoofishOrderEventLog {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
/** erp_goofish_order.id */
|
||||||
|
private Long orderId;
|
||||||
|
private String appKey;
|
||||||
|
private String orderNo;
|
||||||
|
/** ORDER_SYNC / LOGISTICS_SYNC / SHIP */
|
||||||
|
private String eventType;
|
||||||
|
/** NOTIFY、LIST、DETAIL_REFRESH、JD_LOGISTICS_PUSH、REDIS_WAYBILL、AUTO_SHIP 等 */
|
||||||
|
private String source;
|
||||||
|
private String message;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家订单变更日志(全表检索,用于排查)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ErpGoofishOrderEventLogQuery extends BaseEntity {
|
||||||
|
|
||||||
|
private Long orderId;
|
||||||
|
private String appKey;
|
||||||
|
private String orderNo;
|
||||||
|
private String eventType;
|
||||||
|
private String source;
|
||||||
|
/** 模糊匹配 message */
|
||||||
|
private String messageKeyword;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import com.ruoyi.erp.request.IERPAccount;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家开放平台应用配置(配置中心)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ErpOpenConfig extends BaseEntity implements IERPAccount {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String appKey;
|
||||||
|
private String appSecret;
|
||||||
|
private String xyUserName;
|
||||||
|
/** 发货:快递公司编码(如日日顺,需与开放平台一致) */
|
||||||
|
private String expressCode;
|
||||||
|
private String expressName;
|
||||||
|
private String status;
|
||||||
|
private Integer orderNum;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiKey() {
|
||||||
|
return appKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiKeySecret() {
|
||||||
|
return appSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getXyName() {
|
||||||
|
return xyUserName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼商品导出专用行(字段偏多,便于给 AI / 离线分析使用)
|
||||||
|
*/
|
||||||
|
public class ErpProductExportRow implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Excel(name = "导出批次时间", width = 22, sort = 1)
|
||||||
|
private String exportBatchAt;
|
||||||
|
|
||||||
|
@Excel(name = "本表主键ID", sort = 2)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Excel(name = "管家商品ID", sort = 3)
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Excel(name = "管家商品ID文本", width = 22, sort = 4)
|
||||||
|
private String productIdText;
|
||||||
|
|
||||||
|
@Excel(name = "商品标题", width = 45, sort = 5)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Excel(name = "主图URL", width = 55, sort = 6)
|
||||||
|
private String mainImage;
|
||||||
|
|
||||||
|
@Excel(name = "价格_分_原始整数", sort = 7)
|
||||||
|
private Long priceFen;
|
||||||
|
|
||||||
|
@Excel(name = "价格_元_可读", sort = 8)
|
||||||
|
private String priceYuan;
|
||||||
|
|
||||||
|
@Excel(name = "库存", sort = 9)
|
||||||
|
private Integer stock;
|
||||||
|
|
||||||
|
@Excel(name = "商品状态_码", sort = 10)
|
||||||
|
private Integer productStatusCode;
|
||||||
|
|
||||||
|
@Excel(name = "商品状态_说明", width = 14, sort = 11)
|
||||||
|
private String productStatusLabel;
|
||||||
|
|
||||||
|
@Excel(name = "销售状态_码", sort = 12)
|
||||||
|
private Integer saleStatusCode;
|
||||||
|
|
||||||
|
@Excel(name = "闲鱼会员名", width = 18, sort = 13)
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Excel(name = "ERP应用appid", width = 22, sort = 14)
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
@Excel(name = "商品链接", width = 55, sort = 15)
|
||||||
|
private String productUrl;
|
||||||
|
|
||||||
|
@Excel(name = "上架时间_unix秒", width = 18, sort = 16)
|
||||||
|
private Long onlineTimeUnix;
|
||||||
|
|
||||||
|
@Excel(name = "上架时间_可读", width = 22, sort = 17)
|
||||||
|
private String onlineTimeReadable;
|
||||||
|
|
||||||
|
@Excel(name = "下架时间_unix秒", width = 18, sort = 18)
|
||||||
|
private Long offlineTimeUnix;
|
||||||
|
|
||||||
|
@Excel(name = "下架时间_可读", width = 22, sort = 19)
|
||||||
|
private String offlineTimeReadable;
|
||||||
|
|
||||||
|
@Excel(name = "售出时间_unix秒", width = 18, sort = 20)
|
||||||
|
private Long soldTimeUnix;
|
||||||
|
|
||||||
|
@Excel(name = "售出时间_可读", width = 22, sort = 21)
|
||||||
|
private String soldTimeReadable;
|
||||||
|
|
||||||
|
@Excel(name = "闲鱼创建_unix秒", width = 18, sort = 22)
|
||||||
|
private Long createTimeXyUnix;
|
||||||
|
|
||||||
|
@Excel(name = "闲鱼创建_可读", width = 22, sort = 23)
|
||||||
|
private String createTimeXyReadable;
|
||||||
|
|
||||||
|
@Excel(name = "闲鱼更新_unix秒", width = 18, sort = 24)
|
||||||
|
private Long updateTimeXyUnix;
|
||||||
|
|
||||||
|
@Excel(name = "闲鱼更新_可读", width = 22, sort = 25)
|
||||||
|
private String updateTimeXyReadable;
|
||||||
|
|
||||||
|
@Excel(name = "备注_本表", width = 30, sort = 26)
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Excel(name = "库创建时间", width = 22, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 27)
|
||||||
|
private Date dbCreateTime;
|
||||||
|
|
||||||
|
@Excel(name = "库更新时间", width = 22, dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 28)
|
||||||
|
private Date dbUpdateTime;
|
||||||
|
|
||||||
|
public static List<ErpProductExportRow> fromList(List<ErpProduct> list, String exportBatchAt)
|
||||||
|
{
|
||||||
|
List<ErpProductExportRow> rows = new ArrayList<>(list.size());
|
||||||
|
for (ErpProduct p : list)
|
||||||
|
{
|
||||||
|
rows.add(from(p, exportBatchAt));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErpProductExportRow from(ErpProduct p, String exportBatchAt)
|
||||||
|
{
|
||||||
|
ErpProductExportRow r = new ErpProductExportRow();
|
||||||
|
r.setExportBatchAt(exportBatchAt != null ? exportBatchAt : "");
|
||||||
|
if (p == null)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
r.setId(p.getId());
|
||||||
|
r.setProductId(p.getProductId());
|
||||||
|
r.setProductIdText(p.getProductId() != null ? String.valueOf(p.getProductId()) : "");
|
||||||
|
r.setTitle(p.getTitle());
|
||||||
|
r.setMainImage(p.getMainImage());
|
||||||
|
r.setPriceFen(p.getPrice());
|
||||||
|
if (p.getPrice() != null)
|
||||||
|
{
|
||||||
|
r.setPriceYuan(BigDecimal.valueOf(p.getPrice()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP).toPlainString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r.setPriceYuan("");
|
||||||
|
}
|
||||||
|
r.setStock(p.getStock());
|
||||||
|
r.setProductStatusCode(p.getProductStatus());
|
||||||
|
r.setProductStatusLabel(productStatusLabel(p.getProductStatus()));
|
||||||
|
r.setSaleStatusCode(p.getSaleStatus());
|
||||||
|
r.setUserName(p.getUserName());
|
||||||
|
r.setAppid(p.getAppid());
|
||||||
|
r.setProductUrl(p.getProductUrl());
|
||||||
|
|
||||||
|
r.setOnlineTimeUnix(p.getOnlineTime());
|
||||||
|
r.setOnlineTimeReadable(formatUnixSeconds(p.getOnlineTime()));
|
||||||
|
r.setOfflineTimeUnix(p.getOfflineTime());
|
||||||
|
r.setOfflineTimeReadable(formatUnixSeconds(p.getOfflineTime()));
|
||||||
|
r.setSoldTimeUnix(p.getSoldTime());
|
||||||
|
r.setSoldTimeReadable(formatUnixSeconds(p.getSoldTime()));
|
||||||
|
r.setCreateTimeXyUnix(p.getCreateTimeXy());
|
||||||
|
r.setCreateTimeXyReadable(formatUnixSeconds(p.getCreateTimeXy()));
|
||||||
|
r.setUpdateTimeXyUnix(p.getUpdateTimeXy());
|
||||||
|
r.setUpdateTimeXyReadable(formatUnixSeconds(p.getUpdateTimeXy()));
|
||||||
|
|
||||||
|
r.setRemark(p.getRemark());
|
||||||
|
r.setDbCreateTime(p.getCreateTime());
|
||||||
|
r.setDbUpdateTime(p.getUpdateTime());
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatUnixSeconds(Long unixSeconds)
|
||||||
|
{
|
||||||
|
if (unixSeconds == null || unixSeconds <= 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date(unixSeconds * 1000L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String productStatusLabel(Integer status)
|
||||||
|
{
|
||||||
|
if (status == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
return "删除";
|
||||||
|
case 10:
|
||||||
|
return "其它(10)";
|
||||||
|
case 21:
|
||||||
|
return "待发布";
|
||||||
|
case 22:
|
||||||
|
return "销售中";
|
||||||
|
case 23:
|
||||||
|
return "已售罄";
|
||||||
|
case 31:
|
||||||
|
return "手动下架";
|
||||||
|
case 33:
|
||||||
|
return "售出下架";
|
||||||
|
case 36:
|
||||||
|
return "自动下架";
|
||||||
|
default:
|
||||||
|
return "未知(" + status + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExportBatchAt()
|
||||||
|
{
|
||||||
|
return exportBatchAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportBatchAt(String exportBatchAt)
|
||||||
|
{
|
||||||
|
this.exportBatchAt = exportBatchAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId()
|
||||||
|
{
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId)
|
||||||
|
{
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductIdText()
|
||||||
|
{
|
||||||
|
return productIdText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductIdText(String productIdText)
|
||||||
|
{
|
||||||
|
this.productIdText = productIdText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle()
|
||||||
|
{
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainImage()
|
||||||
|
{
|
||||||
|
return mainImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainImage(String mainImage)
|
||||||
|
{
|
||||||
|
this.mainImage = mainImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPriceFen()
|
||||||
|
{
|
||||||
|
return priceFen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriceFen(Long priceFen)
|
||||||
|
{
|
||||||
|
this.priceFen = priceFen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPriceYuan()
|
||||||
|
{
|
||||||
|
return priceYuan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriceYuan(String priceYuan)
|
||||||
|
{
|
||||||
|
this.priceYuan = priceYuan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStock()
|
||||||
|
{
|
||||||
|
return stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStock(Integer stock)
|
||||||
|
{
|
||||||
|
this.stock = stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getProductStatusCode()
|
||||||
|
{
|
||||||
|
return productStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductStatusCode(Integer productStatusCode)
|
||||||
|
{
|
||||||
|
this.productStatusCode = productStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductStatusLabel()
|
||||||
|
{
|
||||||
|
return productStatusLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductStatusLabel(String productStatusLabel)
|
||||||
|
{
|
||||||
|
this.productStatusLabel = productStatusLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSaleStatusCode()
|
||||||
|
{
|
||||||
|
return saleStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaleStatusCode(Integer saleStatusCode)
|
||||||
|
{
|
||||||
|
this.saleStatusCode = saleStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName()
|
||||||
|
{
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName)
|
||||||
|
{
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppid()
|
||||||
|
{
|
||||||
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppid(String appid)
|
||||||
|
{
|
||||||
|
this.appid = appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductUrl()
|
||||||
|
{
|
||||||
|
return productUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductUrl(String productUrl)
|
||||||
|
{
|
||||||
|
this.productUrl = productUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOnlineTimeUnix()
|
||||||
|
{
|
||||||
|
return onlineTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnlineTimeUnix(Long onlineTimeUnix)
|
||||||
|
{
|
||||||
|
this.onlineTimeUnix = onlineTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOnlineTimeReadable()
|
||||||
|
{
|
||||||
|
return onlineTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnlineTimeReadable(String onlineTimeReadable)
|
||||||
|
{
|
||||||
|
this.onlineTimeReadable = onlineTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOfflineTimeUnix()
|
||||||
|
{
|
||||||
|
return offlineTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOfflineTimeUnix(Long offlineTimeUnix)
|
||||||
|
{
|
||||||
|
this.offlineTimeUnix = offlineTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOfflineTimeReadable()
|
||||||
|
{
|
||||||
|
return offlineTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOfflineTimeReadable(String offlineTimeReadable)
|
||||||
|
{
|
||||||
|
this.offlineTimeReadable = offlineTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSoldTimeUnix()
|
||||||
|
{
|
||||||
|
return soldTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoldTimeUnix(Long soldTimeUnix)
|
||||||
|
{
|
||||||
|
this.soldTimeUnix = soldTimeUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoldTimeReadable()
|
||||||
|
{
|
||||||
|
return soldTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoldTimeReadable(String soldTimeReadable)
|
||||||
|
{
|
||||||
|
this.soldTimeReadable = soldTimeReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreateTimeXyUnix()
|
||||||
|
{
|
||||||
|
return createTimeXyUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTimeXyUnix(Long createTimeXyUnix)
|
||||||
|
{
|
||||||
|
this.createTimeXyUnix = createTimeXyUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateTimeXyReadable()
|
||||||
|
{
|
||||||
|
return createTimeXyReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTimeXyReadable(String createTimeXyReadable)
|
||||||
|
{
|
||||||
|
this.createTimeXyReadable = createTimeXyReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUpdateTimeXyUnix()
|
||||||
|
{
|
||||||
|
return updateTimeXyUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTimeXyUnix(Long updateTimeXyUnix)
|
||||||
|
{
|
||||||
|
this.updateTimeXyUnix = updateTimeXyUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateTimeXyReadable()
|
||||||
|
{
|
||||||
|
return updateTimeXyReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTimeXyReadable(String updateTimeXyReadable)
|
||||||
|
{
|
||||||
|
this.updateTimeXyReadable = updateTimeXyReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark()
|
||||||
|
{
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark)
|
||||||
|
{
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDbCreateTime()
|
||||||
|
{
|
||||||
|
return dbCreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDbCreateTime(Date dbCreateTime)
|
||||||
|
{
|
||||||
|
this.dbCreateTime = dbCreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDbUpdateTime()
|
||||||
|
{
|
||||||
|
return dbUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDbUpdateTime(Date dbUpdateTime)
|
||||||
|
{
|
||||||
|
this.dbUpdateTime = dbUpdateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后返/跟团返现 Excel 上传记录
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class GroupRebateExcelUpload extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String documentTitle;
|
||||||
|
private String originalFilename;
|
||||||
|
/** 若依资源路径,如 /profile/upload/group-rebate-excel/... */
|
||||||
|
private String filePath;
|
||||||
|
private Long fileSize;
|
||||||
|
/** 1 解析失败或未处理 2 已成功解析并写订单 */
|
||||||
|
private Integer importStatus;
|
||||||
|
private Integer dataRows;
|
||||||
|
private Integer updatedOrders;
|
||||||
|
private Integer notFoundCount;
|
||||||
|
private String resultDetailJson;
|
||||||
|
}
|
||||||
@@ -30,6 +30,10 @@ public class JDOrder extends BaseEntity {
|
|||||||
@Excel(name = "型号")
|
@Excel(name = "型号")
|
||||||
private String modelNumber;
|
private String modelNumber;
|
||||||
|
|
||||||
|
/** 列表筛选:型号不含此子串(对应 SQL NOT LIKE %值%),不入库 */
|
||||||
|
@Transient
|
||||||
|
private String modelNumberExclude;
|
||||||
|
|
||||||
/** 链接 */
|
/** 链接 */
|
||||||
@Excel(name = "链接")
|
@Excel(name = "链接")
|
||||||
private String link;
|
private String link;
|
||||||
@@ -145,6 +149,30 @@ public class JDOrder extends BaseEntity {
|
|||||||
@Excel(name = "评价日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "评价日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date reviewPostedDate;
|
private Date reviewPostedDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后返备注(多次导入跟团返现等 Excel 的记录,JSON 数组字符串)
|
||||||
|
* @see com.ruoyi.jarvis.domain.dto.RebateRemarkItem
|
||||||
|
*/
|
||||||
|
private String rebateRemarkJson;
|
||||||
|
|
||||||
|
/** 后返备注中是否存在异常项(1是 0否),便于列表筛选 */
|
||||||
|
private Integer rebateRemarkHasAbnormal;
|
||||||
|
|
||||||
|
/** 售价渠道:direct 直款,xianyu 闲鱼(仅 F 单使用) */
|
||||||
|
private String sellingPriceType;
|
||||||
|
|
||||||
|
/** 售价(对客成交价,可手动改) */
|
||||||
|
private Double sellingPrice;
|
||||||
|
|
||||||
|
/** 利润(可手动改;非 H-TF/F 一般为空) */
|
||||||
|
private Double profit;
|
||||||
|
|
||||||
|
/** 售价是否手动锁定(1 是:不再按型号配置自动回填) */
|
||||||
|
private Integer sellingPriceManual;
|
||||||
|
|
||||||
|
/** 利润是否手动锁定(1 是:保存时不再自动重算) */
|
||||||
|
private Integer profitManual;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ public class ProductJdConfig extends BaseEntity
|
|||||||
/** 佣金(支付) - 支付给下单人的佣金 */
|
/** 佣金(支付) - 支付给下单人的佣金 */
|
||||||
private BigDecimal commissionPay;
|
private BigDecimal commissionPay;
|
||||||
|
|
||||||
|
/** 参考售价(直款渠道) */
|
||||||
|
private BigDecimal sellingPriceDirect;
|
||||||
|
|
||||||
|
/** 参考售价(闲鱼渠道,订单侧仍会 ×0.984 计算实收) */
|
||||||
|
private BigDecimal sellingPriceXianyu;
|
||||||
|
|
||||||
public ProductJdConfig() {
|
public ProductJdConfig() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +82,22 @@ public class ProductJdConfig extends BaseEntity
|
|||||||
this.commissionPay = commissionPay;
|
this.commissionPay = commissionPay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSellingPriceDirect() {
|
||||||
|
return sellingPriceDirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSellingPriceDirect(BigDecimal sellingPriceDirect) {
|
||||||
|
this.sellingPriceDirect = sellingPriceDirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSellingPriceXianyu() {
|
||||||
|
return sellingPriceXianyu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSellingPriceXianyu(BigDecimal sellingPriceXianyu) {
|
||||||
|
this.sellingPriceXianyu = sellingPriceXianyu;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ProductJdConfig{" +
|
return "ProductJdConfig{" +
|
||||||
@@ -84,6 +106,8 @@ public class ProductJdConfig extends BaseEntity
|
|||||||
", commission=" + commission +
|
", commission=" + commission +
|
||||||
", commissionReceive=" + commissionReceive +
|
", commissionReceive=" + commissionReceive +
|
||||||
", commissionPay=" + commissionPay +
|
", commissionPay=" + commissionPay +
|
||||||
|
", sellingPriceDirect=" + sellingPriceDirect +
|
||||||
|
", sellingPriceXianyu=" + sellingPriceXianyu +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class TencentDocBatchPushRecord extends BaseEntity {
|
|||||||
/** 错误数量 */
|
/** 错误数量 */
|
||||||
private Integer errorCount;
|
private Integer errorCount;
|
||||||
|
|
||||||
/** 状态:RUNNING-执行中,SUCCESS-成功,PARTIAL-部分成功,FAILED-失败 */
|
/** 状态:RUNNING-执行中,SUCCESS-成功,PARTIAL-部分成功,FAILED-失败,INTERRUPTED-已中断(超时/未正常结束) */
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 结果消息 */
|
/** 结果消息 */
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微 inbound 消息追踪 wecom_inbound_trace
|
||||||
|
*/
|
||||||
|
public class WeComInboundTrace extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Excel(name = "消息ID")
|
||||||
|
private String msgId;
|
||||||
|
|
||||||
|
@Excel(name = "AgentID")
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
@Excel(name = "CorpId")
|
||||||
|
private String corpId;
|
||||||
|
|
||||||
|
@Excel(name = "发送人UserID")
|
||||||
|
private String fromUserName;
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@Excel(name = "微信发送时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date wxMsgTime;
|
||||||
|
|
||||||
|
private String replyContent;
|
||||||
|
|
||||||
|
@Excel(name = "会话进行中", readConverterExp = "0=否,1=是")
|
||||||
|
private Integer sessionActive;
|
||||||
|
|
||||||
|
@Excel(name = "会话场景")
|
||||||
|
private String sessionScene;
|
||||||
|
|
||||||
|
@Excel(name = "会话步骤")
|
||||||
|
private String sessionStep;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsgId() {
|
||||||
|
return msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMsgId(String msgId) {
|
||||||
|
this.msgId = msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgentId() {
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgentId(String agentId) {
|
||||||
|
this.agentId = agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCorpId() {
|
||||||
|
return corpId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCorpId(String corpId) {
|
||||||
|
this.corpId = corpId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromUserName() {
|
||||||
|
return fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromUserName(String fromUserName) {
|
||||||
|
this.fromUserName = fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getWxMsgTime() {
|
||||||
|
return wxMsgTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWxMsgTime(Date wxMsgTime) {
|
||||||
|
this.wxMsgTime = wxMsgTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReplyContent() {
|
||||||
|
return replyContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyContent(String replyContent) {
|
||||||
|
this.replyContent = replyContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSessionActive() {
|
||||||
|
return sessionActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionActive(Integer sessionActive) {
|
||||||
|
this.sessionActive = sessionActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionScene() {
|
||||||
|
return sessionScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionScene(String sessionScene) {
|
||||||
|
this.sessionScene = sessionScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionStep() {
|
||||||
|
return sessionStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionStep(String sessionStep) {
|
||||||
|
this.sessionStep = sessionStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.ruoyi.jarvis.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微分享链物流任务 wecom_share_link_logistics_job。
|
||||||
|
* 状态含 CANCELLED:不再参与对账入队与队列扫描(订单取消等场景)。
|
||||||
|
*/
|
||||||
|
public class WeComShareLinkLogisticsJob extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Excel(name = "任务Key")
|
||||||
|
private String jobKey;
|
||||||
|
|
||||||
|
@Excel(name = "发送人UserID")
|
||||||
|
private String fromUserName;
|
||||||
|
|
||||||
|
private String trackingUrl;
|
||||||
|
|
||||||
|
/** 用户填写的备注(表字段 remark,避免与 BaseEntity.remark 混淆) */
|
||||||
|
@Excel(name = "用户备注")
|
||||||
|
private String userRemark;
|
||||||
|
|
||||||
|
@Excel(name = "推送接收人")
|
||||||
|
private String touserPush;
|
||||||
|
|
||||||
|
@Excel(name = "状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Excel(name = "运单号")
|
||||||
|
private String waybillNo;
|
||||||
|
|
||||||
|
@Excel(name = "扫描次数")
|
||||||
|
private Integer scanAttempts;
|
||||||
|
|
||||||
|
private String lastNote;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJobKey() {
|
||||||
|
return jobKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJobKey(String jobKey) {
|
||||||
|
this.jobKey = jobKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromUserName() {
|
||||||
|
return fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromUserName(String fromUserName) {
|
||||||
|
this.fromUserName = fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTrackingUrl() {
|
||||||
|
return trackingUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrackingUrl(String trackingUrl) {
|
||||||
|
this.trackingUrl = trackingUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserRemark() {
|
||||||
|
return userRemark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserRemark(String userRemark) {
|
||||||
|
this.userRemark = userRemark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTouserPush() {
|
||||||
|
return touserPush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTouserPush(String touserPush) {
|
||||||
|
this.touserPush = touserPush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWaybillNo() {
|
||||||
|
return waybillNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaybillNo(String waybillNo) {
|
||||||
|
this.waybillNo = waybillNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getScanAttempts() {
|
||||||
|
return scanAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScanAttempts(Integer scanAttempts) {
|
||||||
|
this.scanAttempts = scanAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastNote() {
|
||||||
|
return lastNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastNote(String lastNote) {
|
||||||
|
this.lastNote = lastNote;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,9 +23,13 @@ public class KdocsTokenInfo implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExpired() {
|
public boolean isExpired() {
|
||||||
if (expiresIn == null || createTime == null) {
|
if (accessToken == null || accessToken.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 部分 OAuth 响应不返回 expires_in;若按 null 判过期会导致前端误判「未授权」
|
||||||
|
if (expiresIn == null || createTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
long expireTime = createTime + (expiresIn * 1000L);
|
long expireTime = createTime + (expiresIn * 1000L);
|
||||||
return System.currentTimeMillis() >= (expireTime - 5 * 60 * 1000L);
|
return System.currentTimeMillis() >= (expireTime - 5 * 60 * 1000L);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷录单页:型号下拉项及该型号最近一次落库单的付款 / 后返
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class QuickRecordModelOption {
|
||||||
|
|
||||||
|
private String modelNumber;
|
||||||
|
|
||||||
|
/** 最近一次订单的下单付款金额 */
|
||||||
|
private Double lastPaymentAmount;
|
||||||
|
|
||||||
|
/** 最近一次订单的后返金额 */
|
||||||
|
private Double lastRebateAmount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单「后返备注」中单条记录(多次导入会追加多条)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RebateRemarkItem {
|
||||||
|
|
||||||
|
/** 文档标题,如:跟团+返现 260316 */
|
||||||
|
private String documentTitle;
|
||||||
|
|
||||||
|
/** Excel「是否返现」列原文 */
|
||||||
|
private String whetherRebate;
|
||||||
|
|
||||||
|
/** Excel 返现金额列(优先总共返现)展示值 */
|
||||||
|
private String rebateAmount;
|
||||||
|
|
||||||
|
/** 写入时间戳(毫秒) */
|
||||||
|
private Long uploadTime;
|
||||||
|
|
||||||
|
/** 对应 jd_group_rebate_excel_upload.id,用于删除上传记录时精确撤销本条备注 */
|
||||||
|
private Long uploadRecordId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否异常:导入时「是否返现」列 trim 后不等于「已返现」则为 true,便于列表筛选与着色
|
||||||
|
*/
|
||||||
|
private Boolean abnormal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微侧多轮交互会话(与 Jarvis_java JDUtil 中 interaction_state + Redis 存 JSON 方式对齐)
|
||||||
|
*/
|
||||||
|
public class WeComChatSession {
|
||||||
|
|
||||||
|
public static final String SCENE_JD_LOGISTICS_SHARE = "JD_LOGISTICS_SHARE";
|
||||||
|
public static final String STEP_WAIT_REMARK = "WAIT_REMARK";
|
||||||
|
|
||||||
|
private static final DateTimeFormatter FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
|
/** 业务场景 */
|
||||||
|
private String scene;
|
||||||
|
/** 当前步骤 */
|
||||||
|
private String step;
|
||||||
|
/** 已识别的京东 3.cn 物流短链 */
|
||||||
|
private String logisticsUrl;
|
||||||
|
/** 最近一次交互时间(超时清理用) */
|
||||||
|
private String lastInteractionTime;
|
||||||
|
|
||||||
|
public static WeComChatSession startLogisticsWaitRemark(String logisticsUrl) {
|
||||||
|
WeComChatSession s = new WeComChatSession();
|
||||||
|
s.setScene(SCENE_JD_LOGISTICS_SHARE);
|
||||||
|
s.setStep(STEP_WAIT_REMARK);
|
||||||
|
s.setLogisticsUrl(logisticsUrl);
|
||||||
|
s.touch();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch() {
|
||||||
|
this.lastInteractionTime = LocalDateTime.now().format(FMT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否处于「已收物流链、待备注」步骤。方法名避免使用 isXxx,防止 Fastjson 序列化 Redis JSON 时混入多余布尔字段。
|
||||||
|
*/
|
||||||
|
public boolean matchLogisticsWaitRemark() {
|
||||||
|
return SCENE_JD_LOGISTICS_SHARE.equals(scene) && STEP_WAIT_REMARK.equals(step)
|
||||||
|
&& logisticsUrl != null && !logisticsUrl.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScene() {
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScene(String scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStep() {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStep(String step) {
|
||||||
|
this.step = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogisticsUrl() {
|
||||||
|
return logisticsUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogisticsUrl(String logisticsUrl) {
|
||||||
|
this.logisticsUrl = logisticsUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastInteractionTime() {
|
||||||
|
return lastInteractionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastInteractionTime(String lastInteractionTime) {
|
||||||
|
this.lastInteractionTime = lastInteractionTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微消息经 wxSend 转发至 Jarvis 的请求体。
|
||||||
|
* <p>
|
||||||
|
* <b>身份约定</b>:{@link #fromUserName} 必须为解密后 XML 中的成员 UserID,节点名 {@code FromUserName}(见 {@link com.ruoyi.jarvis.wecom.WeComConvention}),
|
||||||
|
* 全链路(会话 Redis、权限识别目标)均以此为准,勿用 MsgId、纯展示昵称等。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class WeComInboundRequest {
|
||||||
|
/** 成员 UserID,与 XML {@code FromUserName} 同源 */
|
||||||
|
private String fromUserName;
|
||||||
|
private String content;
|
||||||
|
/** 企微 CorpId */
|
||||||
|
private String toUserName;
|
||||||
|
private String agentId;
|
||||||
|
private String msgId;
|
||||||
|
/** 企微 XML CreateTime,秒级 Unix 时间戳(wxSend 传入) */
|
||||||
|
private Long wxCreateTime;
|
||||||
|
|
||||||
|
public String getFromUserName() {
|
||||||
|
return fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromUserName(String fromUserName) {
|
||||||
|
this.fromUserName = fromUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToUserName() {
|
||||||
|
return toUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToUserName(String toUserName) {
|
||||||
|
this.toUserName = toUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgentId() {
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgentId(String agentId) {
|
||||||
|
this.agentId = agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsgId() {
|
||||||
|
return msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMsgId(String msgId) {
|
||||||
|
this.msgId = msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getWxCreateTime() {
|
||||||
|
return wxCreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWxCreateTime(Long wxCreateTime) {
|
||||||
|
this.wxCreateTime = wxCreateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微桥接处理结果:首条走被动回复,其余由 Jarvis 调 wxSend 主动推送。
|
||||||
|
*/
|
||||||
|
public class WeComInboundResult {
|
||||||
|
|
||||||
|
private static final String TRACE_SEP = "\n\n————————————\n\n";
|
||||||
|
|
||||||
|
private final String passiveReply;
|
||||||
|
private final List<String> activePushContents;
|
||||||
|
|
||||||
|
public WeComInboundResult(String passiveReply, List<String> activePushContents) {
|
||||||
|
this.passiveReply = passiveReply != null ? passiveReply : "";
|
||||||
|
this.activePushContents = activePushContents != null
|
||||||
|
? Collections.unmodifiableList(new ArrayList<>(activePushContents))
|
||||||
|
: Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeComInboundResult empty() {
|
||||||
|
return new WeComInboundResult("", Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeComInboundResult passiveOnly(String passive) {
|
||||||
|
return new WeComInboundResult(passive, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassiveReply() {
|
||||||
|
return passiveReply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getActivePushContents() {
|
||||||
|
return activePushContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActivePush() {
|
||||||
|
return !activePushContents.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 追踪表落库:被动 + 主动条文拼在一起便于审计 */
|
||||||
|
public String toTraceFullText() {
|
||||||
|
if (activePushContents.isEmpty()) {
|
||||||
|
return passiveReply;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder(passiveReply);
|
||||||
|
for (String s : activePushContents) {
|
||||||
|
sb.append(TRACE_SEP).append(s != null ? s : "");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ruoyi.jarvis.domain.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微联调/测试数据清理选项(默认全选)
|
||||||
|
*/
|
||||||
|
public class WeComTestDataCleanRequest {
|
||||||
|
|
||||||
|
private Boolean clearTraceTable = true;
|
||||||
|
private Boolean clearWecomSessions = true;
|
||||||
|
private Boolean clearAdhocQueue = true;
|
||||||
|
|
||||||
|
public Boolean getClearTraceTable() {
|
||||||
|
return clearTraceTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearTraceTable(Boolean clearTraceTable) {
|
||||||
|
this.clearTraceTable = clearTraceTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getClearWecomSessions() {
|
||||||
|
return clearWecomSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearWecomSessions(Boolean clearWecomSessions) {
|
||||||
|
this.clearWecomSessions = clearWecomSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getClearAdhocQueue() {
|
||||||
|
return clearAdhocQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearAdhocQueue(Boolean clearAdhocQueue) {
|
||||||
|
this.clearAdhocQueue = clearAdhocQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ruoyi.jarvis.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GoofishNotifyMessage {
|
||||||
|
private String appid;
|
||||||
|
private Long timestamp;
|
||||||
|
private String body;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ErpGoofishOrderEventLogMapper {
|
||||||
|
|
||||||
|
int insert(ErpGoofishOrderEventLog row);
|
||||||
|
|
||||||
|
List<ErpGoofishOrderEventLog> selectByOrderId(@Param("orderId") Long orderId);
|
||||||
|
|
||||||
|
List<ErpGoofishOrderEventLog> selectLogList(ErpGoofishOrderEventLogQuery query);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ErpGoofishOrderMapper {
|
||||||
|
|
||||||
|
ErpGoofishOrder selectById(Long id);
|
||||||
|
|
||||||
|
ErpGoofishOrder selectByAppKeyAndOrderNo(@Param("appKey") String appKey, @Param("orderNo") String orderNo);
|
||||||
|
|
||||||
|
List<ErpGoofishOrder> selectList(ErpGoofishOrder query);
|
||||||
|
|
||||||
|
int insert(ErpGoofishOrder row);
|
||||||
|
|
||||||
|
int update(ErpGoofishOrder row);
|
||||||
|
|
||||||
|
List<ErpGoofishOrder> selectPendingShip(@Param("statuses") List<Integer> statuses, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按闲鱼买家订单号(与 jd_order.third_party_order_no 对齐时)检索,用于京东运单就绪后补绑发货。
|
||||||
|
*/
|
||||||
|
List<ErpGoofishOrder> selectByGoofishOrderNo(@Param("orderNo") String orderNo);
|
||||||
|
|
||||||
|
int resetShipForRetry(@Param("id") Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.ErpOpenConfig;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ErpOpenConfigMapper {
|
||||||
|
|
||||||
|
ErpOpenConfig selectById(Long id);
|
||||||
|
|
||||||
|
ErpOpenConfig selectByAppKey(@Param("appKey") String appKey);
|
||||||
|
|
||||||
|
List<ErpOpenConfig> selectList(ErpOpenConfig query);
|
||||||
|
|
||||||
|
List<ErpOpenConfig> selectEnabledOrderBySort();
|
||||||
|
|
||||||
|
int insert(ErpOpenConfig row);
|
||||||
|
|
||||||
|
int update(ErpOpenConfig row);
|
||||||
|
|
||||||
|
int deleteById(Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface GroupRebateExcelUploadMapper {
|
||||||
|
|
||||||
|
int insertGroupRebateExcelUpload(GroupRebateExcelUpload row);
|
||||||
|
|
||||||
|
int updateGroupRebateExcelUpload(GroupRebateExcelUpload row);
|
||||||
|
|
||||||
|
int deleteGroupRebateExcelUploadById(Long id);
|
||||||
|
|
||||||
|
GroupRebateExcelUpload selectGroupRebateExcelUploadById(Long id);
|
||||||
|
|
||||||
|
List<GroupRebateExcelUpload> selectGroupRebateExcelUploadList(GroupRebateExcelUpload query);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.jarvis.mapper;
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +47,11 @@ public interface JDOrderMapper {
|
|||||||
*/
|
*/
|
||||||
JDOrder selectJDOrderByThirdPartyOrderNo(String thirdPartyOrderNo);
|
JDOrder selectJDOrderByThirdPartyOrderNo(String thirdPartyOrderNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后返备注 JSON 中含指定 uploadRecordId 的订单主键(撤销导入时用)
|
||||||
|
*/
|
||||||
|
List<Long> selectOrderIdsByRebateRemarkUploadRecordId(Long uploadRecordId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除(根据主键ID)
|
* 批量删除(根据主键ID)
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +62,11 @@ public interface JDOrderMapper {
|
|||||||
* @return 订单列表
|
* @return 订单列表
|
||||||
*/
|
*/
|
||||||
List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每个型号取其主键最大的一条订单的付款 / 后返(用于快捷录单下拉回填)
|
||||||
|
*/
|
||||||
|
List<QuickRecordModelOption> selectQuickRecordModelOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -65,4 +65,9 @@ public interface SuperAdminMapper
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int deleteSuperAdminByIds(Long[] ids);
|
public int deleteSuperAdminByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微成员 UserID:匹配 super_admin.wxid,或包含在 touser(逗号分隔)中
|
||||||
|
*/
|
||||||
|
SuperAdmin selectSuperAdminByWecomUserId(String wxid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.jarvis.mapper;
|
|||||||
import com.ruoyi.jarvis.domain.TencentDocBatchPushRecord;
|
import com.ruoyi.jarvis.domain.TencentDocBatchPushRecord;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,5 +42,11 @@ public interface TencentDocBatchPushRecordMapper {
|
|||||||
*/
|
*/
|
||||||
TencentDocBatchPushRecord selectLastSuccessRecord(@Param("fileId") String fileId,
|
TencentDocBatchPushRecord selectLastSuccessRecord(@Param("fileId") String fileId,
|
||||||
@Param("sheetId") String sheetId);
|
@Param("sheetId") String sheetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仍为 RUNNING 且开始时间早于指定时间的批次(用于超时归档)
|
||||||
|
*/
|
||||||
|
List<TencentDocBatchPushRecord> selectRunningRecordsBefore(@Param("fileId") String fileId,
|
||||||
|
@Param("beforeTime") Date beforeTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.WeComInboundTrace;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface WeComInboundTraceMapper {
|
||||||
|
|
||||||
|
int insertWeComInboundTrace(WeComInboundTrace trace);
|
||||||
|
|
||||||
|
WeComInboundTrace selectWeComInboundTraceById(Long id);
|
||||||
|
|
||||||
|
List<WeComInboundTrace> selectWeComInboundTraceList(WeComInboundTrace query);
|
||||||
|
|
||||||
|
int deleteWeComInboundTraceByIds(Long[] ids);
|
||||||
|
|
||||||
|
int deleteAllWeComInboundTrace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reply 中含「已加入查询队列」的消息(企微分享链备注提交成功后的被动回复)
|
||||||
|
*/
|
||||||
|
List<WeComInboundTrace> selectTracesShareLinkRemarkDone(@Param("replyMark") String replyMark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一用户、更早的一条含 3.cn 的消息(用于与备注消息配对出短链)
|
||||||
|
*/
|
||||||
|
WeComInboundTrace selectLatestPriorTraceWith3cnLink(@Param("fromUserName") String fromUserName,
|
||||||
|
@Param("beforeId") long beforeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.ruoyi.jarvis.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface WeComShareLinkLogisticsJobMapper {
|
||||||
|
|
||||||
|
int insertWeComShareLinkLogisticsJob(WeComShareLinkLogisticsJob job);
|
||||||
|
|
||||||
|
int updateByJobKey(@Param("jobKey") String jobKey,
|
||||||
|
@Param("status") String status,
|
||||||
|
@Param("lastNote") String lastNote,
|
||||||
|
@Param("scanAttempts") Integer scanAttempts,
|
||||||
|
@Param("waybillNo") String waybillNo);
|
||||||
|
|
||||||
|
WeComShareLinkLogisticsJob selectByJobKey(String jobKey);
|
||||||
|
|
||||||
|
List<WeComShareLinkLogisticsJob> selectWeComShareLinkLogisticsJobList(WeComShareLinkLogisticsJob query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待自动扫描但可能未在 Redis 队列中的任务(如纯 SQL 补录的 IMPORTED、入队失败后的 PENDING 等)。
|
||||||
|
* 含曾 {@code ABANDONED} 的任务(对账时会先归零扫描次数并改回 {@code WAITING} 再入队)。
|
||||||
|
* 仅 {@code create_time} 在最近一个月内的记录,避免扫到过旧历史。
|
||||||
|
*/
|
||||||
|
List<WeComShareLinkLogisticsJob> selectJobsNeedingQueueReconcile(@Param("limit") int limit);
|
||||||
|
|
||||||
|
int deleteByJobKey(@Param("jobKey") String jobKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人「京外物列表」:最近若干天内的任务,按 id 倒序,可选备注子串筛选。
|
||||||
|
*/
|
||||||
|
List<WeComShareLinkLogisticsJob> selectRecentForInstruction(@Param("remarkKeyword") String remarkKeyword,
|
||||||
|
@Param("days") int days, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人「京外物删」:按备注与短链精确匹配(trim)物理删除,返回删除行数。
|
||||||
|
*/
|
||||||
|
int deleteByRemarkAndTrackingUrl(@Param("remark") String remark, @Param("trackingUrl") String trackingUrl);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.ruoyi.jarvis.mq;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.jarvis.dto.GoofishNotifyMessage;
|
||||||
|
import com.ruoyi.jarvis.service.goofish.GoofishOrderPipeline;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家订单推送消费(需配置 rocketmq.name-server)。
|
||||||
|
* <p>
|
||||||
|
* 在此线程内同步执行 {@link GoofishOrderPipeline#runFullPipeline},避免再投递 {@code @Async} 线程池造成
|
||||||
|
* 「MQ 堆积 + goofishTaskExecutor 排队」的双重延迟;回调 HTTP 已在 {@link com.ruoyi.jarvis.service.impl.ErpGoofishOrderServiceImpl#publishOrProcessNotify} 中快速返回。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(name = "rocketmq.name-server")
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
nameServer = "${rocketmq.name-server}",
|
||||||
|
topic = "${jarvis.goofish-order.mq-topic:jarvis-goofish-erp-order}",
|
||||||
|
consumerGroup = "${jarvis.goofish-order.consumer-group:jarvis-goofish-order-consumer}"
|
||||||
|
)
|
||||||
|
public class GoofishOrderNotifyConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private GoofishOrderPipeline goofishOrderPipeline;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String message) {
|
||||||
|
GoofishNotifyMessage m = JSON.parseObject(message, GoofishNotifyMessage.class);
|
||||||
|
if (m == null || m.getAppid() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONObject body = m.getBody() == null ? new JSONObject() : JSON.parseObject(m.getBody());
|
||||||
|
if (body == null) {
|
||||||
|
body = new JSONObject();
|
||||||
|
}
|
||||||
|
goofishOrderPipeline.runFullPipeline(m.getAppid(), body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IErpGoofishOrderService {
|
||||||
|
|
||||||
|
void publishOrProcessNotify(String appid, Long timestamp, JSONObject body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MQ 消费者或线程池:写库、拉详情、关联京东单、同步运单、尝试发货
|
||||||
|
*/
|
||||||
|
void asyncPipelineAfterNotify(String appid, JSONObject notifyBody);
|
||||||
|
|
||||||
|
List<ErpGoofishOrder> selectList(ErpGoofishOrder query);
|
||||||
|
|
||||||
|
ErpGoofishOrder selectById(Long id);
|
||||||
|
|
||||||
|
int pullOrdersForAppKey(String appKey, int lookbackHours);
|
||||||
|
|
||||||
|
int pullAllEnabled(int lookbackHours);
|
||||||
|
|
||||||
|
/** 按配置的起始天数 + 时间分段,尽量拉全历史订单(update_time) */
|
||||||
|
int pullOrdersForAppKeyFullHistory(String appKey);
|
||||||
|
|
||||||
|
int pullAllEnabledFullHistory();
|
||||||
|
|
||||||
|
void refreshDetail(Long id);
|
||||||
|
|
||||||
|
void retryShip(Long id);
|
||||||
|
|
||||||
|
int syncWaybillAndTryShipBatch(int limit);
|
||||||
|
|
||||||
|
void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 京东单物流扫描已得到运单号并写入 Redis 后调用:同步到闲鱼单并尝试开放平台发货。
|
||||||
|
*/
|
||||||
|
void notifyJdWaybillReady(Long jdOrderId);
|
||||||
|
|
||||||
|
/** 京东物流服务在写 Redis / 企微货主推送后、触发闲鱼同步前记一笔(source=JD_LOGISTICS_PUSH)。 */
|
||||||
|
void traceJdLogisticsPushForGoofish(Long jdOrderId, String waybillNo, String summary);
|
||||||
|
|
||||||
|
/** 是否存在关联本京东单的闲管家订单(用于物流企微走闲鱼自建应用)。 */
|
||||||
|
boolean hasLinkedGoofishOrder(Long jdOrderId);
|
||||||
|
|
||||||
|
/** 订单状态 / 物流 / 发货 变更日志(新→旧) */
|
||||||
|
List<ErpGoofishOrderEventLog> listEventLogsByOrderId(Long orderId);
|
||||||
|
|
||||||
|
/** 全表分页检索(排查用,配合 PageHelper) */
|
||||||
|
List<ErpGoofishOrderEventLog> selectEventLogList(ErpGoofishOrderEventLogQuery query);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.ErpOpenConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IErpOpenConfigService {
|
||||||
|
|
||||||
|
ErpOpenConfig selectById(Long id);
|
||||||
|
|
||||||
|
ErpOpenConfig selectByAppKey(String appKey);
|
||||||
|
|
||||||
|
ErpOpenConfig selectFirstEnabled();
|
||||||
|
|
||||||
|
List<ErpOpenConfig> selectList(ErpOpenConfig query);
|
||||||
|
|
||||||
|
List<ErpOpenConfig> selectEnabledOrderBySort();
|
||||||
|
|
||||||
|
int insert(ErpOpenConfig row);
|
||||||
|
|
||||||
|
int update(ErpOpenConfig row);
|
||||||
|
|
||||||
|
int deleteById(Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IGroupRebateExcelUploadService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 独立事务写入上传记录(导入主流程回滚时仍保留审计记录)
|
||||||
|
*/
|
||||||
|
void saveRecordInNewTransaction(GroupRebateExcelUpload row);
|
||||||
|
|
||||||
|
void updateRecordInNewTransaction(GroupRebateExcelUpload row);
|
||||||
|
|
||||||
|
GroupRebateExcelUpload selectGroupRebateExcelUploadById(Long id);
|
||||||
|
|
||||||
|
List<GroupRebateExcelUpload> selectGroupRebateExcelUploadList(GroupRebateExcelUpload query);
|
||||||
|
}
|
||||||
@@ -28,6 +28,12 @@ public interface IInstructionService {
|
|||||||
*/
|
*/
|
||||||
java.util.List<String> execute(String command, boolean forceGenerate, boolean isFromConsole);
|
java.util.List<String> execute(String command, boolean forceGenerate, boolean isFromConsole);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行文本指令(支持传入企微成员 UserID,用于「京」统计按绑定联盟过滤)
|
||||||
|
* @param wecomUserId 企业微信成员 UserID;控制台等非企微入口传 null(按全局规则统计)
|
||||||
|
*/
|
||||||
|
java.util.List<String> execute(String command, boolean forceGenerate, boolean isFromConsole, String wecomUserId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取历史消息记录
|
* 获取历史消息记录
|
||||||
* @param type 消息类型:request(请求) 或 response(响应)
|
* @param type 消息类型:request(请求) 或 response(响应)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单利润/售价:按分销标识规则计算并写回订单对象(由列表保存前调用)。
|
||||||
|
*/
|
||||||
|
public interface IJDOrderProfitService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分销标识、型号配置、手动标记等,填充售价(自动时)并计算利润。
|
||||||
|
* F / H-TF:利润 = 对客实收(直款=售价,闲鱼=扣点后的到账)-(下单付款 - 后返金额);
|
||||||
|
* H-TF 未配置型号直款价时回退为固定 15 / 凡- 开头 65。
|
||||||
|
* 会修改传入的 {@code order}。
|
||||||
|
*/
|
||||||
|
void recalculate(JDOrder order);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对「利润未手动锁定」的订单按当前库内数据重算售价/利润字段;仅当计算结果与库中不一致时才 UPDATE。
|
||||||
|
*
|
||||||
|
* @return 实际执行 UPDATE 的条数
|
||||||
|
*/
|
||||||
|
int syncAutoProfitIfChanged(List<Long> ids);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.jarvis.service;
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +49,9 @@ public interface IJDOrderService {
|
|||||||
|
|
||||||
/** 查询分销标记为F或PDD且有物流链接的订单列表 */
|
/** 查询分销标记为F或PDD且有物流链接的订单列表 */
|
||||||
java.util.List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
java.util.List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
||||||
|
|
||||||
|
/** 快捷录单:型号及最近一次单的付款 / 后返 */
|
||||||
|
List<QuickRecordModelOption> selectQuickRecordModelOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.ruoyi.jarvis.service;
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 物流信息服务接口
|
* 物流信息服务接口
|
||||||
@@ -13,6 +16,46 @@ public interface ILogisticsService {
|
|||||||
*/
|
*/
|
||||||
boolean fetchLogisticsAndPush(JDOrder order);
|
boolean fetchLogisticsAndPush(JDOrder order);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享类京东物流短链:查运单并通过 wxts 推送到指定 touser(不依赖订单表)
|
||||||
|
*/
|
||||||
|
boolean fetchLogisticsByShareLinkAndPush(String trackingUrl, String remark, String touser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微备注提交后仅入队,由 {@link com.ruoyi.jarvis.task.LogisticsScanTask} 与订单扫描一并拉取物流,避免阻塞 HTTP 回调。
|
||||||
|
*
|
||||||
|
* @param fromWecomUserId 企微消息 FromUserName,入库监控并在未配 touser 时作为推送目标
|
||||||
|
*/
|
||||||
|
void enqueueShareLinkForScan(String trackingUrl, String remark, String touser, String fromWecomUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将已落库任务按与 {@link #enqueueShareLinkForScan} 相同的 JSON 格式推入 Redis(补录、手动失败后重入队等)。
|
||||||
|
* Redis 项中的 attempts 与定时 drain 重试上限对齐,取自 {@link WeComShareLinkLogisticsJob#getScanAttempts()}(仅由 drain 回写递增;管理端手动拉取失败不会垫高该计数)。
|
||||||
|
*/
|
||||||
|
void pushShareLinkJobToRedis(WeComShareLinkLogisticsJob job);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描落库表中仍待处理的任务(含 {@code ABANDONED},对账时会重置计数后再入队;仅 {@code create_time} 一个月内),在限频下补入 Redis。
|
||||||
|
*
|
||||||
|
* @return 本次获得限频锁并成功推入队列的条数
|
||||||
|
*/
|
||||||
|
int reconcileShareLinkJobsIntoPendingQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务内:依次弹出队列并调用 {@link #fetchLogisticsByShareLinkAndPush}。
|
||||||
|
*
|
||||||
|
* @return 本轮从 Redis 弹出并尝试处理的条数(与定时任务尾段逻辑一致)
|
||||||
|
*/
|
||||||
|
int drainPendingShareLinkQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端调试:立刻请求物流接口并尝试推送分享链模板,返回字段与订单「手动获取物流」相近(requestUrl、responseRaw、responseData、pushSent 等)。
|
||||||
|
*/
|
||||||
|
Map<String, Object> adminFetchShareLinkLogisticsDebug(String trackingUrl, String remark, String touser);
|
||||||
|
|
||||||
|
/** 测试清理:删除分享链待扫描队列键 */
|
||||||
|
void clearAdhocPendingQueue();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查订单是否已处理过(Redis中是否有运单号)
|
* 检查订单是否已处理过(Redis中是否有运单号)
|
||||||
* @param orderId 订单ID
|
* @param orderId 订单ID
|
||||||
@@ -26,6 +69,14 @@ public interface ILogisticsService {
|
|||||||
*/
|
*/
|
||||||
HealthCheckResult checkHealth();
|
HealthCheckResult checkHealth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造调用物流解析服务的完整 GET URL(路径与编码与 {@link #fetchLogisticsAndPush} 一致)。
|
||||||
|
* 配置多个 {@code jarvis.server.logistics.base-urls} 时按轮询选取 base,便于内网多实例并行。
|
||||||
|
*
|
||||||
|
* @param logisticsLink 原始物流追踪链接(未编码)
|
||||||
|
*/
|
||||||
|
String buildFetchLogisticsRequestUrl(String logisticsLink);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健康检测结果
|
* 健康检测结果
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,12 +65,43 @@ public interface ISocialMediaService
|
|||||||
com.ruoyi.common.core.domain.AjaxResult deletePromptTemplate(String key);
|
com.ruoyi.common.core.domain.AjaxResult deletePromptTemplate(String key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据标题(+可选型号备注)生成闲鱼文案(代下单、教你下单),不依赖JD接口
|
* 生成闲鱼文案(代下单、教你下单),并可选生成种草文案。
|
||||||
*
|
*
|
||||||
* @param title 商品标题(必填)
|
* request 常用字段:
|
||||||
* @param remark 型号/备注(可选)
|
* - title/remark(兼容旧版手动入口)
|
||||||
* @return 包含代下单、教你下单两种文案的 Map
|
* - generateSeedNote(boolean) 是否额外生成种草文案
|
||||||
|
* - goods_title/goods_model/goods_type(种草文案必传)
|
||||||
|
* - goods_brand/channel_source/official_price/sell_price/warranty/delivery_install(可选)
|
||||||
|
*
|
||||||
|
* @return 包含代下单、教你下单,以及可选 seedNote 的 Map
|
||||||
*/
|
*/
|
||||||
Map<String, Object> generateXianyuWenan(String title, String remark);
|
Map<String, Object> generateXianyuWenan(Map<String, Object> request);
|
||||||
|
|
||||||
|
/** 列出多套大模型接入配置及当前激活的 id(与 Jarvis 共用 Redis) */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult listLlmProfiles();
|
||||||
|
|
||||||
|
/** 获取单套配置(编辑用,密钥脱敏) */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult getLlmProfile(String id);
|
||||||
|
|
||||||
|
/** 新增一套配置 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult createLlmProfile(Map<String, Object> request);
|
||||||
|
|
||||||
|
/** 更新一套配置 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult updateLlmProfile(String id, Map<String, Object> request);
|
||||||
|
|
||||||
|
/** 删除一套配置 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult deleteLlmProfile(String id);
|
||||||
|
|
||||||
|
/** 激活指定 id,Jarvis 调用时使用该套 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult setActiveLlmProfile(String id);
|
||||||
|
|
||||||
|
/** 取消激活(Jarvis 回退 yml 默认 Ollama) */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult clearActiveLlmProfile();
|
||||||
|
|
||||||
|
/** 清空所有套及旧版单键,Jarvis 回退默认 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult resetAllLlmConfig();
|
||||||
|
|
||||||
|
/** 转发 Jarvis 做一次 LLM 连通测试(可指定 profileId 与自定义问题) */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult testLlmProfile(Map<String, Object> request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,5 +41,10 @@ public interface ITencentDocBatchPushService {
|
|||||||
* 获取推送状态和倒计时信息
|
* 获取推送状态和倒计时信息
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getPushStatusAndCountdown();
|
Map<String, Object> getPushStatusAndCountdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将长时间仍处于 RUNNING 的批次归档为 INTERRUPTED(并可选发企微告警,见实现类配置)
|
||||||
|
*/
|
||||||
|
void reconcileStaleRunningRecords(String fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComChatSession;
|
||||||
|
import com.ruoyi.jarvis.wecom.WeComConvention;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微多轮会话(Redis JSON)。键由 {@link WeComConvention#sessionRedisKey(String)} 生成,
|
||||||
|
* 参数 wecomUserId 必须为明文 XML 中的 <b>FromUserName</b>(成员 UserID),与 wxSend 转发字段一致。
|
||||||
|
*/
|
||||||
|
public interface IWeComChatSessionService {
|
||||||
|
|
||||||
|
/** @param wecomUserId 成员 UserID,同 FromUserName */
|
||||||
|
WeComChatSession get(String wecomUserId);
|
||||||
|
|
||||||
|
/** @param wecomUserId 成员 UserID,同 FromUserName */
|
||||||
|
void put(String wecomUserId, WeComChatSession session);
|
||||||
|
|
||||||
|
/** @param wecomUserId 成员 UserID,同 FromUserName */
|
||||||
|
void delete(String wecomUserId);
|
||||||
|
|
||||||
|
/** 测试清理:删除所有 interaction_state:wecom:* */
|
||||||
|
int deleteAllWecomSessionsForTest();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微文本消息业务入口(由 wxSend 通过 HTTPS + 共享密钥调用)
|
||||||
|
*/
|
||||||
|
public interface IWeComInboundService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长文本按企微上限拆成多段(每段 ≤2048 UTF-8 字节):首段被动回复,后续段由控制器异步调 wxSend /wecom/active-push。
|
||||||
|
*/
|
||||||
|
WeComInboundResult handleInbound(WeComInboundRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.WeComInboundTrace;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
||||||
|
import com.ruoyi.jarvis.domain.dto.WeComTestDataCleanRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IWeComInboundTraceService {
|
||||||
|
|
||||||
|
void recordInbound(WeComInboundRequest request, String reply);
|
||||||
|
|
||||||
|
WeComInboundTrace selectWeComInboundTraceById(Long id);
|
||||||
|
|
||||||
|
List<WeComInboundTrace> selectWeComInboundTraceList(WeComInboundTrace query);
|
||||||
|
|
||||||
|
int deleteWeComInboundTraceByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理联调/测试数据,选项见 {@link WeComTestDataCleanRequest}
|
||||||
|
*/
|
||||||
|
Map<String, Object> cleanTestData(WeComTestDataCleanRequest options);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.jarvis.service;
|
||||||
|
|
||||||
|
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IWeComShareLinkLogisticsJobService {
|
||||||
|
|
||||||
|
WeComShareLinkLogisticsJob selectByJobKey(String jobKey);
|
||||||
|
|
||||||
|
List<WeComShareLinkLogisticsJob> selectList(WeComShareLinkLogisticsJob query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wecom_inbound_trace 补录:reply 含「已加入查询队列」且能解析出 3.cn 短链。
|
||||||
|
* jobKey 固定为 tracebf{traceId},可重复执行跳过已存在项。
|
||||||
|
*/
|
||||||
|
Map<String, Object> backfillImportedFromInboundTrace();
|
||||||
|
|
||||||
|
List<WeComShareLinkLogisticsJob> selectRecentForInstruction(String remarkKeyword, int days, int limit);
|
||||||
|
|
||||||
|
int deleteByJobKey(String jobKey);
|
||||||
|
|
||||||
|
int deleteByRemarkAndTrackingUrl(String remark, String trackingUrl);
|
||||||
|
}
|
||||||
@@ -5,11 +5,16 @@ package com.ruoyi.jarvis.service;
|
|||||||
*/
|
*/
|
||||||
public interface IWxSendService {
|
public interface IWxSendService {
|
||||||
/**
|
/**
|
||||||
* 检查微信推送服务健康状态
|
* 检查微信推送服务健康状态(会真实下发一条测试消息,仅用于服务监控页「手动测试」)
|
||||||
* @return 健康状态信息,包含是否健康、状态描述等
|
* @return 健康状态信息,包含是否健康、状态描述等
|
||||||
*/
|
*/
|
||||||
HealthCheckResult checkHealth();
|
HealthCheckResult checkHealth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已配置的微信推送健康检查 URL(展示用,不发起请求)
|
||||||
|
*/
|
||||||
|
String getHealthCheckServiceUrl();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健康检测结果
|
* 健康检测结果
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -59,4 +59,6 @@ public interface SuperAdminService
|
|||||||
public int deleteSuperAdminById(Long id);
|
public int deleteSuperAdminById(Long id);
|
||||||
|
|
||||||
SuperAdmin selectSuperAdminByUnionId(Long unionId);
|
SuperAdmin selectSuperAdminByUnionId(Long unionId);
|
||||||
|
|
||||||
|
SuperAdmin selectSuperAdminByWecomUserId(String wxid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.ruoyi.jarvis.service.erp;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.erp.request.ERPAccount;
|
||||||
|
import com.ruoyi.erp.request.IERPAccount;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpOpenConfig;
|
||||||
|
import com.ruoyi.jarvis.service.IErpOpenConfigService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ErpAccountResolver {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpOpenConfigService erpOpenConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先库表启用配置,其次兼容历史枚举,最后默认胡歌
|
||||||
|
*/
|
||||||
|
public IERPAccount resolve(String appid) {
|
||||||
|
if (StringUtils.isNotEmpty(appid)) {
|
||||||
|
ErpOpenConfig cfg = erpOpenConfigService.selectByAppKey(appid.trim());
|
||||||
|
if (cfg != null && "0".equals(cfg.getStatus())) {
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
for (ERPAccount a : ERPAccount.values()) {
|
||||||
|
if (a.getApiKey().equals(appid.trim())) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErpOpenConfig first = erpOpenConfigService.selectFirstEnabled();
|
||||||
|
if (first != null) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
return ERPAccount.ACCOUNT_HUGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验回调签名时须严格对应 appid,不允许回落到默认账号 */
|
||||||
|
public IERPAccount resolveStrict(String appid) {
|
||||||
|
if (StringUtils.isEmpty(appid)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ErpOpenConfig cfg = erpOpenConfigService.selectByAppKey(appid.trim());
|
||||||
|
if (cfg != null && "0".equals(cfg.getStatus())) {
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
for (ERPAccount a : ERPAccount.values()) {
|
||||||
|
if (a.getApiKey().equals(appid.trim())) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.jarvis.service.goofish;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GoofishNotifyAsyncFacade {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private GoofishOrderPipeline goofishOrderPipeline;
|
||||||
|
|
||||||
|
@Async("goofishTaskExecutor")
|
||||||
|
public void afterNotify(String appid, JSONObject body) {
|
||||||
|
goofishOrderPipeline.runFullPipeline(appid, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.ruoyi.jarvis.service.goofish;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
|
||||||
|
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
|
||||||
|
import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper;
|
||||||
|
import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲管家订单:状态 / 物流 / 发货 变更落库 + SLF4J
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GoofishOrderChangeLogger {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GoofishOrderChangeLogger.class);
|
||||||
|
private static final int MESSAGE_MAX = 1000;
|
||||||
|
|
||||||
|
public static final String TYPE_ORDER = "ORDER_SYNC";
|
||||||
|
public static final String TYPE_LOGISTICS = "LOGISTICS_SYNC";
|
||||||
|
public static final String TYPE_SHIP = "SHIP";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 京东物流扫描:货主通知已由 LogisticsServiceImpl 走闲鱼自建应用或 PDD;此处仅落库,避免再调 wxSend 重复推送。
|
||||||
|
*/
|
||||||
|
public static final String SOURCE_JD_LOGISTICS_PUSH = "JD_LOGISTICS_PUSH";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis 写入本地运单:与后续详情运单合并、发货成功通知重复度高,仅落库便于对账,不推企微。
|
||||||
|
*/
|
||||||
|
public static final String SOURCE_REDIS_WAYBILL = "REDIS_WAYBILL";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ErpGoofishOrderEventLogMapper erpGoofishOrderEventLogMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WxSendGoofishNotifyClient wxSendGoofishNotifyClient;
|
||||||
|
|
||||||
|
public void append(Long orderId, String appKey, String orderNo, String eventType, String source, String message) {
|
||||||
|
append(orderId, appKey, orderNo, eventType, source, message, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param notifyWecom 为 false 时仅写事件日志,不推企微(用于过滤纯对齐类状态)。
|
||||||
|
*/
|
||||||
|
public void append(Long orderId, String appKey, String orderNo, String eventType, String source, String message,
|
||||||
|
boolean notifyWecom) {
|
||||||
|
if (orderId == null || StringUtils.isEmpty(message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String msg = message.length() > MESSAGE_MAX ? message.substring(0, MESSAGE_MAX - 1) + "…" : message;
|
||||||
|
ErpGoofishOrderEventLog row = new ErpGoofishOrderEventLog();
|
||||||
|
row.setOrderId(orderId);
|
||||||
|
row.setAppKey(appKey);
|
||||||
|
row.setOrderNo(orderNo != null ? orderNo : "");
|
||||||
|
row.setEventType(eventType != null ? eventType : "UNKNOWN");
|
||||||
|
row.setSource(source != null ? source : "");
|
||||||
|
row.setMessage(msg);
|
||||||
|
row.setCreateTime(DateUtils.getNowDate());
|
||||||
|
try {
|
||||||
|
erpGoofishOrderEventLogMapper.insert(row);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("闲管家订单事件日志写入失败 orderId={} {}", orderId, e.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("[goofish-order-event] orderId={} orderNo={} type={} source={} {}", orderId, orderNo, eventType, source, msg);
|
||||||
|
if (SOURCE_JD_LOGISTICS_PUSH.equals(source) || SOURCE_REDIS_WAYBILL.equals(source)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!notifyWecom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
wxSendGoofishNotifyClient.notifyGoofishEvent(orderNo, eventType, source, msg);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.debug("闲鱼订单事件 wxSend 通知跳过 err={}", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并摘要前后对比:订单状态/退款状态、平台运单与快递、本地运单。
|
||||||
|
*
|
||||||
|
* @param beforeSnap 合并前从 row 拷贝的跟踪字段快照(可不含 id)
|
||||||
|
* @param afterRow 合并并 apply 后的 row
|
||||||
|
*/
|
||||||
|
public void logSummaryMergeDiff(ErpGoofishOrder beforeSnap, ErpGoofishOrder afterRow, String source) {
|
||||||
|
if (afterRow == null || afterRow.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RowSnap b = RowSnap.from(beforeSnap);
|
||||||
|
RowSnap a = RowSnap.from(afterRow);
|
||||||
|
|
||||||
|
boolean refundDiff = !Objects.equals(b.refundStatus, a.refundStatus);
|
||||||
|
boolean orderDiff = !Objects.equals(b.orderStatus, a.orderStatus);
|
||||||
|
|
||||||
|
List<String> orderParts = new ArrayList<>();
|
||||||
|
if (orderDiff) {
|
||||||
|
orderParts.add("订单状态 " + GoofishStatusLabels.orderStatusChangeForNotify(b.orderStatus, a.orderStatus));
|
||||||
|
}
|
||||||
|
if (refundDiff) {
|
||||||
|
orderParts.add("退款状态 " + GoofishStatusLabels.refundStatusChange(b.refundStatus, a.refundStatus));
|
||||||
|
}
|
||||||
|
if (!orderParts.isEmpty()) {
|
||||||
|
boolean notifyWecom = refundDiff || (orderDiff
|
||||||
|
&& GoofishStatusLabels.isWxNotifiableOrderStatusChange(b.orderStatus, a.orderStatus));
|
||||||
|
append(afterRow.getId(), afterRow.getAppKey(), afterRow.getOrderNo(), TYPE_ORDER, source,
|
||||||
|
String.join(";", orderParts), notifyWecom);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> logParts = new ArrayList<>();
|
||||||
|
if (!eqStr(b.detailWaybillNo, a.detailWaybillNo)) {
|
||||||
|
logParts.add("平台运单 " + str(b.detailWaybillNo) + "→" + str(a.detailWaybillNo));
|
||||||
|
}
|
||||||
|
if (!eqStr(b.detailExpressCode, a.detailExpressCode)) {
|
||||||
|
logParts.add("快递编码 " + str(b.detailExpressCode) + "→" + str(a.detailExpressCode));
|
||||||
|
}
|
||||||
|
if (!eqStr(b.detailExpressName, a.detailExpressName)) {
|
||||||
|
logParts.add("快递名称 " + str(b.detailExpressName) + "→" + str(a.detailExpressName));
|
||||||
|
}
|
||||||
|
if (!eqStr(b.localWaybillNo, a.localWaybillNo)) {
|
||||||
|
logParts.add("本地运单 " + str(b.localWaybillNo) + "→" + str(a.localWaybillNo));
|
||||||
|
}
|
||||||
|
if (!logParts.isEmpty()) {
|
||||||
|
append(afterRow.getId(), afterRow.getAppKey(), afterRow.getOrderNo(), TYPE_LOGISTICS, source,
|
||||||
|
String.join(";", logParts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String str(Object o) {
|
||||||
|
return o == null ? "null" : String.valueOf(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean eqStr(String x, String y) {
|
||||||
|
String a = x == null ? "" : x.trim();
|
||||||
|
String b = y == null ? "" : y.trim();
|
||||||
|
return Objects.equals(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RowSnap {
|
||||||
|
Integer orderStatus;
|
||||||
|
Integer refundStatus;
|
||||||
|
String detailWaybillNo;
|
||||||
|
String detailExpressCode;
|
||||||
|
String detailExpressName;
|
||||||
|
String localWaybillNo;
|
||||||
|
|
||||||
|
static RowSnap from(ErpGoofishOrder r) {
|
||||||
|
RowSnap s = new RowSnap();
|
||||||
|
if (r == null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
s.orderStatus = r.getOrderStatus();
|
||||||
|
s.refundStatus = r.getRefundStatus();
|
||||||
|
s.detailWaybillNo = r.getDetailWaybillNo();
|
||||||
|
s.detailExpressCode = r.getDetailExpressCode();
|
||||||
|
s.detailExpressName = r.getDetailExpressName();
|
||||||
|
s.localWaybillNo = r.getLocalWaybillNo();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user