飞书提醒
parent
179bc4b0dd
commit
7fcdedfd41
|
|
@ -6,6 +6,7 @@ import com.ruoyi.common.core.controller.XktBaseController;
|
|||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageVO;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.framework.notice.fs.FsNotice;
|
||||
import com.ruoyi.web.controller.xkt.vo.BasePageVO;
|
||||
import com.ruoyi.web.controller.xkt.vo.account.*;
|
||||
import com.ruoyi.xkt.dto.account.*;
|
||||
|
|
@ -35,6 +36,8 @@ public class AssetController extends XktBaseController {
|
|||
private IAssetService assetService;
|
||||
@Autowired
|
||||
private AliPaymentMangerImpl aliPaymentManger;
|
||||
@Autowired
|
||||
private FsNotice fsNotice;
|
||||
|
||||
@ApiOperation(value = "档口资产")
|
||||
@GetMapping(value = "store/current")
|
||||
|
|
@ -89,8 +92,8 @@ public class AssetController extends XktBaseController {
|
|||
//付款单到账
|
||||
if (success) {
|
||||
assetService.withdrawSuccess(prepareResult.getFinanceBillId());
|
||||
}else {
|
||||
//TODO 告警-人工介入
|
||||
} else {
|
||||
fsNotice.sendMsg2DefaultChat("档口提现异常", "付款单: " + prepareResult.getFinanceBillId());
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package com.ruoyi.framework.notice;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author liangyq
|
||||
* @date 2025-05-18 17:15
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractNotice {
|
||||
|
||||
@Autowired
|
||||
protected RedisCache redisCache;
|
||||
|
||||
protected String httpGet(String url, Map<String, String> headers, Map<String, Object> paramMap) {
|
||||
HttpRequest httpRequest = HttpUtil.createGet(url);
|
||||
if (MapUtil.isNotEmpty(headers)) {
|
||||
httpRequest.addHeaders(headers);
|
||||
}
|
||||
if (MapUtil.isNotEmpty(paramMap)) {
|
||||
httpRequest.form(paramMap);
|
||||
}
|
||||
return httpRequest.execute().body();
|
||||
}
|
||||
|
||||
protected String httpPost(String url, Map<String, String> headers, String body) {
|
||||
HttpRequest httpRequest = HttpUtil.createPost(url);
|
||||
if (MapUtil.isNotEmpty(headers)) {
|
||||
httpRequest.addHeaders(headers);
|
||||
}
|
||||
if (StrUtil.isNotBlank(body)) {
|
||||
httpRequest.body(body);
|
||||
}
|
||||
return httpRequest.execute().body();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.ruoyi.framework.notice.fs;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.framework.notice.AbstractNotice;
|
||||
import com.ruoyi.framework.notice.fs.entity.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author liangyq
|
||||
* @date 2025-05-18 17:14
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FsNotice extends AbstractNotice {
|
||||
|
||||
public static final String FEI_SHU_ROBOT_TOKEN_CACHE_PREFIX = "FS_ROBOT_TOKEN_";
|
||||
public static final String FEI_SHU_ROBOT_TOKEN_REQUEST_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/";
|
||||
public static final String FEI_SHU_ROBOT_SEND_MSG_URL = "https://open.feishu.cn/open-apis/message/v4/send/";
|
||||
|
||||
@Value("${fs.robot.appId:cli_a8a3175e84f8500b}")
|
||||
private String defaultAppId;
|
||||
@Value("${fs.robot.appSecret:AZTpyNiW4hpLRXcDhrdjJcr8Z404gVqf}")
|
||||
private String defaultAppSecret;
|
||||
@Value("${fs.robot.defaultChatId:oc_4f66ce3cf471f50aac6c3fdae7f0aad9}")
|
||||
private String defaultChatId;
|
||||
|
||||
/**
|
||||
* 发送消息给默认会话
|
||||
*
|
||||
* @param title
|
||||
* @param content
|
||||
*/
|
||||
public void sendMsg2DefaultChat(String title, String content) {
|
||||
ThreadUtil.execAsync(() -> sendMsg(title, content, new String[]{defaultChatId}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param title
|
||||
* @param content
|
||||
* @param chartIds
|
||||
* @param params
|
||||
*/
|
||||
private void sendMsg(String title, String content, String[] chartIds, Object... params) {
|
||||
try {
|
||||
List<FeiShuMsg> msgs = createFeiShuMsg(title, content, chartIds, params);
|
||||
for (FeiShuMsg msg : msgs) {
|
||||
String response = httpPost(FEI_SHU_ROBOT_SEND_MSG_URL, createDefaultHeader(), JSON.toJSONString(msg));
|
||||
log.info("[FS]发送消息: msg={}, response={}", msg, response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[FS]发送消息异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建飞书消息
|
||||
*
|
||||
* @param title
|
||||
* @param content
|
||||
* @param chartIds
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
private List<FeiShuMsg> createFeiShuMsg(String title, String content, String[] chartIds, Object... params) {
|
||||
List<FeiShuMsg> msgs = new ArrayList<>(chartIds.length);
|
||||
for (String chartId : chartIds) {
|
||||
//标题和内容
|
||||
FeiShuMsg.ZhCn zhCn = new FeiShuMsg.ZhCn();
|
||||
zhCn.setTitle(title);
|
||||
|
||||
String textContent = ArrayUtil.isNotEmpty(params) ? StrUtil.indexedFormat(content, params) : content;
|
||||
List<List<FeiShuMsg.BaseField>> contentField = Collections.singletonList(Arrays
|
||||
.asList(FeiShuTextField.createText(textContent), FeiShuAtField.createAtAll()));
|
||||
zhCn.setContent(contentField);
|
||||
//消息体
|
||||
FeiShuMsg feiShuMsg = new FeiShuMsg();
|
||||
feiShuMsg.setChatId(chartId);
|
||||
feiShuMsg.setMsgType("post");
|
||||
feiShuMsg.setContent(FeiShuMsg.Content.builder().post(FeiShuMsg.Post.builder().zhCn(zhCn).build()).build());
|
||||
msgs.add(feiShuMsg);
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 飞书请求头
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Map<String, String> createDefaultHeader() {
|
||||
return createHeaderWithAuth(getToken(defaultAppId, defaultAppSecret));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带有认证信息的请求头
|
||||
*
|
||||
* @param token 飞书token
|
||||
* @return
|
||||
*/
|
||||
private Map<String, String> createHeaderWithAuth(String token) {
|
||||
Map<String, String> headers = new HashMap<>(2);
|
||||
headers.put("Content-Type", "application/json; charset=utf-8");
|
||||
if (StrUtil.isNotEmpty(token)) {
|
||||
//值格式:"Bearer access_token"
|
||||
headers.put("Authorization", "Bearer ".concat(token));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*
|
||||
* @param appId 应用id
|
||||
* @param appSecret 应用秘钥
|
||||
* @return token
|
||||
*/
|
||||
private String getToken(String appId, String appSecret) {
|
||||
String token = redisCache.getCacheObject(FEI_SHU_ROBOT_TOKEN_CACHE_PREFIX.concat(appId));
|
||||
if (StrUtil.isEmpty(token)) {
|
||||
synchronized (this) {
|
||||
token = redisCache.getCacheObject(FEI_SHU_ROBOT_TOKEN_CACHE_PREFIX.concat(appId));
|
||||
if (StrUtil.isEmpty(token)) {
|
||||
token = refreshToken(appId, appSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从飞书服务器获取token,并刷新缓存
|
||||
*
|
||||
* @param appId 应用id
|
||||
* @param appSecret 秘钥
|
||||
* @return
|
||||
*/
|
||||
private String refreshToken(String appId, String appSecret) {
|
||||
String response = httpPost(FEI_SHU_ROBOT_TOKEN_REQUEST_URL, null,
|
||||
JSON.toJSONString(FeiShuTokenReq.builder().appId(appId).appSecret(appSecret).build()));
|
||||
FeiShuTokenResp respToken = StrUtil.isEmpty(response) ?
|
||||
null : JSON.parseObject(response, FeiShuTokenResp.class);
|
||||
if (Objects.isNull(respToken) || StrUtil.isEmpty(respToken.getTenantAccessToken())) {
|
||||
throw new ServiceException("[FS]TOKEN获取失败");
|
||||
}
|
||||
String token = respToken.getTenantAccessToken();
|
||||
//Token 有效期为 2 小时,在此期间调用该接口 token 不会改变。
|
||||
//当 token 有效期小于 30 分的时候,再次请求获取 token 的时候,会生成一个新的 token,与此同时老的 token 依然有效。
|
||||
int expire = Integer.parseInt(respToken.getExpire()) - 30 * 60;
|
||||
redisCache.setCacheObject(FEI_SHU_ROBOT_TOKEN_CACHE_PREFIX.concat(appId), token, expire > 0 ? expire : 1,
|
||||
TimeUnit.SECONDS);
|
||||
log.info("[FS]TOKEN缓存刷新成功appId={},expire={}", appId, expire);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.ruoyi.framework.notice.fs.entity;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author liangyuqi
|
||||
* @date 2021/7/14 15:20
|
||||
*/
|
||||
@Data
|
||||
public class FeiShuAtField extends FeiShuMsg.BaseField {
|
||||
private String tag = "at";
|
||||
@JSONField(name = "user_id")
|
||||
private String userId;
|
||||
|
||||
public static FeiShuAtField createAtAll() {
|
||||
return createAt("all");
|
||||
}
|
||||
|
||||
public static FeiShuAtField createAt(String userId) {
|
||||
FeiShuAtField feiShuAtField = new FeiShuAtField();
|
||||
feiShuAtField.setUserId(userId);
|
||||
return feiShuAtField;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.ruoyi.framework.notice.fs.entity;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 飞书消息
|
||||
*
|
||||
* {
|
||||
* "open_id":"ou_5ad573a6411d72b8305fda3a9c15c70e",
|
||||
* "root_id":"om_40eb06e7b84dc71c03e009ad3c754195",
|
||||
* "chat_id":"oc_5ad11d72b830411d72b836c20",
|
||||
* "user_id": "92e39a99",
|
||||
* "email":"fanlv@gmail.com",
|
||||
* "msg_type":"post",
|
||||
* "content":{
|
||||
* "post":{
|
||||
* "zh_cn":{}, // option
|
||||
* "ja_jp":{}, // option
|
||||
* "en_us":{} // option
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @author liangyuqi
|
||||
* @date 2021/7/14 10:52
|
||||
*/
|
||||
@Data
|
||||
public class FeiShuMsg {
|
||||
@JSONField(name = "open_id")
|
||||
private String openId;
|
||||
@JSONField(name = "root_id")
|
||||
private String rootId;
|
||||
@JSONField(name = "chat_id")
|
||||
private String chatId;
|
||||
@JSONField(name = "user_id")
|
||||
private String userId;
|
||||
private String email;
|
||||
@JSONField(name = "msg_type")
|
||||
private String msgType;
|
||||
private Content content;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Content {
|
||||
private Post post;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Post {
|
||||
@JSONField(name = "zh_cn")
|
||||
private ZhCn zhCn;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ZhCn {
|
||||
private String title;
|
||||
private List<List<BaseField>> content;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static abstract class BaseField {
|
||||
private String tag;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.ruoyi.framework.notice.fs.entity;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author liangyuqi
|
||||
* @date 2021/7/14 15:20
|
||||
*/
|
||||
@Data
|
||||
public class FeiShuTextField extends FeiShuMsg.BaseField {
|
||||
@JSONField(name = "un_escape")
|
||||
private boolean unEscape = true;
|
||||
private String tag = "text";
|
||||
private String text;
|
||||
|
||||
public static FeiShuTextField createText(String text) {
|
||||
FeiShuTextField feiShuTextField = new FeiShuTextField();
|
||||
feiShuTextField.setText(text);
|
||||
return feiShuTextField;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.ruoyi.framework.notice.fs.entity;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author liangyuqi
|
||||
* @date 2021/7/14 14:12
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FeiShuTokenReq {
|
||||
@JSONField(name = "app_id")
|
||||
private String appId;
|
||||
@JSONField(name = "app_secret")
|
||||
private String appSecret;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.ruoyi.framework.notice.fs.entity;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author liangyuqi
|
||||
* @date 2021/7/14 14:12
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FeiShuTokenResp {
|
||||
@JSONField(name = "tenant_access_token")
|
||||
private String tenantAccessToken;
|
||||
@JSONField(name = "expire")
|
||||
private String expire;
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.ruoyi.xkt.manager.impl;
|
||||
|
||||
import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
|
@ -12,8 +11,8 @@ import com.alipay.api.diagnosis.DiagnosisUtils;
|
|||
import com.alipay.api.domain.*;
|
||||
import com.alipay.api.request.*;
|
||||
import com.alipay.api.response.*;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.framework.notice.fs.FsNotice;
|
||||
import com.ruoyi.xkt.domain.StoreOrderDetail;
|
||||
import com.ruoyi.xkt.dto.finance.AlipayReqDTO;
|
||||
import com.ruoyi.xkt.dto.order.StoreOrderExt;
|
||||
|
|
@ -25,6 +24,7 @@ import com.ruoyi.xkt.enums.EPayStatus;
|
|||
import com.ruoyi.xkt.manager.PaymentManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -85,6 +85,9 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
@Value("${alipay.gatewayUrl:https://openapi-sandbox.dl.alipaydev.com/gateway.do}")
|
||||
private String gatewayUrl;
|
||||
|
||||
@Autowired
|
||||
private FsNotice fsNotice;
|
||||
|
||||
@Override
|
||||
public EPayChannel channel() {
|
||||
return EPayChannel.ALI_PAY;
|
||||
|
|
@ -165,6 +168,8 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
default:
|
||||
throw new ServiceException("未知的支付来源");
|
||||
}
|
||||
//告警
|
||||
fsNotice.sendMsg2DefaultChat("支付发起异常", JSON.toJSONString(reqDTO));
|
||||
throw new ServiceException("支付发起失败");
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +221,8 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
} catch (Exception e) {
|
||||
log.error("退款异常", e);
|
||||
}
|
||||
//告警
|
||||
fsNotice.sendMsg2DefaultChat("退款发起异常", JSON.toJSONString(orderRefund));
|
||||
throw new ServiceException("退款失败");
|
||||
}
|
||||
|
||||
|
|
@ -262,6 +269,8 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
} catch (Exception e) {
|
||||
log.error("查询订单支付结果异常", e);
|
||||
}
|
||||
//告警
|
||||
fsNotice.sendMsg2DefaultChat("查询订单支付结果异常", JSON.toJSONString(model));
|
||||
throw new ServiceException("查询订单支付结果失败");
|
||||
}
|
||||
|
||||
|
|
@ -314,6 +323,8 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
} catch (Exception e) {
|
||||
log.error("支付宝转账异常", e);
|
||||
}
|
||||
//告警
|
||||
fsNotice.sendMsg2DefaultChat("支付宝转账异常", JSON.toJSONString(model));
|
||||
throw new ServiceException("支付宝转账失败");
|
||||
}
|
||||
|
||||
|
|
@ -362,6 +373,8 @@ public class AliPaymentMangerImpl implements PaymentManager {
|
|||
} catch (Exception e) {
|
||||
log.error("查询支付宝转账结果异常", e);
|
||||
}
|
||||
//告警
|
||||
fsNotice.sendMsg2DefaultChat("查询支付宝转账结果异常", JSON.toJSONString(model));
|
||||
throw new ServiceException("查询支付宝转账结果失败");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue