在工作管理系統場景中,上下級和不同部門之間常常有請假,餐補等流程操作,而這些操作通常需要人員手動進行,這里我們引入一個釘釘的api,可以基于釘釘來發送工作消息通知
1、導入釘釘sdk
<dependency><groupId>com.aliyun</groupId><artifactId>alibaba-dingtalk-service-sdk</artifactId><version>2.0.0</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>dingtalk</artifactId><version>2.2.26</version></dependency>
2、登錄釘釘開發者后臺,選擇租戶
3、點擊右上角創建應用
4、申請開通工作消息API基礎權限
5、獲取企業AngentId并獲取ClientID和ClientSecret,用于后續程序中獲取token
6、配置clientId和clientSecret
7、進行api的調用和業務邏輯的開發(這里我封裝了一個接口用于發送釘釘消息,方便前端調用)
@Tag(name = "釘釘-發送工作消息")
@RestController
@RequestMapping("/dingtalk")
@Validated
public class DingTalkController {@Resourceprivate DingTalkService dingTalkService;@PostMapping("/sendSubmitMessage")@Operation(summary = "釘釘發送消息通知提交餐補證明")
/* @PreAuthorize("@ss.hasPermission('dingtalk:hr:sendsubmitmsg')")*/public CommonResult<Boolean> sendSubmitMessage(@RequestBody List<Long> ids) {Boolean result = dingTalkService.sendSubmitMessageToUser(ids);return success(result);}@PostMapping("/sendReSubmitMessage")@Operation(summary = "釘釘發送消息通知重新提交餐補證明")/*@PreAuthorize("@ss.hasPermission('dingtalk:hr:sendremsg')")*/public CommonResult<Boolean> sendReSubmitMessage(@RequestParam Long poofId, @RequestParam String remark) {dingTalkService.sendReSubmitMessageToUser(poofId, remark);return success(true);}
}
這里我們以發送上傳餐補證明消息給指定用戶接口為例
@Service
@Slf4j
public class DingTalkServiceImpl implements DingTalkService {// 注入釘釘應用的 AppKey 和 AppSecret@Value("${justauth.type.DINGTALK.client-id}")private String clientId;@Value("${justauth.type.DINGTALK.client-secret}")private String clientSecret;private static final String DINGTALK_API_BASE_URL = "https://oapi.dingtalk.com";private static final String GET_TOKEN_URL = "/gettoken";@Resourceprivate AdminUserApi adminUserApi;@Resourceprivate RestTemplate restTemplate;@Resourceprivate MealAllowanceProofMapper mealAllowanceProofMapper;@Resourceprivate MealAllowanceDataMapper mealAllowanceDataMapper;@Overridepublic Boolean sendSubmitMessageToUser(List<Long> userIds) {/*** 發送釘釘消息* @param*///遍歷用戶idBoolean result = true;for (Long userId : userIds) {//通過用戶id得到對應用戶的釘釘idString userRemark = adminUserApi.getUserRemark(userId).replaceAll("\\D+", "");//通過用戶id得到對應用戶的餐補總金額BigDecimal totalAmount = mealAllowanceDataMapper.selectByUserId(userId).getTotalAmount();// 調用下方方法發送result = sendSingleUserNotice(userRemark, totalAmount);if (!result) {throw exception(DING_TALK_SEND_MESSAGE_ERROR);}}return result;}
發送消息體封裝邏輯和消息外觀構造:
@SneakyThrowsprivate Boolean sendSingleUserNotice(String dingUserId, BigDecimal amount){//獲取AccessTokenString accessToken = getAccessToken();//"https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"為釘釘發送消息的接口請求地址DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");//構建消息請求體OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();request.setAgentId(#自己企業的AngentId#);//設置發送給對應用戶的釘釘idrequest.setUseridList(dingUserId);//不發給全體成員request.setToAllUser(false);//構建PC端和移動端的url跳轉路徑地址,點擊路徑可以跳轉到提交餐補信息的平臺String PcUrl = "http://effi.fzxs.com.cn:8089/overtime/meal-allowance";String mobileUrl = "dingtalk://dingtalkclient/page/link?url=" + URLEncoder.encode(PcUrl, "UTF-8") + "&pc_slide=false";// 使用ActionCard消息類型創建帶按鈕的消息OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();msg.setMsgtype("action_card");// 創建ActionCard消息OapiMessageCorpconversationAsyncsendV2Request.ActionCard actionCard = new OapiMessageCorpconversationAsyncsendV2Request.ActionCard();// 設置消息標題actionCard.setTitle("💰 加班餐補通知");// 構建精美的Markdown內容StringBuilder content = new StringBuilder();content.append("## 你有新的加班餐補啦 \n\n");content.append("### **💰餐補金額: ¥").append(amount.toString()).append("**\n\n");content.append("**請及時上傳:** 餐飲消費憑證截圖,不要讓hr小姐姐久等啦 \n\n");content.append("?? **截止時間:** 2個工作日內,過期作廢");actionCard.setMarkdown(content.toString());// 設置按鈕布局為豎直排列actionCard.setBtnOrientation("0");// 創建按鈕列表List<OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList> btnList = new ArrayList<>();// 添加上傳證明按鈕OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList uploadBtn = new OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList();uploadBtn.setTitle("立即上傳餐補證明");uploadBtn.setActionUrl(mobileUrl);btnList.add(uploadBtn);actionCard.setBtnJsonList(btnList);msg.setActionCard(actionCard);request.setMsg(msg);try {OapiMessageCorpconversationAsyncsendV2Response response = client.execute(request, accessToken);if (response.getErrcode() == 0) {log.info("[sendSingleUserNotice][發送餐補通知成功] userId={}, amount={}, taskId={}", dingUserId, amount, response.getTaskId());return true;} else {log.error("[sendSingleUserNotice][發送餐補通知失敗] userId={}, errcode={}, errmsg={}", dingUserId, response.getErrcode(), response.getErrmsg());return false;}} catch (ApiException e) {log.error("[sendSingleUserNotice][發送餐補通知異常] userId={}, error={}", dingUserId, e.getMessage(), e);throw ServiceExceptionUtil.exception(DING_TALK_SEND_MESSAGE_ERROR);}}/*** 創建美觀的釘釘卡片消息* * @param title 卡片標題* @param content 卡片內容* @param pcUrl PC端鏈接* @param mobileUrl 移動端鏈接* @return ActionCard對象*/private OapiMessageCorpconversationAsyncsendV2Request.ActionCard createBeautifulActionCard(String title, String content, String pcUrl, String mobileUrl) {OapiMessageCorpconversationAsyncsendV2Request.ActionCard actionCard = new OapiMessageCorpconversationAsyncsendV2Request.ActionCard();actionCard.setTitle(title);actionCard.setMarkdown(content);actionCard.setBtnOrientation("1");// 添加操作按鈕List<OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList> btnList = new ArrayList<>();// 移動端按鈕OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList mobileBtn = new OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList();mobileBtn.setTitle("📱 移動端處理");mobileBtn.setActionUrl(mobileUrl);btnList.add(mobileBtn);// PC端按鈕OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList pcBtn = new OapiMessageCorpconversationAsyncsendV2Request.BtnJsonList();pcBtn.setTitle("💻 電腦端處理");pcBtn.setActionUrl(pcUrl);btnList.add(pcBtn);actionCard.setBtnJsonList(btnList);return actionCard;}
獲取accessToken方法封裝:
/*** 獲取釘釘訪問令牌* @return*/public String getAccessToken() {try {String url = UriComponentsBuilder.fromHttpUrl(DINGTALK_API_BASE_URL + GET_TOKEN_URL).queryParam("appkey", clientId).queryParam("appsecret", clientSecret).toUriString();ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);JSONObject result = JSON.parseObject(response.getBody());if (result.getInteger("errcode") != 0) {log.error("獲取釘釘訪問令牌失敗:{}", result.getString("errmsg"));throw exception(AUTH_ACCESS_TOKEN_ERROR);}return result.getString("access_token");} catch (Exception e) {log.error("獲取釘釘訪問令牌異常",e);throw exception(AUTH_ACCESS_TOKEN_ERROR);}}