微信iLink Bot Java SDK 2.1.0开箱即用:二维码登录+全类型消息收发+媒体上传下载
前言:前段时间微信官方开放了iLink协议,并推出了一套标准化接口,支持开发者通过API完成机器人登录、消息收发(支持markdown格式)、媒体上传下载等核心能力。但原生接口存在调用流程繁琐、参数结构复杂、加解密逻辑需要手动处理、上下文管理等问题,直接接入成本高、开发效率低,很多开发者花费大量时间在协议对接上,反而无暇聚焦业务逻辑。
为此,我基于微信iLink官方协议,全新升级打造了这套Java SDK(当前版本2.1.0),对底层接口进行全面封装,简化了调用逻辑与参数处理,内置异常体系、状态管理、资源释放等核心能力,让Java开发者无需关心协议细节、无需处理加解密与上下文缓存,只需几行代码即可快速搭建稳定、合规的微信机器人。
在我看来,iLink协议真正的核心价值,并非只是做一个闲聊机器人,而是为开发者提供了第一条微信官方认可、合规安全的消息接入通道。它让服务端可以正规接收微信消息,告别以往通过Hook、逆向协议等非正规方式带来的封号风险与稳定性问题。基于这套官方通道,开发者可以自由扩展业务逻辑:消息触发、后台任务、系统通知、数据处理,甚至可以与大模型AI结合,构建智能客服、自动化工具、企业内部系统等复杂业务场景,真正实现安全、稳定、可持续的微信生态开发。
项目开源地址:github.com/lith0924/we…,欢迎Star、Fork,一起完善迭代~
一、SDK核心特性一览(2.1.0版本升级亮点)
相较于1.0版本,2.1.0版本进一步优化封装逻辑,完善功能覆盖,兼顾易用性与灵活性,新手可快速上手,老手可灵活扩展:
-
**全功能覆盖,无死角**:支持二维码登录、登录状态轮询、文本/图片/视频/文件/语音消息收发、输入态控制、媒体下载与AES解密、会话上下文管理,覆盖iLink Bot开发全链路。
-
**异常友好,精准排查**:自定义专属异常类,精准区分会话过期、业务异常、网络异常、上下文缺失等场景,方便开发者针对性处理,减少调试成本。
-
**内置优化,稳定可靠**:会话过期自动检测、网络波动指数退避重试、HTTP资源自动管理、SLF4J日志适配,同时内置心跳探测、线程池管理,保障生产环境稳定运行。
-
**易集成,零冗余依赖**:支持Maven、Gradle快速引入,无额外第三方冗余依赖,兼容主流Java版本,无需手动下载jar包,开箱即用。
-
**结构清晰,易于扩展**:采用分层设计,核心门面、业务服务、数据层、支撑层分离,配套专属DTO和实体类,避免参数混乱,便于后续功能扩展与维护。
二、快速引入依赖(2.1.0最新版本)
SDK已发布至Maven中央仓库,直接复制对应依赖即可集成,无需手动下载jar包,当前最新稳定版本为2.1.0。
2.1 Maven依赖
<dependency>
<groupId>io.github.lith0924</groupId>
<artifactId>wechat-ilink-sdk</artifactId>
<version>2.1.0</version>
</dependency>
2.2 Gradle依赖
implementation 'io.github.lith0924:wechat-ilink-sdk:2.1.0'
三、项目核心结构说明(分层设计,一目了然)
SDK做了清晰的分层设计,将底层原始接口和上层封装接口分离,同时配套专属DTO和实体类,避免参数混乱,开发者可按需选择调用,后续扩展更便捷:
| 类型 | 说明 | 示例 |
|---|---|---|
| 核心门面(入口层) | SDK统一入口,封装所有对外暴露的API,简化调用 | ILinkClient、ILinkClientBuilder |
| 核心服务(业务层) | 封装底层业务逻辑,区分原始API与封装API | LoginService、MessageService、MediaService |
| DTO/实体类(数据层) | 友好型DTO屏蔽冗余字段,实体类保留官方完整结构 | LoginContext、QrCodeInfo、WeixinMessage |
| 基础设施层(支撑层) | 提供通用能力支撑,降低业务层耦合 | ILinkConfig、RetryPolicy、HttpClientFacade |
四、极速上手:10分钟搭建iLink机器人
下面通过完整代码示例,演示从创建客户端、扫码登录、消息接发到各类媒体消息发送的全流程,复制即可运行测试,新手也能快速上手。
package com.lth.wechat.ilink.example;
import com.github.wechat.ilink.sdk.ILinkClient;
import com.github.wechat.ilink.sdk.core.config.ILinkConfig;
import com.github.wechat.ilink.sdk.core.listener.OnLoginListener;
import com.github.wechat.ilink.sdk.core.listener.OnMessageListener;
import com.github.wechat.ilink.sdk.core.login.LoginContext;
import com.github.wechat.ilink.sdk.core.model.MessageItem;
import com.github.wechat.ilink.sdk.core.model.WeixinMessage;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
/**
* 微信 iLink Bot SDK 2.1.0 完整示例(扫码登录+消息收发+媒体操作)
*/
public class ILinkFullDemo {
// 初始化客户端(可自定义配置)
private static ILinkClient client = ILinkClient.builder()
.config(ILinkConfig.builder()
.connectTimeoutMs(35000)
.readTimeoutMs(35000)
.httpMaxRetries(3)
.heartbeatEnabled(true)
.build())
.onLogin(new OnLoginListener() {
@Override
public void onLoginSuccess(LoginContext context) {
System.out.println("登录成功,botId = " + context.getBotId());
}
@Override
public void onLoginFailure(Throwable throwable) {
System.err.println("登录失败: " + throwable.getMessage());
}
})
.onMessage(new OnMessageListener() {
@Override
public void onMessages(List<WeixinMessage> messages) {
System.out.println("收到" + messages.size() + "条消息");
}
})
.build();
private static String targetUserId, contextToken, cursor = "";
public static void main(String[] args) throws Exception {
login(); // 1. 二维码登录
receiveMessage(); // 2. 接收消息(获取目标用户ID和contextToken)
sendTextWithTyping(); // 3. 带输入态发送文本
sendImage(); // 4. 发送图片
sendFile(); // 5. 发送文件
sendVideo(); // 6. 发送视频
sendVoice(); // 7. 发送语音
downloadMedia(); // 8. 下载媒体消息
client.close(); // 9. 释放资源(必做)
}
/**
* 1. 二维码登录(自动轮询登录状态)
*/
private static void login() throws Exception {
// 获取二维码内容(自行渲染为二维码)
String qrCodeContent = client.executeLogin();
System.out.println("请将以下内容渲染为二维码后扫码登录:");
System.out.println(qrCodeContent);
// 阻塞等待登录完成,获取登录凭证
LoginContext context = client.getLoginFuture().get();
System.out.println("登录成功,botId = " + context.getBotId());
}
/**
* 2. 接收消息(获取目标用户ID和contextToken,用于后续发送消息)
*/
private static void receiveMessage() throws Exception {
while (targetUserId == null) {
// 拉取消息(SDK自动管理cursor游标)
List<WeixinMessage> messages = client.getUpdates();
if (!messages.isEmpty()) {
WeixinMessage msg = messages.get(0);
targetUserId = msg.getFrom_user_id();
contextToken = msg.getContext_token();
System.out.println("获取目标用户ID:" + targetUserId);
System.out.println("获取上下文标识:" + contextToken);
}
Thread.sleep(3000);
}
}
/**
* 3. 带输入态发送文本消息(模拟原生输入效果)
*/
private static void sendTextWithTyping() throws Exception {
// 开启输入态
client.startTyping(targetUserId);
// 模拟输入延迟
Thread.sleep(1500);
// 发送文本消息(SDK自动使用缓存的contextToken)
client.sendText(targetUserId, "微信iLink Bot SDK 2.0.0 测试消息");
// 停止输入态
client.stopTyping(targetUserId);
}
/**
* 4. 发送图片消息
*/
private static void sendImage() throws Exception {
// 读取本地图片字节
byte[] imageBytes = Files.readAllBytes(Paths.get("demo.png"));
// 发送图片(自动上传媒体,无需手动调用上传接口)
client.sendImage(targetUserId, imageBytes, "demo.png", "测试图片");
}
/**
* 5. 发送文件消息
*/
private static void sendFile() throws Exception {
byte[] fileBytes = Files.readAllBytes(Paths.get("demo.pdf"));
client.sendFile(targetUserId, fileBytes, "demo.pdf", "测试文件");
}
/**
* 6. 发送视频消息
*/
private static void sendVideo() throws Exception {
byte[] videoBytes = Files.readAllBytes(Paths.get("demo.mp4"));
// 参数:目标用户ID、视频字节、文件名、视频时长(ms)、视频描述
client.sendVideo(targetUserId, videoBytes, "demo.mp4", 5000, "测试视频");
}
/**
* 7. 发送语音消息(silk格式)
*/
private static void sendVoice() throws Exception {
byte[] voiceBytes = Files.readAllBytes(Paths.get("demo.silk"));
// 参数:目标用户ID、语音字节、文件名、语音时长(ms)、采样率
client.sendVoice(targetUserId, voiceBytes, "demo.silk", 3000, 16000);
}
/**
* 8. 下载媒体消息(从接收的消息中下载)
*/
private static void downloadMedia() throws Exception {
List<WeixinMessage> messages = client.getUpdates();
for (WeixinMessage msg : messages) {
if (msg.getItem_list() == null) continue;
for (MessageItem item : msg.getItem_list()) {
// 下载图片(自动AES解密)
if (item.getImage_item() != null) {
byte[] imageBytes = client.downloadImageFromMessageItem(item);
Files.write(Paths.get("download.png"), imageBytes);
System.out.println("图片下载完成");
}
}
}
}
}
效果演示
SDK 2.1.0已完整支持各类媒体消息发送与下载,包括语音、图片、视频、文件等常见类型,支持markdown格式转化,同时可模拟微信原生输入状态展示,交互体验更贴近官方客户端,无需额外开发适配。
1.接收消息,可以接收普通消息,媒体类型消息,可以将媒体类型数据下载到本地处理,后端可做任意处理
2.发送消息,普通消息以及媒体类型消息,可以动态控制输入状态(正在输入中),普通消息支持markdown格式转换,此处不做演示了,感兴趣的朋友可以自己测试一下
五、核心API详解(分类整理,方便查阅)
SDK核心API分为登录、消息收发、媒体操作、配置四大类,所有封装API均简化了参数传递,无需拼接复杂请求体,直接调用即可实现对应功能。
5.1 登录相关API(最常用)
| 方法名称 | 参数 | 返回值 | 接口类型 | 核心说明 |
|---|---|---|---|---|
| executeLogin() | 无 | String(二维码内容) | 封装API | 一键获取二维码内容,新手首选,简化登录流程 |
| getLoginFuture() | 无 | Future<LoginContext> | 封装API | 获取登录结果,阻塞等待登录完成,返回登录凭证 |
| getBotQrCode() | 无 | QrCodeResp | 原始API | 原生获取二维码响应,保留官方完整字段,适合二次开发 |
5.2 消息收发API(全覆盖)
支持文本、图片、视频、文件、语音、输入态控制等各类消息,封装API直接传参即可调用,无需关心底层协议细节:
-
sendText(String targetUserId, String content):发送文本消息,最常用
-
sendTextWithTyping(String targetUserId, String content, long typingDurationMs):带输入态发送文本,一键实现输入态控制
-
sendImage(String targetUserId, byte[] imageBytes, String fileName, String desc):发送图片消息,自动上传媒体
-
sendVideo(String targetUserId, byte[] videoBytes, String fileName, long durationMs, String desc):发送视频消息
-
sendFile(String targetUserId, byte[] fileBytes, String fileName, String desc):发送文件消息
-
sendVoice(String targetUserId, byte[] voiceBytes, String fileName, long durationMs, int sampleRate):发送语音消息
-
getUpdates():接收消息,SDK自动管理cursor游标,无需手动维护
-
startTyping(String targetUserId) / stopTyping(String targetUserId):手动开启/停止输入态
5.3 媒体操作API
发送图片、视频等媒体消息前,SDK会自动完成媒体上传,无需手动调用上传接口;下载媒体时,自动完成AES解密,简化开发流程:
// 1. 发送媒体(自动上传)
byte[] imageBytes = Files.readAllBytes(Paths.get("test.jpg"));
client.sendImage("user@im.wechat", imageBytes, "test.jpg", "测试图片");
// 2. 下载媒体(自动解密)
List<WeixinMessage> messages = client.getUpdates();
for (WeixinMessage msg : messages) {
for (MessageItem item : msg.getItem_list()) {
if (item.getImage_item() != null) {
byte[] imageBytes = client.downloadImageFromMessageItem(item);
Files.write(Paths.get("download.jpg"), imageBytes);
}
}
}
5.4 配置与上下文API
-
ILinkConfig.builder():自定义客户端配置(超时时间、重试次数、心跳等)
-
clearContext(String targetUserId):清理单个用户会话上下文
-
clearAllContexts():清理所有用户会话上下文
-
close():关闭客户端,释放HTTP资源、线程池等(必做)
六、关键参数与规则详解(避坑必备)
对接iLink协议时,很多开发者会因参数格式、规则不熟悉踩坑,这里整理核心要点,帮大家快速避坑:
6.1 用户ID格式规范(必看)
-
普通用户ID:xxx@im.wechat(示例:abc123@im.wechat)
-
机器人ID:xxx@im.bot(示例:ba36538a1eb2@im.bot)
注意:发送消息时,目标用户ID格式错误会直接导致发送失败,需严格遵循上述格式。
6.2 游标Cursor机制(核心)
Cursor是消息分页与增量拉取的关键,必须遵循以下规则,否则会出现消息重复或丢失:
-
首次接收消息:cursor传空字符串(SDK内部已默认处理,无需手动传入)
-
后续接收:SDK自动用上一次返回的nextCursor,无需手动管理
-
旧游标:会返回该游标及之后所有历史数据
-
新游标:仅返回游标之后的新消息
6.3 登录状态枚举
-
WAIT / WAITING:等待扫码
-
SCANED / SCANNED:已扫码,待用户确认
-
CONFIRMED / LOGGED_IN:已确认,登录成功
-
EXPIRED:二维码已过期,需重新获取二维码
6.4 媒体类型对应值
| 类型值 | 媒体类型 | 说明 |
|---|---|---|
| 1 | 图片 | 支持png、jpg等常见格式 |
| 2 | 视频 | 需传入视频时长(ms) |
| 3 | 文件 | 无格式限制,需传入文件名 |
| 4 | 语音 | 仅支持silk格式,需传入时长和采样率 |
6.5 contextToken(会话上下文核心)
contextToken是消息上下文标识,用于关联发送消息与对应会话,必须满足以下条件才能正常使用:
-
来源:从接收的消息(getUpdates())中获取,无法自定义
-
缓存:SDK会自动按用户ID缓存最新的contextToken,无需手动传入
-
异常:若提示“missing latest context token”,需先让目标用户发消息,再调用getUpdates()拉取
七、异常处理最佳实践
SDK自定义了专属异常,重点关注会话过期异常,出现该异常必须重新扫码登录,其他异常可统一捕获处理,降低调试成本:
try {
// 调用消息接收或发送接口
List<WeixinMessage> messages = client.getUpdates();
client.sendText(targetUserId, "测试消息");
} catch (NotLoginException e) {
// 未登录异常,需先执行登录
System.err.println("请先扫码登录:" + e.getMessage());
} catch (ILinkSessionExpiredException e) {
// 会话过期(错误码ret=-14),必须重新扫码登录
System.err.println("会话已过期,请重新启动程序扫码登录");
} catch (ILinkException e) {
// 其他业务异常,参数错误、网络失败等
System.err.println("SDK调用异常:" + e.getMessage());
} catch (IOException e) {
// 网络或IO异常,如文件读取失败、HTTP请求异常
System.err.println("IO/网络异常:" + e.getMessage());
} catch (Exception e) {
// 通用异常捕获
e.printStackTrace();
}
八、自定义客户端配置
默认客户端已满足绝大多数场景,如需自定义超时时间、HTTP客户端配置、重试策略、心跳参数等,可手动创建配置对象传入,灵活性拉满:
import com.github.wechat.ilink.sdk.core.config.ILinkConfig;
import java.time.Duration;
import java.net.http.HttpClient;
// 1. 自定义HTTP客户端
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10)) // 连接超时10秒
.build();
// 2. 自定义SDK配置
ILinkConfig config = ILinkConfig.builder()
.connectTimeoutMs(15000) // 连接超时15秒
.readTimeoutMs(15000) // 读取超时15秒
.writeTimeoutMs(15000) // 写入超时15秒
.httpMaxRetries(5) // HTTP最大重试次数5次
.retryBaseDelayMs(1000) // 重试基础退避时间1秒
.retryMaxDelayMs(10000) // 最大退避时间10秒
.heartbeatEnabled(true) // 启用心跳探测
.heartbeatIntervalMs(30000) // 心跳间隔30秒
.channelVersion("1.0.0") // 渠道版本
.build();
// 3. 初始化自定义配置的客户端
ILinkClient client = ILinkClient.builder()
.config(config)
.httpClient(httpClient)
.build();
九、SDK内置亮点特性(2.1.0升级重点)
-
**会话过期自动检测**:精准捕获ret=-14异常,抛出专属会话过期异常,无需手动判断错误码,简化异常处理逻辑。
-
**指数退避重试**:网络波动、接口超时场景自动重试,避免单次请求失败导致业务中断,保障服务稳定性。
-
**资源自动管理**:内置HTTP连接池、线程池管理,实现AutoCloseable接口,调用close()即可释放所有资源,防止内存泄漏。
-
**日志适配**:兼容SLF4J日志框架,可接入项目现有日志体系,方便调试和线上监控,日志粒度可灵活控制。
-
**上下文自动缓存**:拉取消息后自动缓存最新contextToken,发送消息时无需手动传入,降低调用难度。
-
**Builder模式优化**:客户端、配置均支持Builder模式,代码更简洁,配置更灵活,易于扩展。
十、常见问题与避坑指南(高频问题汇总)
- Q:发送消息提示“missing latest context token”? A:原因:目标用户未给Bot发过消息,或消息未被getUpdates()拉取到,导致SDK无法获取contextToken。
解决:让目标用户先发一条消息 → 调用getUpdates()拉取消息 → 再执行发送操作。
-
Q:登录失败或登录结果为空? A:优先检查:网络是否正常、二维码是否过期、登录轮询是否完整、是否成功获取LoginContext。
-
Q:接收不到新消息? A:检查cursor是否由SDK自动管理,无需手动传入;若手动维护cursor,需确保每次使用上一次返回的nextCursor。
-
Q:媒体消息发送失败? A:先确认文件能正常读取、媒体类型值匹配;发送图片/视频/文件/语音时,无需手动上传媒体,SDK会自动处理。
-
Q:媒体下载失败? A:优先检查:消息项是否包含媒体、CDNMedia的encrypt_query_param和aes_key是否存在、下载后文件是否正确保存。
-
Q:登录掉线后无法恢复? A:当前产品模型下,登录状态失效后需手动重新登录,不支持自动重新拉起二维码流程,可捕获会话过期异常触发重新登录。
十一、项目总结与后续规划
微信iLink Bot Java SDK 2.1.0 核心目标是“让iLink Bot开发更简单、更稳定”,无需关注底层协议细节,无需重复开发通用能力,一键集成登录、消息、媒体等核心功能,适配企业级开发场景,稳定、易用、可扩展。
相较于1.0版本,2.1.0进一步优化了封装逻辑,完善了功能覆盖,新增了上下文自动缓存、Builder模式、心跳探测等特性,解决了开发者在对接过程中遇到的各类痛点,让开发者能将更多精力聚焦在业务逻辑上。
后续将随微信官方接口开放持续迭代扩展
目前我也正在基于该协议做一些有趣的项目产品开发,大家感兴趣的可以持续关注一下。
再次附上项目开源地址:github.com/lith0924/we…,欢迎大家Star支持,提交Issue反馈问题、提交PR贡献代码,一起完善这个工具,让更多开发者受益!