- 最终我们会在公众号中收到消息通知。如果我们发送时候设置了跳转小程序或者跳转链接的话,点击之后就会进行页面跳转。
- 还记得我们在开发者配置的时候需要我们配置一个外网的服务接口地址吗。我是通过ngrok进行玩网映射的。为什么微信需要我们配置一个交互接口呢?因为微信会将用户与微信公众号的交互动作转发到我们配置的服务上。这样我们就能够基于用户的反馈进行回应了。
- 同样的我们这里只关心【客服接口-发消息】。其他接口我们就不看了。有需要的我们自己封装就行了。因为实际上就是调用接口而已。没必要每个地方都指出来。
请求方式 | url | 参数 |
POST | api.weixin.qq.com/cgi-bin/mes… | json |
- 关于请求体这里需要着重说明下。因为在客服消息中支持发送【文本】、【图片】、【语音】、【视频】、【音乐】、【图文】,甚至我们还可以通过客服消息接口发送【菜单】消息,【卡券】消息。
- 为了方便后续各种消息结构的扩展,在代码中我将消息进行抽象化。
public class Message {
//文本消息
protected static final String MESSAGE_TEXT="text";
//图片消息
protected static final String MESSAGE_IMAGE="image";
//语音消息
protected static final String MESSAGE_VOICE="voice";
//音乐
protected static final String MESSAGE_MUSIC="music";
//图文
protected static final String MESSAGE_NEWS="news";
//文件
protected static final String MESSAGE_FILE="file";
//链接消息
protected static final String MESSAGE_LINK="link";
//markdown消息
protected static final String MESSAGE_MARKDOWN="markdown";
//卡片消息
protected static final String MESSAGE_CARD="action_card";
private String type;
public Message(String type) {
this.type = type;
}
public Map<String, Object> initMap() {
Map<String, Object> map = new HashMap<>();
map.put("msgtype", this.type);
return map;
}
}
复制代码
- 目前列举所有相关类型。后续如果新增了消息类型我们可以在此处新增,也完全可以不在此处枚举,只需要保证每个子类对应的拥有type就行了。其中有些类型是钉钉中的类型的。因为我对比了两遍的消息结构基本上一致的。都是通过msgtype指定消息类型的。所以基类中提供了初始化请求体方法,就是将对应的type填充进去。
public class ImageMessage extends Message {
private String mediaId;
public ImageMessage(String mediaId) {
super(MESSAGE_IMAGE);
this.mediaId = mediaId;
}
@Override
public String toString() {
Map<String, Object> map = initMap();
Map<String, Object> childMap = new HashMap<>();
childMap.put("media_id", this.mediaId);
map.put(getType(), childMap);
return JSON.toJSONString(map);
}
}
复制代码
- 比如我们需要发送图片消息,那么我们只需要实现继承Message的子类,并制定自己对应的type .
{
"touser":"OPENID",
"msgtype":"image",
"image":
{
"media_id":"MEDIA_ID"
}
}
复制代码
- 自信观察分析下,图文消息除了类型以外,我们还需要一个media_id就可以完成消息的构建。所以我们在ImageMessage构造中再接收一个media_id就可以了。最终重写toString构建最终的消息体就可以了。
- 发送文本消息很简单,我们直接构建对应的TextMessage就可以了。但是发送图片消息时,微信为了加快性能响应,所以他要求开发者必须先将图片上传至素材库。
- 而上文提到的media_id就是在素材库中的一个唯一ID,我们发送的时候只需要将ID给微信,微信会自动在素材库中寻找对应的素材发送给用户。
- 在【素材管理】中提供了上传素材的接口。我们根据自己需要看时上传临时的还是永久的。我们这里就临时的解决。
请求方式 | url | 参数 |
POST | api.weixin.qq.com/cgi-bin/med… | json |
- 官网上也明确提到了支持的素材格式为图片(image)、语音(voice)、视频(video)和缩略图(thumb);同样为了后期的扩展,这里的文件格式我也进行了抽象
@Data
public class Meterial {
//图片
//钉钉 :图片,图片最大1MB。支持上传jpg、gif、png、bmp格式
//微信 :10M,支持PNG\JPEG\JPG\GIF格式
protected static final String METERIAL_IMAGE="image";
//语音
//钉钉 : 语音,语音文件最大2MB。支持上传amr、mp3、wav格式
//微信 : 2M,播放长度不超过60s,支持AMR\MP3格式
protected static final String METERIAL_VOICE="voice";
//视频
//钉钉: 视频,视频最大10MB。支持上传mp4格式。
//* 10MB,支持MP4格式
protected static final String METERIAL_VIDEO="video";
//文件
//仅钉钉支持 : 普通文件,最大10MB。支持上传doc、docx、xls、xlsx、ppt、pptx、zip、pdf、rar格式
protected static final String METERIAL_FILE="file";
//缩略图
//仅微信支持 : 64KB,支持JPG格式
protected static final String METERIAL_THUMB="thumb";
private String type;
public Meterial(String type) {
this.type = type;
}
}
复制代码
- 而子类只需制定类型就行。不需要做其他的工作。上传素材时需要上传文件。所以我们在HttpUtils中还需要在准备一个文件上传的功能。为了方便在服务器间交互,这里我们接收InputStream 。
public static String upload(InputStream inputStream , String fileName,String urlLink) throws IOException {
String result = StringUtils.EMPTY;
URL url=new URL(urlLink);
HttpsURLConnection conn=(HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");//以POST方式提交表单
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);//POST方式不能使用缓存
//设置请求头信息
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Charset", "UTF-8");
//设置边界
String BOUNDARY="----------" System.currentTimeMillis();
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" BOUNDARY);
//请求正文信息
//第一部分
StringBuilder sb=new StringBuilder();
sb.append("--");//必须多两条道
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data;name=\"media\"; filename=\"" fileName "\"\r\n");
sb.append("Content-Type:application/octet-stream\r\n\r\n");
System.out.println("sb:" sb);
//获得输出流
OutputStream out=new DataOutputStream(conn.getOutputStream());
//输出表头
out.write(sb.toString().getBytes("UTF-8"));
//文件正文部分
//把文件以流的方式 推送道URL中
DataInputStream din=new DataInputStream(inputStream);
int bytes=0;
byte[] buffer=new byte[1024];
while((bytes=din.read(buffer))!=-1){
out.write(buffer,0,bytes);
}
din.close();
//结尾部分
byte[] foot=("\r\n--" BOUNDARY "--\r\n").getBytes("UTF-8");//定义数据最后分割线
out.write(foot);
out.flush();
out.close();
if(HttpsURLConnection.HTTP_OK==conn.getResponseCode()){
StringBuffer strbuffer=null;
BufferedReader reader=null;
try {
strbuffer=new StringBuffer();
reader=new BufferedReader(new InputStreamReader(conn.getInputStream()));
String lineString=null;
while((lineString=reader.readLine())!=null){
strbuffer.append(lineString);
}
if(StringUtils.isEmpty(result)){
result=strbuffer.toString();
System.out.println("result:" result);
}
} catch (IOException e) {
System.out.println("发送POST请求出现异常!" e);
e.printStackTrace();
}finally{
if(reader!=null){
reader.close();
}
}
}
return result;
}
复制代码
测试用例
- three-party-message/message-demo/src/test/java/com/github/zxhtom/message/demo/WechatTest.upload用于上传素材
- three-party-message/message-demo/src/test/java/com/github/zxhtom/message/demo/WechatTest.sendPic用于发送图片给用户