feat: 钉钉通知改为卡片消息格式,支持跳转官网
This commit is contained in:
parent
a520890284
commit
a62afec617
@ -35,8 +35,10 @@ public class DingTalkService {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final DepartmentMapper departmentMapper;
|
||||
private final SystemConfigService systemConfigService;
|
||||
|
||||
private static final String DINGTALK_API_BASE = "https://oapi.dingtalk.com";
|
||||
private static final String DEFAULT_SITE_URL = "https://your-domain.com";
|
||||
|
||||
/**
|
||||
* 获取钉钉配置(优先获取启用的配置,如果没有则获取最新的配置)
|
||||
@ -195,9 +197,35 @@ public class DingTalkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作通知
|
||||
* 获取配置的官网地址
|
||||
*/
|
||||
private String getSiteUrl() {
|
||||
String siteUrl = systemConfigService.getConfigValue("site_url");
|
||||
if (siteUrl == null || siteUrl.trim().isEmpty()) {
|
||||
siteUrl = systemConfigService.getConfigValue("dingtalk_redirect_url");
|
||||
}
|
||||
if (siteUrl == null || siteUrl.trim().isEmpty()) {
|
||||
siteUrl = DEFAULT_SITE_URL;
|
||||
}
|
||||
return siteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作通知(卡片消息格式)
|
||||
* 用户点击卡片可跳转到官网
|
||||
*/
|
||||
public boolean sendWorkNotification(List<String> userIds, String title, String content) {
|
||||
return sendWorkNotification(userIds, title, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作通知(卡片消息格式,支持自定义跳转链接)
|
||||
* @param userIds 接收用户ID列表
|
||||
* @param title 消息标题
|
||||
* @param content 消息内容(支持markdown格式)
|
||||
* @param customUrl 自定义跳转链接,为空则使用官网地址
|
||||
*/
|
||||
public boolean sendWorkNotification(List<String> userIds, String title, String content, String customUrl) {
|
||||
DingTalkConfig config = getConfig();
|
||||
if (config == null) {
|
||||
log.warn("钉钉未配置,跳过发送通知");
|
||||
@ -213,14 +241,21 @@ public class DingTalkService {
|
||||
String url = String.format("%s/topapi/message/corpconversation/asyncsend_v2?access_token=%s",
|
||||
DINGTALK_API_BASE, accessToken);
|
||||
|
||||
// 获取跳转链接
|
||||
String jumpUrl = (customUrl != null && !customUrl.trim().isEmpty()) ? customUrl : getSiteUrl();
|
||||
|
||||
// 构建卡片消息
|
||||
Map<String, Object> msg = new HashMap<>();
|
||||
msg.put("msgtype", "action_card");
|
||||
|
||||
Map<String, String> actionCard = new HashMap<>();
|
||||
actionCard.put("title", title);
|
||||
actionCard.put("markdown", content);
|
||||
actionCard.put("single_title", "查看详情");
|
||||
// 构建 markdown 正文,包含标题和内容
|
||||
String markdown = "### " + title + "\n\n" + content + "\n\n---\n*点击下方按钮查看详情*";
|
||||
actionCard.put("markdown", markdown);
|
||||
actionCard.put("single_title", "查看详情 »");
|
||||
actionCard.put("single_url", "dingtalk://dingtalkclient/page/link?url=" +
|
||||
URLEncoder.encode("https://your-domain.com", StandardCharsets.UTF_8.toString()));
|
||||
URLEncoder.encode(jumpUrl, StandardCharsets.UTF_8.toString()));
|
||||
msg.put("action_card", actionCard);
|
||||
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
@ -238,7 +273,7 @@ public class DingTalkService {
|
||||
if (body != null) {
|
||||
Object errcode = body.get("errcode");
|
||||
if (errcode != null && (errcode.equals(0) || errcode.equals(0L) || "0".equals(errcode.toString()))) {
|
||||
log.info("钉钉工作通知发送成功: userIds={}", userIds);
|
||||
log.info("钉钉工作通知发送成功: userIds={}, title={}", userIds, title);
|
||||
return true;
|
||||
} else {
|
||||
log.error("钉钉工作通知发送失败: errcode={}, errmsg={}, body={}",
|
||||
@ -256,16 +291,17 @@ public class DingTalkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送机器人消息
|
||||
* 发送机器人消息(卡片格式)
|
||||
*/
|
||||
public boolean sendRobotMessage(String title, String content) {
|
||||
return sendRobotMessage(title, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送机器人消息,支持@指定人(通过手机号)
|
||||
* 发送机器人消息(卡片格式),支持@指定人(通过手机号)
|
||||
* 使用整体跳转ActionCard样式,点击可跳转到官网
|
||||
* @param title 消息标题
|
||||
* @param content 消息内容
|
||||
* @param content 消息内容(支持markdown格式)
|
||||
* @param atMobiles 要@的手机号列表,null或空表示不@任何人
|
||||
*/
|
||||
public boolean sendRobotMessage(String title, String content, List<String> atMobiles) {
|
||||
@ -285,14 +321,36 @@ public class DingTalkService {
|
||||
webhook = webhook + "×tamp=" + timestamp + "&sign=" + sign;
|
||||
}
|
||||
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("msgtype", "markdown");
|
||||
Map<String, String> markdown = new HashMap<>();
|
||||
markdown.put("title", title);
|
||||
markdown.put("text", "### " + title + "\n\n" + content);
|
||||
message.put("markdown", markdown);
|
||||
// 获取官网地址
|
||||
String siteUrl = getSiteUrl();
|
||||
|
||||
// 添加@人功能
|
||||
// 构建卡片消息(ActionCard格式)
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("msgtype", "action_card");
|
||||
|
||||
Map<String, Object> actionCard = new HashMap<>();
|
||||
actionCard.put("title", title);
|
||||
|
||||
// 构建 markdown 正文
|
||||
StringBuilder markdownBuilder = new StringBuilder();
|
||||
markdownBuilder.append("### ").append(title).append("\n\n");
|
||||
markdownBuilder.append(content);
|
||||
|
||||
// 如果有@人,在内容中添加@标记
|
||||
if (atMobiles != null && !atMobiles.isEmpty()) {
|
||||
markdownBuilder.append("\n\n");
|
||||
for (String mobile : atMobiles) {
|
||||
markdownBuilder.append("@").append(mobile).append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
actionCard.put("markdown", markdownBuilder.toString());
|
||||
actionCard.put("single_title", "查看详情 »");
|
||||
actionCard.put("single_url", siteUrl);
|
||||
|
||||
message.put("action_card", actionCard);
|
||||
|
||||
// 添加@人功能(对于action_card,@功能可能有限制)
|
||||
if (atMobiles != null && !atMobiles.isEmpty()) {
|
||||
Map<String, Object> at = new HashMap<>();
|
||||
at.put("atMobiles", atMobiles);
|
||||
@ -311,7 +369,7 @@ public class DingTalkService {
|
||||
if (body != null) {
|
||||
Object errcode = body.get("errcode");
|
||||
if (errcode != null && (errcode.equals(0) || errcode.equals(0L) || "0".equals(errcode.toString()))) {
|
||||
log.info("钉钉机器人消息发送成功: title={}", title);
|
||||
log.info("钉钉机器人卡片消息发送成功: title={}", title);
|
||||
return true;
|
||||
} else {
|
||||
log.error("钉钉机器人消息发送失败: errcode={}, errmsg={}, body={}",
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
-- 添加官网地址配置(用于钉钉卡片消息跳转)
|
||||
INSERT INTO `sys_config` (`config_key`, `config_value`, `config_group`, `config_name`, `description`, `value_type`, `built_in`, `sort_order`, `create_time`, `update_time`, `deleted`)
|
||||
VALUES ('site_url', '', 'system', '官网地址', '系统官网地址,用于钉钉通知卡片跳转,例如:https://example.com', 'string', 1, 10, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE `description` = '系统官网地址,用于钉钉通知卡片跳转,例如:https://example.com';
|
||||
|
||||
@ -62,6 +62,18 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="官网地址">
|
||||
<a-input
|
||||
v-model:value="siteSettings.siteUrl"
|
||||
placeholder="请输入官网地址,例如:https://example.com"
|
||||
size="large"
|
||||
/>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #888;">
|
||||
<Icon name="info" size="sm" color="#888" style="margin-right: 4px;" />
|
||||
钉钉卡片通知将跳转到此地址
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-divider orientation="left">功能开关</a-divider>
|
||||
<a-form-item label="注册功能">
|
||||
<a-switch
|
||||
@ -2060,9 +2072,11 @@ const router = useRouter()
|
||||
const siteSettings = reactive({
|
||||
siteName: 'DesignHub',
|
||||
logoUrl: '',
|
||||
siteUrl: '',
|
||||
registerEnabled: true,
|
||||
siteNameConfigId: null as number | null,
|
||||
logoConfigId: null as number | null,
|
||||
siteUrlConfigId: null as number | null,
|
||||
registerEnabledConfigId: null as number | null
|
||||
})
|
||||
|
||||
@ -3776,6 +3790,10 @@ const loadSiteSettings = async () => {
|
||||
const response = await getSystemConfigsByGroup('site')
|
||||
const configs = response.data || []
|
||||
|
||||
// 同时获取system分组的配置(包含site_url)
|
||||
const systemResponse = await getSystemConfigsByGroup('system')
|
||||
const systemConfigs = systemResponse.data || []
|
||||
|
||||
// 查找网站名称和Logo配置
|
||||
const siteNameConfig = configs.find((c: any) => c.configKey === 'site.name')
|
||||
const logoConfig = configs.find((c: any) => c.configKey === 'site.logo')
|
||||
@ -3794,6 +3812,15 @@ const loadSiteSettings = async () => {
|
||||
siteSettings.logoUrl = siteSettingsStore.logoUrl
|
||||
}
|
||||
|
||||
// 查找官网地址配置(在system分组中)
|
||||
const siteUrlConfig = systemConfigs.find((c: any) => c.configKey === 'site_url')
|
||||
if (siteUrlConfig) {
|
||||
siteSettings.siteUrl = siteUrlConfig.configValue || ''
|
||||
siteSettings.siteUrlConfigId = siteUrlConfig.id
|
||||
} else {
|
||||
siteSettings.siteUrl = ''
|
||||
}
|
||||
|
||||
// 查找注册功能开关配置
|
||||
const registerConfig = configs.find((c: any) => c.configKey === 'site.register.enabled')
|
||||
if (registerConfig) {
|
||||
@ -3807,6 +3834,7 @@ const loadSiteSettings = async () => {
|
||||
// 如果API失败,尝试从store读取
|
||||
siteSettings.siteName = siteSettingsStore.siteName
|
||||
siteSettings.logoUrl = siteSettingsStore.logoUrl
|
||||
siteSettings.siteUrl = ''
|
||||
siteSettings.registerEnabled = true // 默认开启
|
||||
} finally {
|
||||
loadingSiteSettings.value = false
|
||||
@ -3906,6 +3934,29 @@ const handleSaveSiteSettings = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存官网地址
|
||||
if (siteSettings.siteUrlConfigId) {
|
||||
// 如果配置已存在,更新
|
||||
await updateSystemConfig(siteSettings.siteUrlConfigId, {
|
||||
configValue: siteSettings.siteUrl
|
||||
})
|
||||
} else if (siteSettings.siteUrl) {
|
||||
// 如果配置不存在但有值,创建
|
||||
const response = await createSystemConfig({
|
||||
configKey: 'site_url',
|
||||
configValue: siteSettings.siteUrl,
|
||||
configGroup: 'system',
|
||||
configName: '官网地址',
|
||||
description: '系统官网地址,用于钉钉通知卡片跳转',
|
||||
valueType: 'string',
|
||||
builtIn: 1,
|
||||
sortOrder: 10
|
||||
})
|
||||
if (response.data && response.data.id) {
|
||||
siteSettings.siteUrlConfigId = response.data.id
|
||||
}
|
||||
}
|
||||
|
||||
// 更新全局store(这会自动更新所有使用store的组件)
|
||||
siteSettingsStore.updateSiteSettings(siteSettings.siteName, siteSettings.logoUrl)
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
### 调用流程
|
||||
|
||||
```
|
||||
1. 任务系统生成任务号(如 TASK202601030184)
|
||||
1. 任务系统生成任务编号(如 TASK202601030184)
|
||||
2. 员工在素材平台上传素材时填入任务号
|
||||
3. 员工在任务系统点击「完成」
|
||||
4. 任务系统调用查询接口获取素材详情和下载链接
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user