commit
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
package com.budwk.app.base.sms.impl.ypi;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.sms.SmsService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.impl.PropertiesProxy;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学校统一消息V4发送服务实现。
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
public class UnifiedMessageV4ServiceImpl implements SmsService {
|
||||
|
||||
private static final String TOKEN_CACHE_KEY = "unified_message_v4:token:";
|
||||
private static final int TOKEN_CACHE_SECONDS = 270;
|
||||
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
@Inject
|
||||
private PropertiesProxy conf;
|
||||
|
||||
@Override
|
||||
public void send(String loginName, String content) {
|
||||
send(loginName, "智慧工会", content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String loginName, String title, String content) {
|
||||
send(loginName, title, content, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String loginName, String title, String content, String link) {
|
||||
if (StrUtil.isBlank(loginName)) {
|
||||
throw new BaseException("学校统一消息发送失败:接收人工号不能为空");
|
||||
}
|
||||
sendMessage(title, content, createReceivers(List.of(new Receiver(loginName, loginName))), link, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massSend(List<String> loginNames, String title, String content, String link) {
|
||||
if (loginNames == null || loginNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Receiver> receivers = loginNames.stream()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.map(loginName -> new Receiver(loginName, loginName))
|
||||
.toList();
|
||||
sendMessage(title, content, createReceivers(receivers), link, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massSendByUsers(List<Sys_user> users, String title, String content, String pcUrl, String mobileUrl) {
|
||||
if (users == null || users.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Receiver> receivers = users.stream()
|
||||
.filter(user -> StrUtil.isNotBlank(user.getLoginname()) && StrUtil.isNotBlank(user.getUsername()))
|
||||
.map(user -> new Receiver(user.getLoginname(), user.getUsername()))
|
||||
.toList();
|
||||
if (receivers.isEmpty()) {
|
||||
throw new BaseException("学校统一消息发送失败:接收人工号或姓名不能为空");
|
||||
}
|
||||
sendMessage(title, content, createReceivers(receivers), pcUrl, mobileUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用统一消息V4普通消息接口;有链接时按外链模式同时传递PC和手机端地址。
|
||||
*/
|
||||
private void sendMessage(String title, String content, JSONArray receivers, String pcUrl, String mobileUrl) {
|
||||
String token = getAccessToken();
|
||||
boolean hasLink = StrUtil.isNotBlank(pcUrl) || StrUtil.isNotBlank(mobileUrl);
|
||||
JSONObject body = JSONUtil.createObj();
|
||||
body.set("title", title);
|
||||
body.set("messageMode", hasLink ? "2" : "1");
|
||||
body.set("content", content);
|
||||
body.set("sendWay", "2");
|
||||
body.set("channel", JSONUtil.toJsonStr(List.of(getRequiredConfig("school-message.channel"))));
|
||||
body.set("receive", JSONUtil.toJsonStr(receivers));
|
||||
if (hasLink) {
|
||||
body.set("url", StrUtil.blankToDefault(pcUrl, mobileUrl));
|
||||
body.set("mobileUrl", StrUtil.blankToDefault(mobileUrl, pcUrl));
|
||||
}
|
||||
|
||||
String sendUrl = getRequiredConfig("school-message.send-url");
|
||||
String requestBody = JSONUtil.toJsonStr(body);
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(sendUrl)
|
||||
.header("domain-name", getRequiredConfig("school-message.domain-name"))
|
||||
.header("token", token)
|
||||
.body(requestBody);
|
||||
addInterceptDomainHeader(request);
|
||||
String responseBody = request.execute().body();
|
||||
JSONObject response = JSONUtil.parseObj(responseBody);
|
||||
if (response.getInt("state", 0) != 200) {
|
||||
throw new BaseException("学校统一消息发送失败:{}", response.getStr("message", responseBody));
|
||||
}
|
||||
log.info("学校统一消息发送成功,接收人数量={},response={}", receivers.size(), responseBody);
|
||||
} catch (BaseException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("学校统一消息发送异常,url={},body={}", sendUrl, requestBody, e);
|
||||
throw new BaseException("学校统一消息发送异常:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学校统一消息V4令牌;文档有效期为300秒,缓存270秒避免临界过期。
|
||||
*/
|
||||
private String getAccessToken() {
|
||||
String cacheKey = TOKEN_CACHE_KEY + getRequiredConfig("school-message.app-id");
|
||||
String cachedToken = redisService.get(cacheKey);
|
||||
if (StrUtil.isNotBlank(cachedToken)) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
String tokenUrl = getRequiredConfig("school-message.token-url");
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(tokenUrl)
|
||||
.header("domain-name", getRequiredConfig("school-message.domain-name"))
|
||||
.header("appId", getRequiredConfig("school-message.app-id"))
|
||||
.header("appPassword", getRequiredConfig("school-message.app-password"));
|
||||
addInterceptDomainHeader(request);
|
||||
String responseBody = request.execute().body();
|
||||
JSONObject response = JSONUtil.parseObj(responseBody);
|
||||
JSONObject data = response.getJSONObject("data");
|
||||
String token = data == null ? null : data.getStr("token");
|
||||
if (response.getInt("state", 0) != 200 || StrUtil.isBlank(token)) {
|
||||
throw new BaseException("学校统一消息获取令牌失败:{}", response.getStr("message", responseBody));
|
||||
}
|
||||
redisService.setex(cacheKey, TOKEN_CACHE_SECONDS, token);
|
||||
return token;
|
||||
} catch (BaseException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("学校统一消息获取令牌异常,url={}", tokenUrl, e);
|
||||
throw new BaseException("学校统一消息获取令牌异常:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可信域名仅在学校平台配置白名单时传递,未配置则不发送该请求头。
|
||||
*/
|
||||
private void addInterceptDomainHeader(HttpRequest request) {
|
||||
String interceptDomainName = conf.get("school-message.intercept-domain-name", "");
|
||||
if (StrUtil.isNotBlank(interceptDomainName)) {
|
||||
request.header("intercept-domain-name", interceptDomainName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校统一消息所需配置缺失时阻止发送,避免向错误的网关地址提交数据。
|
||||
*/
|
||||
private String getRequiredConfig(String key) {
|
||||
String value = conf.get(key);
|
||||
if (StrUtil.isBlank(value)) {
|
||||
throw new BaseException("学校统一消息配置{}为空", key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一消息V4接收人字段固定为工号和姓名。
|
||||
*/
|
||||
private JSONArray createReceivers(List<Receiver> receiverList) {
|
||||
JSONArray receivers = JSONUtil.createArray();
|
||||
receiverList.forEach(receiver -> receivers.add(JSONUtil.createObj()
|
||||
.set("userCode", receiver.userCode())
|
||||
.set("userName", receiver.userName())));
|
||||
return receivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收人传输对象,避免调用方遗漏统一消息要求的姓名字段。
|
||||
*/
|
||||
private record Receiver(String userCode, String userName) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user