package com.designhub.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.common.Result; import com.designhub.entity.DingTalkConfig; import com.designhub.entity.User; import com.designhub.mapper.UserMapper; import com.designhub.service.DingTalkService; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 钉钉集成 Controller */ @Slf4j @Tag(name = "钉钉集成", description = "钉钉集成管理API") @RestController @RequestMapping("/api/dingtalk") @RequiredArgsConstructor public class DingTalkController { private final DingTalkService dingTalkService; private final ObjectMapper objectMapper; private final UserMapper userMapper; @Operation(summary = "获取钉钉配置") @GetMapping("/config") public Result getConfig() { DingTalkConfig config = dingTalkService.getConfig(); // 如果没有配置,返回默认配置(仅用于前端显示) if (config == null) { config = new DingTalkConfig(); config.setName("默认配置"); config.setEnabled(0); } // 隐藏敏感信息 if (config.getAppSecret() != null && !config.getAppSecret().isEmpty()) { config.setAppSecret("******"); } if (config.getRobotSecret() != null && !config.getRobotSecret().isEmpty()) { config.setRobotSecret("******"); } config.setAccessToken(null); return Result.success(config); } @Operation(summary = "保存钉钉配置") @PostMapping("/config") public Result saveConfig(@RequestBody Map configMap) { try { // 处理enabledFeatures字段 Object enabledFeaturesObj = configMap.get("enabledFeatures"); String enabledFeaturesJson = "{}"; // 默认为空JSON对象 if (enabledFeaturesObj != null) { if (enabledFeaturesObj instanceof String) { enabledFeaturesJson = (String) enabledFeaturesObj; } else if (enabledFeaturesObj instanceof Map && !((Map) enabledFeaturesObj).isEmpty()) { // 转换为JSON字符串 enabledFeaturesJson = objectMapper.writeValueAsString(enabledFeaturesObj); } else { // 如果是空对象或null,使用空JSON对象 enabledFeaturesJson = "{}"; } } // 构建配置对象 DingTalkConfig config = new DingTalkConfig(); config.setId(configMap.get("id") != null ? ((Number) configMap.get("id")).longValue() : null); config.setName((String) configMap.get("name")); config.setAgentId((String) configMap.get("agentId")); config.setAppKey((String) configMap.get("appKey")); config.setAppSecret((String) configMap.get("appSecret")); config.setCorpId((String) configMap.get("corpId")); config.setRobotWebhook((String) configMap.get("robotWebhook")); config.setRobotSecret((String) configMap.get("robotSecret")); config.setEnabled(configMap.get("enabled") != null ? ((Number) configMap.get("enabled")).intValue() : 1); config.setEnabledFeatures(enabledFeaturesJson); // 如果前端传来的appSecret是占位符,则保留原有的secret if ("******".equals(config.getAppSecret())) { DingTalkConfig existingConfig = dingTalkService.getConfig(); if (existingConfig != null) { config.setAppSecret(existingConfig.getAppSecret()); } } if ("******".equals(config.getRobotSecret())) { DingTalkConfig existingConfig = dingTalkService.getConfig(); if (existingConfig != null) { config.setRobotSecret(existingConfig.getRobotSecret()); } } DingTalkConfig saved = dingTalkService.saveConfig(config); // 返回时隐藏敏感信息 if (saved.getAppSecret() != null && !saved.getAppSecret().isEmpty()) { saved.setAppSecret("******"); } if (saved.getRobotSecret() != null && !saved.getRobotSecret().isEmpty()) { saved.setRobotSecret("******"); } saved.setAccessToken(null); return Result.success(saved); } catch (Exception e) { return Result.error("保存配置失败: " + e.getMessage()); } } @Operation(summary = "测试钉钉连接") @PostMapping("/test") public Result testConnection(@RequestBody DingTalkConfig config) { try { // 如果前端传来的appSecret是占位符,则从数据库获取真实的secret if ("******".equals(config.getAppSecret())) { DingTalkConfig existingConfig = dingTalkService.getConfig(); if (existingConfig != null && existingConfig.getAppSecret() != null) { config.setAppSecret(existingConfig.getAppSecret()); } else { return Result.error("请先保存配置后再测试连接"); } } // 如果appKey或appSecret为空,返回错误 if (config.getAppKey() == null || config.getAppKey().isEmpty() || config.getAppSecret() == null || config.getAppSecret().isEmpty()) { return Result.error("AppKey和AppSecret不能为空"); } return Result.success(dingTalkService.testConnection(config)); } catch (Exception e) { return Result.error("测试连接失败: " + e.getMessage()); } } @Operation(summary = "发送测试消息") @PostMapping("/test-message") public Result sendTestMessage(@RequestBody Map params) { String messageType = params.getOrDefault("type", "simple"); String title; StringBuilder content = new StringBuilder(); switch (messageType) { case "task_publish": // 新任务发布样式 title = "新任务发布(测试)"; content.append("-------------------------------- \n"); content.append(" 【 新 任 务 发 布 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("任务积分:100 分 \n"); content.append("任务数量:5 个 \n"); content.append("任务需求类型:3D模型 \n"); content.append("品牌:示例品牌 \n"); content.append("类目:示例类目 \n"); content.append("平台:天猫 \n"); content.append("SPU信息:SPU12345 \n"); content.append("预估工时:8 小时(接单后开始计时) \n"); content.append(" \n"); content.append("发起人:张三 \n"); content.append("发起部门:市场部 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("前往抢单:qd.yydd.ai \n"); content.append("--------------------------------"); break; case "task_assign": // 任务指派样式 title = "任务已指派(测试)"; content.append("-------------------------------- \n"); content.append(" 【 任 务 已 指 派 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("任务积分:100 分 \n"); content.append("指派给:李四 \n"); content.append("预估工时:8 小时 \n"); content.append("发起人:张三 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("请尽快处理!qd.yydd.ai \n"); content.append("--------------------------------"); break; case "task_reject": // 任务驳回样式 title = "任务已驳回(测试)"; content.append("-------------------------------- \n"); content.append(" 【 任 务 已 驳 回 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("原处理人:李四 \n"); content.append("驳回原因:设计稿不符合要求,请重新修改 \n"); content.append(" \n"); content.append("--------------------------------"); break; case "subtask_publish": // 子任务组发布样式 title = "子任务组发布(测试)"; content.append("-------------------------------- \n"); content.append(" 【 子任务组发布 】 \n"); content.append("-------------------------------- \n\n"); content.append("主任务:示例主任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("当前组号:第 2 组 \n"); content.append("子任务数:3 个 \n"); content.append("组积分:60 分 \n"); content.append("任务需求类型:平面设计 \n"); content.append(" \n"); content.append("子任务列表: \n"); content.append(" 1. 首页Banner(20分) \n"); content.append(" 2. 详情页设计(25分) \n"); content.append(" 3. 主图设计(15分) \n"); content.append(" \n"); content.append("发起人:张三 \n"); content.append("发起部门:市场部 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("前往抢单:qd.yydd.ai \n"); content.append("--------------------------------"); break; default: // 简单测试消息 title = params.getOrDefault("title", "系统测试消息"); content.append("-------------------------------- \n"); content.append(" 【 系 统 测 试 】 \n"); content.append("-------------------------------- \n\n"); content.append("这是一条来自 DesignHub 系统的测试消息 \n"); content.append(" \n"); content.append("如果您收到此消息,说明钉钉机器人配置成功! \n"); content.append(" \n"); content.append("测试时间:").append(java.time.LocalDateTime.now().format( java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append(" \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("访问系统:qd.yydd.ai \n"); content.append("--------------------------------"); break; } return Result.success(dingTalkService.sendRobotMessage(title, content.toString())); } @Operation(summary = "发送个人测试消息", description = "向指定用户发送钉钉工作通知") @PostMapping("/test-message-personal") public Result> sendPersonalTestMessage(@RequestBody Map params) { String userName = params.get("userName"); String messageType = params.getOrDefault("type", "simple"); if (userName == null || userName.trim().isEmpty()) { return Result.error("请指定接收人"); } // 根据用户名或真实姓名查找用户 User user = userMapper.selectOne( new LambdaQueryWrapper() .and(w -> w.eq(User::getUsername, userName.trim()) .or().eq(User::getRealName, userName.trim())) .eq(User::getDeleted, 0) .last("LIMIT 1") ); if (user == null) { return Result.error("未找到用户: " + userName); } // 检查用户是否绑定了钉钉ID String dingUserId = user.getDingUserId(); if (dingUserId == null || dingUserId.isEmpty()) { // 尝试通过手机号获取钉钉ID if (user.getPhone() != null && !user.getPhone().isEmpty()) { dingUserId = dingTalkService.getUserIdByMobile(user.getPhone()); if (dingUserId != null) { // 更新用户的钉钉ID user.setDingUserId(dingUserId); userMapper.updateById(user); log.info("通过手机号自动绑定钉钉ID: userId={}, phone={}, dingUserId={}", user.getId(), user.getPhone(), dingUserId); } } } if (dingUserId == null || dingUserId.isEmpty()) { return Result.error("用户未绑定钉钉,请先在钉钉中绑定或确保手机号正确"); } // 构建消息内容 String title; StringBuilder content = new StringBuilder(); switch (messageType) { case "task_publish": title = "新任务发布(测试)"; content.append("-------------------------------- \n"); content.append(" 【 新 任 务 发 布 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("任务积分:100 分 \n"); content.append("任务数量:5 个 \n"); content.append("任务需求类型:3D模型 \n"); content.append("品牌:示例品牌 \n"); content.append("类目:示例类目 \n"); content.append("平台:天猫 \n"); content.append("SPU信息:SPU12345 \n"); content.append("预估工时:8 小时(接单后开始计时) \n"); content.append(" \n"); content.append("发起人:张三 \n"); content.append("发起部门:市场部 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("前往抢单:qd.yydd.ai \n"); content.append("--------------------------------"); break; case "task_assign": title = "任务已指派(测试)"; content.append("-------------------------------- \n"); content.append(" 【 任 务 已 指 派 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("任务积分:100 分 \n"); content.append("指派给:").append(user.getRealName() != null ? user.getRealName() : user.getUsername()).append(" \n"); content.append("预估工时:8 小时 \n"); content.append("发起人:张三 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("请尽快处理!qd.yydd.ai \n"); content.append("--------------------------------"); break; case "task_complete": title = "任务已完成(测试)"; content.append("-------------------------------- \n"); content.append(" 【 任 务 已 完 成 】 \n"); content.append("-------------------------------- \n\n"); content.append("任务名称:示例任务标题 \n"); content.append("任务编号:TSK20260104001 \n"); content.append("任务积分:100 分 \n"); content.append("处理人:").append(user.getRealName() != null ? user.getRealName() : user.getUsername()).append(" \n"); content.append("处理时长:6 小时 30 分钟 \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("请前往验收!qd.yydd.ai \n"); content.append("--------------------------------"); break; case "score_change": title = "积分变动通知(测试)"; content.append("-------------------------------- \n"); content.append(" 【 积 分 变 动 】 \n"); content.append("-------------------------------- \n\n"); content.append("变动类型:任务完成奖励 \n"); content.append("变动积分:+100 分 \n"); content.append("当前积分:1,500 分 \n"); content.append("变动时间:").append(java.time.LocalDateTime.now().format( java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append(" \n"); content.append(" \n"); content.append("--------------------------------"); break; default: title = params.getOrDefault("title", "系统测试消息"); content.append("-------------------------------- \n"); content.append(" 【 系 统 测 试 】 \n"); content.append("-------------------------------- \n\n"); content.append("亲爱的 ").append(user.getRealName() != null ? user.getRealName() : user.getUsername()).append(": \n\n"); content.append("这是一条来自 DesignHub 系统的测试消息 \n"); content.append(" \n"); content.append("如果您收到此消息,说明钉钉个人通知配置成功! \n"); content.append(" \n"); content.append("测试时间:").append(java.time.LocalDateTime.now().format( java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append(" \n"); content.append(" \n"); content.append("-------------------------------- \n"); content.append("访问系统:qd.yydd.ai \n"); content.append("--------------------------------"); break; } // 发送工作通知 boolean success = dingTalkService.sendWorkNotification( Collections.singletonList(dingUserId), title, content.toString() ); Map result = new HashMap<>(); result.put("success", success); result.put("userName", user.getRealName() != null ? user.getRealName() : user.getUsername()); result.put("dingUserId", dingUserId); result.put("messageType", messageType); if (success) { return Result.success(result); } else { result.put("error", "消息发送失败,请检查钉钉配置"); return Result.error("消息发送失败"); } } @Operation(summary = "获取钉钉部门列表") @GetMapping("/departments") public Result>> getDepartmentList() { return Result.success(dingTalkService.getDepartmentList()); } @Operation(summary = "同步钉钉部门") @PostMapping("/sync-departments") public Result syncDepartments() { try { return Result.success(dingTalkService.syncDepartments()); } catch (Exception e) { log.error("同步钉钉部门失败", e); return Result.error("同步钉钉部门失败: " + e.getMessage()); } } @Operation(summary = "同步钉钉用户") @PostMapping("/sync-users") public Result syncUsers() { return Result.success(dingTalkService.syncUsers()); } @Operation(summary = "检查功能状态") @GetMapping("/feature/{feature}") public Result isFeatureEnabled(@PathVariable String feature) { return Result.success(dingTalkService.isFeatureEnabled(feature)); } @Operation(summary = "钉钉OAuth登录发起", description = "生成钉钉登录URL") @GetMapping("/oauth/login") public Result> initiateLogin(@RequestParam(required = false) String redirectUrl) { try { // 检查是否配置 if (!dingTalkService.isConfigured()) { return Result.error("钉钉登录未配置或未启用,请联系管理员在系统设置中配置"); } // 检查登录功能是否启用 if (!dingTalkService.isFeatureEnabled("login")) { return Result.error("钉钉登录功能未启用,请在系统设置中启用钉钉登录功能"); } DingTalkConfig config = dingTalkService.getConfig(); if (config == null) { return Result.error("钉钉配置不存在"); } if (config.getAppKey() == null || config.getAppKey().isEmpty()) { return Result.error("钉钉AppKey未配置"); } // 构建回调URL String callbackUrl = redirectUrl != null ? redirectUrl : "http://localhost:3000/auth/dingtalk/callback"; // 构建钉钉OAuth URL String authUrl = String.format( "https://login.dingtalk.com/oauth2/auth?redirect_uri=%s&response_type=code&client_id=%s&scope=openid&state=STATE&prompt=consent", java.net.URLEncoder.encode(callbackUrl, java.nio.charset.StandardCharsets.UTF_8), config.getAppKey() ); Map result = new HashMap<>(); result.put("authUrl", authUrl); result.put("appKey", config.getAppKey()); return Result.success(result); } catch (Exception e) { log.error("获取钉钉登录地址失败", e); return Result.error("获取钉钉登录地址失败: " + e.getMessage()); } } @Operation(summary = "检查钉钉登录是否已配置") @GetMapping("/is-configured") public Result isConfigured() { return Result.success(dingTalkService.isConfigured()); } }