diff --git a/src/main/java/cn/van333/wxsend/business/controller/WeComActivePushController.java b/src/main/java/cn/van333/wxsend/business/controller/WeComActivePushController.java index f185bf4..8fba0fd 100644 --- a/src/main/java/cn/van333/wxsend/business/controller/WeComActivePushController.java +++ b/src/main/java/cn/van333/wxsend/business/controller/WeComActivePushController.java @@ -16,6 +16,9 @@ import javax.annotation.Resource; * Jarvis 服务端调用的企微应用文本「主动推送」入口(与被动回调 {@link WeComCallbackController} 分离)。 *
* Header: X-WxSend-WeCom-Push-Secret 与 {@code jarvis.wecom.push-secret} 一致 + *
+ * 闲管家订单状态/自动发货等:{@code POST /wecom/goofish-active-push},使用自建应用 + * {@code qywx.app.goofishAgentId},Header {@link #HEADER_GOOFISH_PUSH_SECRET} */ @RestController @RequestMapping("/wecom") @@ -25,9 +28,15 @@ public class WeComActivePushController { public static final String HEADER_PUSH_SECRET = "X-WxSend-WeCom-Push-Secret"; + /** 本地 / ruoyi 调用「闲鱼通知应用」推送时携带,与 {@code jarvis.wecom.goofish-push-secret} 一致 */ + public static final String HEADER_GOOFISH_PUSH_SECRET = "X-WxSend-Goofish-Push-Secret"; + @Value("${jarvis.wecom.push-secret:}") private String pushSecret; + @Value("${jarvis.wecom.goofish-push-secret:}") + private String goofishPushSecret; + @Resource private WeComApplicationTextPushService weComApplicationTextPushService; @@ -52,4 +61,36 @@ public class WeComActivePushController { return R.error("推送失败: " + e.getMessage()); } } + + /** + * 闲鱼订单状态变更、自动发货等:使用企微应用 {@code qywx.app.goofishAgentId}(如 1000013)发文本给成员。 + *
+ * 示例:
+ * curl -X POST http://127.0.0.1:36699/wecom/goofish-active-push \
+ * -H "Content-Type: application/json" \
+ * -H "X-WxSend-Goofish-Push-Secret: <与 jarvis.wecom.goofish-push-secret 一致>" \
+ * -d '{"toUser":"UserID","content":"订单 xxx 状态已更新"}'
+ *
+ */
+ @PostMapping(value = "/goofish-active-push", consumes = MediaType.APPLICATION_JSON_VALUE)
+ public R goofishActivePush(
+ @RequestHeader(value = HEADER_GOOFISH_PUSH_SECRET, required = false) String secret,
+ @RequestBody WeComActivePushRequest body) {
+ if (!StringUtils.hasText(goofishPushSecret) || !goofishPushSecret.equals(secret)) {
+ return R.error(403, "拒绝访问");
+ }
+ if (body == null || !StringUtils.hasText(body.getToUser())) {
+ return R.error(400, "toUser 必填");
+ }
+ if (!StringUtils.hasText(body.getContent())) {
+ return R.error(400, "content 不能为空");
+ }
+ try {
+ weComApplicationTextPushService.sendGoofishTextToUser(body.getToUser().trim(), body.getContent());
+ return R.ok("sent");
+ } catch (Exception e) {
+ log.warn("企微闲鱼应用推送失败 toUser={} err={}", body.getToUser(), e.toString());
+ return R.error("推送失败: " + e.getMessage());
+ }
+ }
}
diff --git a/src/main/java/cn/van333/wxsend/business/service/WeComApplicationTextPushService.java b/src/main/java/cn/van333/wxsend/business/service/WeComApplicationTextPushService.java
index 695b0eb..53487b2 100644
--- a/src/main/java/cn/van333/wxsend/business/service/WeComApplicationTextPushService.java
+++ b/src/main/java/cn/van333/wxsend/business/service/WeComApplicationTextPushService.java
@@ -44,10 +44,34 @@ public class WeComApplicationTextPushService {
@Value("${qywx.app.secret:}")
private String corpSecret;
+ /** 闲管家订单状态/发货通知等:独立自建应用(与默认 qywx.app.agentId 可不同) */
+ @Value("${qywx.app.goofishAgentId:}")
+ private String goofishAgentId;
+
+ @Value("${qywx.app.goofishSecret:}")
+ private String goofishSecret;
+
public void sendTextToUser(String toUser, String content) throws Exception {
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecret) || StrUtil.isBlank(agentId)) {
throw new IllegalStateException("未配置 qywx.app.corpId / agentId / secret,无法主动发应用消息");
}
+ sendTextWithAgent(toUser, content, agentId, corpSecret);
+ }
+
+ /**
+ * 使用「闲鱼/Goofish」自建应用({@code qywx.app.goofishAgentId}+{@code goofishSecret})下发文本。
+ */
+ public void sendGoofishTextToUser(String toUser, String content) throws Exception {
+ if (StrUtil.isBlank(corpId) || StrUtil.isBlank(goofishSecret) || StrUtil.isBlank(goofishAgentId)) {
+ throw new IllegalStateException("未配置 qywx.app.goofishAgentId / goofishSecret,无法下发闲鱼通知");
+ }
+ sendTextWithAgent(toUser, content, goofishAgentId, goofishSecret);
+ }
+
+ private void sendTextWithAgent(String toUser, String content, String useAgentId, String useCorpSecret) throws Exception {
+ if (StrUtil.isBlank(corpId) || StrUtil.isBlank(useCorpSecret) || StrUtil.isBlank(useAgentId)) {
+ throw new IllegalStateException("corpId / agentId / secret 不能为空");
+ }
if (StrUtil.isBlank(toUser)) {
throw new IllegalArgumentException("toUser 不能为空");
}
@@ -56,7 +80,7 @@ public class WeComApplicationTextPushService {
text = text.substring(0, CONTENT_MAX) + "…";
}
- String token = getAccessToken();
+ String token = getAccessToken(useCorpSecret);
if (StrUtil.isBlank(token)) {
throw new IllegalStateException("获取企微 access_token 失败");
}
@@ -65,9 +89,9 @@ public class WeComApplicationTextPushService {
jsonMap.put("touser", toUser.trim());
int agentIdInt;
try {
- agentIdInt = Integer.parseInt(agentId.trim());
+ agentIdInt = Integer.parseInt(useAgentId.trim());
} catch (NumberFormatException e) {
- throw new IllegalStateException("qywx.app.agentId 非法: " + agentId);
+ throw new IllegalStateException("agentId 非法: " + useAgentId);
}
jsonMap.put("agentid", agentIdInt);
jsonMap.put("safe", 0);
@@ -82,7 +106,7 @@ public class WeComApplicationTextPushService {
.timeout(30_000)
.execute()
.body();
- log.info("企微主动文本下发 toUser={} respSnippet={}", toUser,
+ log.info("企微主动文本下发 agentId={} toUser={} respSnippet={}", useAgentId, toUser,
resp != null && resp.length() > 200 ? resp.substring(0, 200) + "..." : resp);
SendRespones sendResp = JSON.parseObject(resp, SendRespones.class);
@@ -92,18 +116,18 @@ public class WeComApplicationTextPushService {
}
}
- private String getAccessToken() {
- if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecret)) {
+ private String getAccessToken(String corpSecretForApp) {
+ if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecretForApp)) {
return null;
}
- String cacheKey = WX_ACCESS_TOKEN + corpSecret;
+ String cacheKey = WX_ACCESS_TOKEN + corpSecretForApp;
String cached = redisCache.getCacheObject(cacheKey);
if (StrUtil.isNotEmpty(cached)) {
return cached;
}
Map