涉及的知识点:接收微信消息事件、生成唯一二维码(产品地址+埋码)、图片上传sftp、两张图片的合并、图片上传微信服务、推送客服消息、记录埋码信息等等。
一、前言:
事情是这样的: 前段时间,周五下午,我正在幻想美好的周末的时候,突然收到我们业务发送的一条微信消息,有个需求需要沟通下。
需求是这样的:业务部门要做一个活动,用来推广一款年金产品。
活动的具体要求是这样的:公司员工在公众号输入的关键字,生成一个唯一带有二维码的推广图片(推广图片+二维码)。客户扫描二维码跳转到产品页面,出单以后要定位是谁推广的。
活动的上线时间:周一上午。还能怎么办?周末加班的命~。心里默念“加班使我快乐”“我爱加班”。
二、需求分析
明白了业务要做什么,接下来就是怎么实现的问题了。需求主要有如下几个问题:
1,我怎么可以定为是哪个员工发送的消息呢?
用户在微信后台发送一条消息,在后台,我可以知道用户发送了什么,也可以知道用户的唯一openid,但是openid和员工并没有关联关系。
当时想的是,做一个绑定关系页面,让每一个想推广的用户先把个人信息和微信进行绑定,这样我就有了openid和用户的绑定关系。当然对于用户体验不好。
第二种方案就是通过关键字来定义。
2,关键字怎么定义呢?
获取到用户发送的消息以后,要判断出,用户是谁?用户发送消息是要干嘛?不能用户随便发送一个消息我都给推送一个图片。
当时想的是,活动名称+手机号,因为手机号是唯一的。业务部门需要有员工和手机号的数据。
最后业务确定用活动名称+姓名,确定公司暂时没有重名的员工。
三、具体实现:
1、获取微信推送的消息事件,在后台做分析。
前提条件是微信必须是开发者模式,需要在微信后台配置回到接口地址
2、接收微信推送的消息事件
微信通过post方式推送报文格式如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
报文中Content就是用户发送的消息,如果用户发送的是“某某活动+用户名”,我们就处理这条消息;如果不是,我们可以做固定字段回复,或者转的客服平台,或者对接智能机器人也可以。
处理用用户消息的时候,需要识别是这次活动,还要考虑扩展性,后期同样类型的活动就不需要开发了,数据库配置下就可以实现。
源码如下:
//获取请求参数
Map<String, String> map = WeChatResplyUtil.parseXmlToMap(request.getInputStream());
logger.info("微信请求的数据:"+JSONObject.fromObject(map));
String msgType = map.get("MsgType");
String userName = map.get("ToUserName");//公众号
String fromUserName = map.get("FromUserName");//用户openid
//文本消息类型
if(WeChatResplyUtil.MESSAGE_TEXT.equals(msgType)){
//获取用户回复的文本消息
String content = map.get("Content");
//判断
if(StringUtils.isNotBlank(content)&&content.contains("+")){
//获取配置信息,配置活动关键字,活动图片地址,产品地址
List<NsType> nsType = nsTypeDao.findByTypeSortAndLikeTypeCode("PRODUCT_URL_QR", keyWord.split("\\+")[0]);
if(nsType!=null||nsType.size()>=0){
//多线程处理业务
threadpool.execute(....);
return "";
}
}
//如果不是活动,可以处理关键字回复,人工客服,智能机器人等
微信有个要求,获取到用户消息以后,必须5s内响应。确定是活动的关键字,直接响应微信""或者"success"。开启多线程处理业务。
2、根据用户发送的消息生成唯一的二维码,并上传服务器。
获取活动的产品地址productUrl 和唯一的标识randomString 生成二维码(不是微信接口生成的二维码),并上传服务器。然后活动图片和二维码合并。
//获取产品地址
String productUrl = nsType.get(0).getRelationtypecode();
//获取推广图片
String tgImageUrl = nsType.get(0).getTypeDesc();
//获取随机字符串
String randomString = "rh_ryb_"+UUID.randomUUID();
//图片上传途径(别的需求配置过的)
String filePath = sysConfigService.getConfig("IDNO_IMAGE_URL") + "QRCode/";
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
//生成二维码
String qrUrl = filePath+randomString+".png";
boolean qrStatus = QRCodeUtil.generateQRCodeImage(productUrl+randomString, 350, 350, qrUrl);
if(qrStatus){
WXControllerLog.INFO("生成二维码成功,二维码地址qrUrl:"+qrUrl);
}
//合并图片
BufferedImage b = ImageUtil.loadImageLocal(qrUrl);
BufferedImage d = ImageUtil.loadImageLocal(tgImageUrl);
String img_suc = filePath+randomString+"_suc.jpg";
ImageUtil.writeImageLocal(img_suc,ImageUtil.modifyImagetogeter(b, d));
WXControllerLog.INFO("合并图片,活动图片的地址tgImageUrl:"+tgImageUrl);
WXControllerLog.INFO("合并图片,合并后的地址img_suc:"+img_suc);
合成后的图片如下:
3、推广图片和二维码合并生成一个图片工具类。
public class ImageUtil {
/**
* 把两张图片合并
*/
private static Font font = new Font("宋体", Font.PLAIN, 12); // 添加字体的属性设置
private static Graphics2D g = null;
private static int fontsize = 0;
private static int x = 0;
private static int y = 0;
/**
* 导入本地图片到缓冲区
*/
public static BufferedImage loadImageLocal(String imgName) {
try {
return ImageIO.read(new File(imgName));
} catch (IOException e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 导入网络图片到缓冲区
*/
public static BufferedImage loadImageUrl(String imgName) {
try {
URL url = new URL(imgName);
return ImageIO.read(url);
} catch (IOException e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 生成新图片到本地
*/
public static void writeImageLocal(String newImage, BufferedImage img) {
if (newImage != null && img != null) {
try {
File outputfile = new File(newImage);
ImageIO.write(img, "jpg", outputfile);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
/**
* 设定文字的字体等
*/
public static void setFont(String fontStyle, int fontSize) {
fontsize = fontSize;
font = new Font(fontStyle, Font.PLAIN, fontSize);
}
/**
* 修改图片,返回修改后的图片缓冲区(只输出一行文本)
*/
public static BufferedImage modifyImage(BufferedImage img, Object content, int x, int y) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.orange);//设置字体颜色
if (font != null)
g.setFont(font);
// 验证输出位置的纵坐标和横坐标
if (x >= h || y >= w) {
x = h - fontsize + 2;
y = w;
} else {
x = x;
y = y;
}
if (content != null) {
g.drawString(content.toString(), x, y);
}
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
/**
* 修改图片,返回修改后的图片缓冲区(输出多个文本段) xory:true表示将内容在一行中输出;false表示将内容多行输出
*/
public BufferedImage modifyImage(BufferedImage img, Object[] contentArr, int x, int y,
boolean xory) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.RED);
if (this.font != null)
g.setFont(this.font);
// 验证输出位置的纵坐标和横坐标
if (x >= h || y >= w) {
this.x = h - this.fontsize + 2;
this.y = w;
} else {
this.x = x;
this.y = y;
}
if (contentArr != null) {
int arrlen = contentArr.length;
if (xory) {
for (int i = 0; i < arrlen; i++) {
g.drawString(contentArr[i].toString(), this.x, this.y);
this.x += contentArr[i].toString().length() * this.fontsize / 2 + 5;// 重新计算文本输出位置
}
} else {
for (int i = 0; i < arrlen; i++) {
g.drawString(contentArr[i].toString(), this.x, this.y);
this.y += this.fontsize + 2;// 重新计算文本输出位置
}
}
}
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
/**
* 修改图片,返回修改后的图片缓冲区(只输出一行文本)
*/
public BufferedImage modifyImageYe(BufferedImage img) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.blue);//设置字体颜色
if (this.font != null)
g.setFont(this.font);
g.drawString("www.hi.baidu.com?xia_mingjian", w - 85, h - 5);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
public static BufferedImage modifyImagetogeter(BufferedImage b, BufferedImage d) {
try {
int w = 110;//b.getWidth();
int h = 110;//b.getHeight();
g = d.createGraphics();
g.drawImage(b, 515, 830, w, h, null);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return d;
}
}
4,生成的图片上传微信服务器,获取media_id,数据库记录信息。
推送微信图片消息,需要获取到图片的media_id,所以需要把合成的图片上传微信获取media_id。并保存信息,再次推送图片消息的时候可以直接数据库查询。数据库记录唯一标识、medis_id、用户消息、公众号标识等
//获取token
WxConfig wxConfig = wxConfigDao.queryByType("accessToken");
String token =wxConfig.getExtends0();
//图片上传至微信服务器
String uploadMeida = WXUtil.Uploadmaterial( "image", img_suc, token);
WXControllerLog.INFO("图片上传至微信服务器,微信返回信息:"+uploadMeida);
JSONObject parseObject = JSONObject.parseObject(uploadMeida);
try {
WxAutoReply wr = wxAutoReplyDao.findAutoReplyByOpenid(keyWord, tencent,fromUserName);
if(wr==null){//保存信息
wr = new WxAutoReply();
wr.setKeyword(keyWord);
wr.setKeyValue(parseObject.getString("media_id"));
wr.setTencent(tencent);
wr.setMsgtype("image");
wr.setExtends3(keyWord.split("\\+")[1]);
wr.setExtends4(randomString);
wr.setExtends6(fromUserName);
wxAutoReplyDao.saveWxAutoReply(wr);
}else{
wr.setKeyword(keyWord);
wr.setKeyValue(parseObject.getString("media_id"));
wr.setTencent(tencent);
wr.setMsgtype("image");
wr.setExtends3(keyWord.split("\\+")[1]);
wr.setExtends4(randomString);
wr.setExtends6(fromUserName);
wxAutoReplyDao.updateWxAutoReply(wr);
}
} catch (Exception e) {
e.printStackTrace();
}
5、根据media_id给用户退送推广图片。
调用微信接口推送消息
//推送信息
String sendMsg ="{\"touser\":\""+fromUserName+"\",\"msgtype\": \"image\",\"image\":{\"media_id\":\""+parseObject.getString("media_id")+"\"}}";
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="+token;
String responseJson = HttpUtil.postXmlRequest(url, sendMsg,"utf-8");
WXControllerLog.INFO("SendWxMessageThread发送微信消息,返回信息:"+responseJson);
6、最后的效果,如图:
7、用户出单以后,数据库记录埋码标识。
只要在用户出单的时候,记录唯一标识randomString就可以了
注:其实需要处理的问题还是挺多的,篇幅有限就只粘贴关键代码了,我觉得上面的工具栏贼好用就直接全部分享了。
微信开发中遇到的相关问题可以留言或者私信,有问必答。

本文暂时没有评论,来添加一个吧(●'◡'●)