diff --git a/backend/src/main/java/com/designhub/service/DingTalkService.java b/backend/src/main/java/com/designhub/service/DingTalkService.java index 5202535..67d0cf3 100644 --- a/backend/src/main/java/com/designhub/service/DingTalkService.java +++ b/backend/src/main/java/com/designhub/service/DingTalkService.java @@ -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 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 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 msg = new HashMap<>(); msg.put("msgtype", "action_card"); + Map 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 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 atMobiles) { @@ -285,14 +321,36 @@ public class DingTalkService { webhook = webhook + "×tamp=" + timestamp + "&sign=" + sign; } - Map message = new HashMap<>(); - message.put("msgtype", "markdown"); - Map markdown = new HashMap<>(); - markdown.put("title", title); - markdown.put("text", "### " + title + "\n\n" + content); - message.put("markdown", markdown); + // 获取官网地址 + String siteUrl = getSiteUrl(); - // 添加@人功能 + // 构建卡片消息(ActionCard格式) + Map message = new HashMap<>(); + message.put("msgtype", "action_card"); + + Map 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 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={}", diff --git a/backend/src/main/resources/db/migration/V11__add_site_url_config.sql b/backend/src/main/resources/db/migration/V11__add_site_url_config.sql new file mode 100644 index 0000000..4090fed --- /dev/null +++ b/backend/src/main/resources/db/migration/V11__add_site_url_config.sql @@ -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'; + diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 846eec8..afa3f56 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -62,6 +62,18 @@ + + +
+ + 钉钉卡片通知将跳转到此地址 +
+
+ 功能开关 { 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) diff --git a/第三方系统完结任务数据接口文档.md b/第三方系统完结任务数据接口文档.md index 4ec6dbd..08b1400 100644 --- a/第三方系统完结任务数据接口文档.md +++ b/第三方系统完结任务数据接口文档.md @@ -15,7 +15,7 @@ ### 调用流程 ``` -1. 任务系统生成任务号(如 TASK202601030184) +1. 任务系统生成任务编号(如 TASK202601030184) 2. 员工在素材平台上传素材时填入任务号 3. 员工在任务系统点击「完成」 4. 任务系统调用查询接口获取素材详情和下载链接