本記事では、Spring Bootを使って钉钉のカスタムBotをゼロから立ち上げ、メッセージの送受信まで一通り実装する手順を紹介します。
前提条件
- 钉钉管理コンソールで「企業内部開発」→「アプリを作成」し、Client ID(旧appKey)とClient Secret(旧appSecret)を取得済み
- アプリに「カスタムBot」能力を追加し、HTTPコールバックURLを設定済み(ローカル開発時はngrok等でトンネリング)
- 必要なAPI権限(例:チャット情報の読み取り)を付与し、リリース済み
- 対象の钉钉グループに上記Botを招待し、Webhook URLを取得済み
プロジェクト構成
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>2.0.24</version>
</dependency>
Webhookコールバックの受信
钉钉からのPOSTリクエストを受け取るRESTコントローラーです。
@RestController
@RequestMapping("/webhook/ding")
@RequiredArgsConstructor
public class DingCallbackController {
private final DingEventHandler dingEventHandler;
@PostMapping("/chat")
public ResponseEntity<String> onChatMessage(@RequestBody(required = false) JSONObject payload) {
dingEventHandler.handle(payload);
return ResponseEntity.ok("success");
}
}
メッセージ送信ユーティリティ
Webhook URLを使って任意のチャットへメッセージを送信する共通クラスです。
@Component
public class DingTalkNotifier {
private final ObjectMapper mapper = new ObjectMapper();
public void push(ChatMessage msg) throws IOException {
DingTalkClient client = new DefaultDingTalkClient(msg.getWebhook());
OapiRobotSendRequest req = buildRequest(msg);
OapiRobotSendResponse resp = client.execute(req);
if (!resp.isSuccess()) {
throw new IllegalStateException("钉钉送信失敗: " + resp.getErrmsg());
}
}
private OapiRobotSendRequest buildRequest(ChatMessage msg) {
OapiRobotSendRequest req = new OapiRobotSendRequest();
req.setMsgtype(msg.getType().name().toLowerCase());
switch (msg.getType()) {
case TEXT:
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(msg.getText());
req.setText(text);
break;
case MARKDOWN:
OapiRobotSendRequest.Markdown md = new OapiRobotSendRequest.Markdown();
md.setTitle(msg.getTitle());
md.setText(msg.getText());
req.setMarkdown(md);
break;
case LINK:
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setTitle(msg.getTitle());
link.setText(msg.getText());
link.setMessageUrl(msg.getLinkUrl());
link.setPicUrl(msg.getPicUrl());
req.setLink(link);
break;
case ACTION_CARD:
req.setActionCard(buildActionCard(msg));
break;
case FEED_CARD:
req.setFeedCard(buildFeedCard(msg));
break;
default:
throw new IllegalArgumentException("未対応のメッセージタイプ");
}
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setAtMobiles(msg.getAtMobiles());
at.setAtUserIds(msg.getAtUserIds());
at.setIsAtAll(msg.isAtAll());
req.setAt(at);
return req;
}
private OapiRobotSendRequest.Actioncard buildActionCard(ChatMessage msg) {
OapiRobotSendRequest.Actioncard card = new OapiRobotSendRequest.Actioncard();
card.setTitle(msg.getTitle());
card.setText(msg.getText());
card.setBtnOrientation(msg.getBtnOrientation());
card.setSingleTitle(msg.getSingleTitle());
card.setSingleURL(msg.getSingleUrl());
List<OapiRobotSendRequest.Btns> btns = msg.getButtons().stream()
.map(b -> {
OapiRobotSendRequest.Btns btn = new OapiRobotSendRequest.Btns();
btn.setTitle(b.getTitle());
btn.setActionURL(b.getUrl());
return btn;
})
.collect(Collectors.toList());
card.setBtns(btns);
return card;
}
private OapiRobotSendRequest.Feedcard buildFeedCard(ChatMessage msg) {
OapiRobotSendRequest.Feedcard feed = new OapiRobotSendRequest.Feedcard();
List<OapiRobotSendRequest.Links> links = msg.getArticles().stream()
.map(a -> {
OapiRobotSendRequest.Links l = new OapiRobotSendRequest.Links();
l.setTitle(a.getTitle());
l.setMessageURL(a.getUrl());
l.setPicURL(a.getPicUrl());
return l;
})
.collect(Collectors.toList());
feed.setLinks(links);
return feed;
}
}
メッセージモデル
@Data
@Builder
public class ChatMessage {
private String webhook;
private MsgType type;
private String title;
private String text;
private String linkUrl;
private String picUrl;
private String singleTitle;
private String singleUrl;
private String btnOrientation;
private List<Button> buttons;
private List<Article> articles;
private List<String> atMobiles;
private List<String> atUserIds;
private boolean isAtAll;
}
@Getter
@AllArgsConstructor
public enum MsgType {
TEXT,
MARKDOWN,
LINK,
ACTION_CARD,
FEED_CARD
}
@Data
@Builder
public class Button {
private String title;
private String url;
}
@Data
@Builder
public class Article {
private String title;
private String url;
private String picUrl;
}
使用例
@Service
public class AlertService {
private final DingTalkNotifier notifier;
public void alertDeployment(String env) throws IOException {
ChatMessage msg = ChatMessage.builder()
.webhook("https://oapi.dingtalk.com/robot/send?access_token=xxx")
.type(MsgType.MARKDOWN)
.title("🚀 デプロイ通知")
.text("### デプロイ完了\n" +
"- **環境**: " + env + "\n" +
"- **時刻**: " + LocalTime.now())
.atMobiles(List.of("15012345678"))
.build();
notifier.push(msg);
}
}