1
This commit is contained in:
@@ -16,6 +16,9 @@ import javax.annotation.Resource;
|
|||||||
* Jarvis 服务端调用的企微应用文本「主动推送」入口(与被动回调 {@link WeComCallbackController} 分离)。
|
* Jarvis 服务端调用的企微应用文本「主动推送」入口(与被动回调 {@link WeComCallbackController} 分离)。
|
||||||
* <p>
|
* <p>
|
||||||
* Header: X-WxSend-WeCom-Push-Secret 与 {@code jarvis.wecom.push-secret} 一致
|
* Header: X-WxSend-WeCom-Push-Secret 与 {@code jarvis.wecom.push-secret} 一致
|
||||||
|
* <p>
|
||||||
|
* 闲管家订单状态/自动发货等:{@code POST /wecom/goofish-active-push},使用自建应用
|
||||||
|
* {@code qywx.app.goofishAgentId},Header {@link #HEADER_GOOFISH_PUSH_SECRET}
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/wecom")
|
@RequestMapping("/wecom")
|
||||||
@@ -25,9 +28,15 @@ public class WeComActivePushController {
|
|||||||
|
|
||||||
public static final String HEADER_PUSH_SECRET = "X-WxSend-WeCom-Push-Secret";
|
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:}")
|
@Value("${jarvis.wecom.push-secret:}")
|
||||||
private String pushSecret;
|
private String pushSecret;
|
||||||
|
|
||||||
|
@Value("${jarvis.wecom.goofish-push-secret:}")
|
||||||
|
private String goofishPushSecret;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private WeComApplicationTextPushService weComApplicationTextPushService;
|
private WeComApplicationTextPushService weComApplicationTextPushService;
|
||||||
|
|
||||||
@@ -52,4 +61,36 @@ public class WeComActivePushController {
|
|||||||
return R.error("推送失败: " + e.getMessage());
|
return R.error("推送失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闲鱼订单状态变更、自动发货等:使用企微应用 {@code qywx.app.goofishAgentId}(如 1000013)发文本给成员。
|
||||||
|
* <p>
|
||||||
|
* 示例:<pre>
|
||||||
|
* 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 状态已更新"}'
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,34 @@ public class WeComApplicationTextPushService {
|
|||||||
@Value("${qywx.app.secret:}")
|
@Value("${qywx.app.secret:}")
|
||||||
private String corpSecret;
|
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 {
|
public void sendTextToUser(String toUser, String content) throws Exception {
|
||||||
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecret) || StrUtil.isBlank(agentId)) {
|
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecret) || StrUtil.isBlank(agentId)) {
|
||||||
throw new IllegalStateException("未配置 qywx.app.corpId / agentId / secret,无法主动发应用消息");
|
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)) {
|
if (StrUtil.isBlank(toUser)) {
|
||||||
throw new IllegalArgumentException("toUser 不能为空");
|
throw new IllegalArgumentException("toUser 不能为空");
|
||||||
}
|
}
|
||||||
@@ -56,7 +80,7 @@ public class WeComApplicationTextPushService {
|
|||||||
text = text.substring(0, CONTENT_MAX) + "…";
|
text = text.substring(0, CONTENT_MAX) + "…";
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = getAccessToken();
|
String token = getAccessToken(useCorpSecret);
|
||||||
if (StrUtil.isBlank(token)) {
|
if (StrUtil.isBlank(token)) {
|
||||||
throw new IllegalStateException("获取企微 access_token 失败");
|
throw new IllegalStateException("获取企微 access_token 失败");
|
||||||
}
|
}
|
||||||
@@ -65,9 +89,9 @@ public class WeComApplicationTextPushService {
|
|||||||
jsonMap.put("touser", toUser.trim());
|
jsonMap.put("touser", toUser.trim());
|
||||||
int agentIdInt;
|
int agentIdInt;
|
||||||
try {
|
try {
|
||||||
agentIdInt = Integer.parseInt(agentId.trim());
|
agentIdInt = Integer.parseInt(useAgentId.trim());
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalStateException("qywx.app.agentId 非法: " + agentId);
|
throw new IllegalStateException("agentId 非法: " + useAgentId);
|
||||||
}
|
}
|
||||||
jsonMap.put("agentid", agentIdInt);
|
jsonMap.put("agentid", agentIdInt);
|
||||||
jsonMap.put("safe", 0);
|
jsonMap.put("safe", 0);
|
||||||
@@ -82,7 +106,7 @@ public class WeComApplicationTextPushService {
|
|||||||
.timeout(30_000)
|
.timeout(30_000)
|
||||||
.execute()
|
.execute()
|
||||||
.body();
|
.body();
|
||||||
log.info("企微主动文本下发 toUser={} respSnippet={}", toUser,
|
log.info("企微主动文本下发 agentId={} toUser={} respSnippet={}", useAgentId, toUser,
|
||||||
resp != null && resp.length() > 200 ? resp.substring(0, 200) + "..." : resp);
|
resp != null && resp.length() > 200 ? resp.substring(0, 200) + "..." : resp);
|
||||||
|
|
||||||
SendRespones sendResp = JSON.parseObject(resp, SendRespones.class);
|
SendRespones sendResp = JSON.parseObject(resp, SendRespones.class);
|
||||||
@@ -92,18 +116,18 @@ public class WeComApplicationTextPushService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAccessToken() {
|
private String getAccessToken(String corpSecretForApp) {
|
||||||
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecret)) {
|
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecretForApp)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String cacheKey = WX_ACCESS_TOKEN + corpSecret;
|
String cacheKey = WX_ACCESS_TOKEN + corpSecretForApp;
|
||||||
String cached = redisCache.getCacheObject(cacheKey);
|
String cached = redisCache.getCacheObject(cacheKey);
|
||||||
if (StrUtil.isNotEmpty(cached)) {
|
if (StrUtil.isNotEmpty(cached)) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
Map<String, String> map = new HashMap<>();
|
Map<String, String> map = new HashMap<>();
|
||||||
map.put("corpid", corpId.trim());
|
map.put("corpid", corpId.trim());
|
||||||
map.put("corpsecret", corpSecret.trim());
|
map.put("corpsecret", corpSecretForApp.trim());
|
||||||
String jsonStr = JSON.toJSONString(map);
|
String jsonStr = JSON.toJSONString(map);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
String responseStr = HttpRequest.post(GET_TOKEN)
|
String responseStr = HttpRequest.post(GET_TOKEN)
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ jarvis:
|
|||||||
shared-secret: jarvis_wecom_bridge_change_me
|
shared-secret: jarvis_wecom_bridge_change_me
|
||||||
# Jarvis 调 POST /wecom/active-push 时的 Header,须与 ruoyi jarvis.wecom.push-secret 一致
|
# Jarvis 调 POST /wecom/active-push 时的 Header,须与 ruoyi jarvis.wecom.push-secret 一致
|
||||||
push-secret: jarvis_wecom_push_change_me
|
push-secret: jarvis_wecom_push_change_me
|
||||||
|
# 本地/ruoyi 调 POST /wecom/goofish-active-push 时 Header: X-WxSend-Goofish-Push-Secret
|
||||||
|
goofish-push-secret: jarvis_wecom_goofish_push_change_me
|
||||||
|
|
||||||
qywx:
|
qywx:
|
||||||
webhook:
|
webhook:
|
||||||
@@ -84,5 +86,9 @@ qywx:
|
|||||||
agentId: "1000012"
|
agentId: "1000012"
|
||||||
# 与 agentId 对应的应用 Secret(企微后台应用管理),用于 access_token + message/send
|
# 与 agentId 对应的应用 Secret(企微后台应用管理),用于 access_token + message/send
|
||||||
secret: ""
|
secret: ""
|
||||||
|
# 闲鱼订单通知自建应用:POST /wecom/goofish-active-push(corpid 仍用上面 corpId)
|
||||||
|
# 生产建议仅通过环境变量注入,勿将真实 Secret 提交仓库
|
||||||
|
goofishAgentId: "1000013"
|
||||||
|
goofishSecret: "${QYWX_GOOFISH_SECRET:hINY8boNFjY3lypOKbcukE8wYnNS9wnXeonB4iTO1rA}"
|
||||||
token: "34NXCtEjkl"
|
token: "34NXCtEjkl"
|
||||||
encodingAESKey: "bNlE8IhjU34CfXflcBhW3gXPwr8xaEDEhnpUvChvw5i"
|
encodingAESKey: "bNlE8IhjU34CfXflcBhW3gXPwr8xaEDEhnpUvChvw5i"
|
||||||
|
|||||||
Reference in New Issue
Block a user