在工作管理系统场景中,上下级和不同部门之间常常有请假,餐补等流程操作,而这些操作通常需要人员手动进行,这里我们引入一个钉钉的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 {
@Resource
private 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";
@Resource
private AdminUserApi adminUserApi;
@Resource
private RestTemplate restTemplate;
@Resource
private MealAllowanceProofMapper mealAllowanceProofMapper;
@Resource
private MealAllowanceDataMapper mealAllowanceDataMapper;
@Override
public Boolean sendSubmitMessageToUser(List<Long> userIds) {
/**
* 发送钉钉消息
* @param
*/
//遍历用户id
Boolean result = true;
for (Long userId : userIds) {
//通过用户id得到对应用户的钉钉id
String 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;
}
发送消息体封装逻辑和消息外观构造:
@SneakyThrows
private Boolean sendSingleUserNotice(String dingUserId, BigDecimal amount){
//获取AccessToken
String 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#);
//设置发送给对应用户的钉钉id
request.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);
}
}