``` String finalNickName = currentUserNickName; CompletableFuture.runAsync(() -> { try { if (uploadFile != null && uploadFile.exists()) { String planUserIds = dingTalkProperties.getPlanUserId(); if (StringUtils.isNotBlank(planUserIds)) { String mediaId = mediaService.uploadMedia("file", uploadFile.getAbsolutePath()); // 支持多人推送,假设多个 userId 以逗号分隔 String[] userIds = planUserIds.split(","); for (String userId : userIds) { String cleanUserId = userId.trim(); if (StringUtils.isNotBlank(cleanUserId)) { try { robotGroupService.sendFileToUser(cleanUserId, mediaId, originalFilename); log.info("上传的工艺计划表已成功发送给用户: {}", cleanUserId); } catch (Exception e) { log.error("发送文件给用户 {} 异常", cleanUserId, e); } } } } else { log.warn("未配置钉钉工艺计划接收人(planUserId),跳过发送文件"); } } } catch (Exception e) { log.error("发送文件给工艺计划接收人异常", e); } finally { if (uploadFile != null && uploadFile.exists()) { uploadFile.delete(); } } ``` ``` /** * 发送文件给个人 * * @param userId 接收人userId * @param mediaId 媒体文件ID * @param fileName 文件名 * @return success */ public String sendFileToUser(String userId, String mediaId, String fileName) { try { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; Client client = new Client(config); BatchSendOTOHeaders headers = new BatchSendOTOHeaders(); headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken(); String msgParam = String.format("{\"mediaId\":\"%s\",\"fileName\":\"%s\"}", mediaId, fileName); BatchSendOTORequest request = new BatchSendOTORequest() .setRobotCode(properties.getRobotCode()) .setUserIds(Arrays.asList(userId)) .setMsgKey("sampleFile") .setMsgParam(msgParam); client.batchSendOTOWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions()); log.info("机器人个人文件发送成功, userId: {}, fileName: {}", userId, fileName); return "success"; } catch (Exception e) { log.error("机器人个人文件发送失败", e); throw new DingException("机器人个人文件发送失败", e); } } ``` ``` /** * 发送Markdown消息给个人 (批量单聊) * * @param userId 接收人userId * @param title 标题 * @param text Markdown内容 */ public void sendMarkdownToUser(String userId, String title, String text) { try { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; Client client = new Client(config); BatchSendOTOHeaders headers = new BatchSendOTOHeaders(); headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken(); String msgParam = String.format("{\"title\":\"%s\",\"text\":\"%s\"}", title.replace("\"", "\\\""), text.replace("\"", "\\\"").replace("\n", "\\n")); BatchSendOTORequest request = new BatchSendOTORequest() .setRobotCode(properties.getRobotCode()) .setUserIds(Collections.singletonList(userId)) .setMsgKey("sampleMarkdown") .setMsgParam(msgParam); client.batchSendOTOWithOptions(request, headers, new RuntimeOptions()); log.info("机器人个人Markdown消息发送成功, userId: {}, title: {}", userId, title); } catch (Exception e) { log.error("机器人个人Markdown消息发送失败", e); throw new DingException("机器人个人Markdown消息发送失败", e); } } ``` ``` /** * 刷新AccessToken */ private void refresh() { try { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; Client client = new Client(config); GetAccessTokenRequest request = new GetAccessTokenRequest() .setAppKey(properties.getAppKey()) .setAppSecret(properties.getAppSecret()); GetAccessTokenResponse response = client.getAccessToken(request); if (response.getBody() != null) { this.accessToken = response.getBody().getAccessToken(); // 设置过期时间,提前100秒刷新 this.expireTime = System.currentTimeMillis() + (response.getBody().getExpireIn() * 1000) - 100000; log.info("钉钉AccessToken刷新成功,过期时间: {}", this.expireTime); } else { throw new DingException("刷新AccessToken失败,返回为空"); } } catch (Exception e) { log.error("刷新钉钉AccessToken失败", e); throw new DingException("刷新钉钉AccessToken失败: " + e.getMessage(), e); } } ```