微信生成带参数的二维码,合成海报,扫码后推送小程序
背景:公司開發(fā)的小程序要實現(xiàn)將產(chǎn)品免費給用戶試用的功能,用戶登錄小程序后在產(chǎn)品頁可以將產(chǎn)品以二維碼海報的方式分享給微信好友,好友掃碼后跳轉(zhuǎn)公眾號,關(guān)注后公眾號推送小程序,點擊小程序后跳轉(zhuǎn)到小程序中的相應(yīng)產(chǎn)品頁面。
如下圖:
這里涉及到兩個重要的環(huán)節(jié):
1.生成帶參數(shù)(產(chǎn)品id,產(chǎn)品縮略圖、分享人openid,小程序跳轉(zhuǎn)路徑等)的二維碼;
2.掃碼后的事件推送,將小程序推送給好友。
一、微信公眾平臺申請服務(wù)號并通過認證
獲得?生成帶參數(shù)的二維碼、接收事件推送、客服接口?接口權(quán)限。
二、驗證請求來自微信
登錄公眾號在 開發(fā)-基本配置中配置開發(fā)人員服務(wù)器信息:
該該服務(wù)器url作為開發(fā)者驗證接口調(diào)用者來自微信服務(wù)器,也作為掃碼關(guān)注微信公眾號后的事件推送接口url。
/*** 驗證微信服務(wù)器* @param signature* @param timestamp* @param nonce* @param echostr* @return*/@RequestMapping(value = "/checkSignature", method = {RequestMethod.GET})public Object validate(String signature, String timestamp, String nonce, String echostr) {LOG.info("signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " echostr:" + echostr);if (StringUtils.isNotBlank(echostr)) {LOG.info("********************************");String signatureRet = SignUtil.getSignature(timestamp, nonce, "jpkj");LOG.info("signatureRet:" + signatureRet);if (StringUtils.isNotBlank(signatureRet) && signatureRet.equals(signature)) {return echostr;}}return "";}?三、事件推送接口
用于接收掃碼后關(guān)注公眾號,微信服務(wù)器將xml數(shù)據(jù)推送給此接口,接收xml數(shù)據(jù)后推送小程序。
注意:接口要與上面的服務(wù)器url路徑一致,請求的路徑是一樣的,但是提交的數(shù)據(jù)方式不同,驗證的http是GET提交,推送則是POST方式。
/*** 微信掃碼后事件推送方法** @param msg 事件輸入xml數(shù)據(jù)封裝* @return* @throws Exception*/@RequestMapping(value = "/checkSignature", method = {RequestMethod.POST}, produces = {MediaType.TEXT_XML_VALUE})public Object pushEvent(@RequestBody InMsgEntity msg) throws Exception {LOG.info("進入方法***************************");LOG.info("msg:" + msg.toString());String accessToken = redisService.getValue("access_token");if (StringUtils.isEmpty(accessToken)) {return ServiceResultHelper.genResultWithFaild(Constant.ErrorCode.INVALID_PARAM_MSG, Constant.ErrorCode.INVALID_PARAM_CODE);}String PUSH_APPLET_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken;if (null != msg) {LOG.info("*************推送*************");if (msg.getMsgType().equals("event")) {LOG.info("*************推送小程序開始*************");Map<String, Object> params = new HashMap<>();params.put("touser", msg.getFromUserName());params.put("msgtype", "miniprogrampage");Map<String, Object> miniprogrampageMap = new HashMap<>();miniprogrampageMap.put("title", "小程序");miniprogrampageMap.put("appid", "wx5c5e9ffc305b66d2111");miniprogrampageMap.put("thumb_media_id", "tByjOrKtvK71V0XZUJ9RMCPyYSbkp2A8CyZCo6W6bK8s");params.put("miniprogrampage", miniprogrampageMap);String result = HttpClientUtil.postJson(PUSH_APPLET_URL, JSON.toJSONString(params), "UTF-8");LOG.info("*************推送小程序結(jié)束result:" + result);}}return null;}注:appid為要推送的小程序的appid,thumb_media_id為推送時小程序附帶的縮略圖,可調(diào)用微信提供的上傳素材接口獲取id。
InMsgEntity類:?對微信推送的xml格式的數(shù)據(jù)進行封裝
package com.jp.tech.applet.ms.scancode.domain;import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement;/*** 微信推送XML數(shù)據(jù)包實體*/@XmlRootElement(name="xml") @XmlAccessorType(XmlAccessType.FIELD) public class InMsgEntity {// 開發(fā)者微信號@XmlElement(name="FromUserName")protected String FromUserName;// 發(fā)送方帳號(一個OpenID)@XmlElement(name="ToUserName")protected String ToUserName;// 消息創(chuàng)建時間@XmlElement(name="CreateTime")protected Long CreateTime;/*** 消息類型* text 文本消息 * image 圖片消息 * voice 語音消息 * video 視頻消息 * music 音樂消息*/@XmlElement(name="MsgType")protected String MsgType;//事件類型,subscribe@XmlElement(name="Event")protected Long Event;// 事件KEY值,qrscene_為前綴,后面為二維碼的參數(shù)值@XmlElement(name="EventKey")private String EventKey;// 二維碼的ticket,可用來換取二維碼圖片@XmlElement(name="Ticket")private String Ticket;//消息id@XmlElement(name="MsgId ")private String MsgId ;//文本內(nèi)容@XmlElement(name="Content ")private String Content ;public String getFromUserName() {return FromUserName;}public void setFromUserName(String fromUserName) {FromUserName = fromUserName;}public String getToUserName() {return ToUserName;}public void setToUserName(String toUserName) {ToUserName = toUserName;}public Long getCreateTime() {return CreateTime;}public void setCreateTime(Long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public Long getEvent() {return Event;}public void setEvent(Long event) {Event = event;}public String getEventKey() {return EventKey;}public void setEventKey(String eventKey) {EventKey = eventKey;}public String getTicket() {return Ticket;}public void setTicket(String ticket) {Ticket = ticket;}public String getMsgId() {return MsgId;}public void setMsgId(String msgId) {MsgId = msgId;}public String getContent() {return Content;}public void setContent(String content) {Content = content;}@Overridepublic String toString() {return "InMsgEntity{" +"FromUserName='" + FromUserName + '\'' +", ToUserName='" + ToUserName + '\'' +", CreateTime=" + CreateTime +", MsgType='" + MsgType + '\'' +", Event=" + Event +", EventKey='" + EventKey + '\'' +", Ticket='" + Ticket + '\'' +", MsgId='" + MsgId + '\'' +", Content='" + Content + '\'' +'}';} }獲取事件推送的xml數(shù)據(jù)后,提取所需數(shù)據(jù),調(diào)用客服服務(wù)接口推送小程序
詳見:客服接口-發(fā)消息
發(fā)送小程序卡片(要求小程序與公眾號已關(guān)聯(lián))
接口調(diào)用示例:
{"touser":"OPENID","msgtype":"miniprogrampage","miniprogrampage":{"title":"title","appid":"appid","pagepath":"pagepath","thumb_media_id":"thumb_media_id"} }?通過下面的命令將阿里云上的圖片上傳至微信服務(wù)器作為永久素材。詳見:新增其他類型永久素
材
返回說明:
{"media_id":MEDIA_ID,"url":URL }返回參數(shù)說明
| media_id | 新增的永久素材的media_id |
| url | 新增的圖片素材的圖片URL(僅新增圖片素材時會返回該字段) |
media_id可以作為推送小程序時的縮略圖使用。代碼中的thumb_media_id使用此命令上傳后返回的media_id即可。
四、獲取微信公眾號access_token,存入redis
獲取帶參數(shù)的二維碼首先要獲取access_token,放到redis中緩存。由于access_token的有效期是兩個小時,所以這里我用的定時任務(wù)是quartz,與springboot整合(不知道怎么整合的請參見springboot整合Quartz實現(xiàn)定時任務(wù)、springboot整合redis實現(xiàn)發(fā)送短信驗證碼)后獲取access_token。
代碼如下:
application.properties中加入微信公眾號的appid和secret:
#微信公眾號 wx.gzh.appid=wx6e3199c6254e43b3huh1 wx.gzh.appsecret=713966cacd2a5da74fa8024a4958bac0dsdq定時任務(wù)獲取access_token:?
package com.jp.tech.applet.web.schedule;import com.jp.tech.applet.ms.scancode.util.AccessTokenUtil; import com.jp.zpzc.service.IRedisService; import org.apache.commons.lang3.StringUtils; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value;import javax.annotation.Resource;/*** @author yangfeng* @desciption 獲取微信公眾號access_token任務(wù)* @date 2018/9/10*/ public class GetAccessTokenTask implements Job {private static Logger LOG = LoggerFactory.getLogger(GetAccessTokenTask.class);@Resourceprivate IRedisService redisService;@Value("${wx.gzh.appid}")String appid;@Value("${wx.gzh.appsecret}")String secret;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {try {String accessToken = AccessTokenUtil.getAccessToken(appid, secret);LOG.info("****access_token:******"+accessToken);if (StringUtils.isNotBlank(accessToken)) {redisService.setKeyNoExpire("access_token", accessToken);}} catch (Exception e) {LOG.error(e.getMessage());}} }獲取token工具類:
package com.jp.tech.applet.ms.scancode.util;import com.alibaba.fastjson.JSONObject; import com.jp.tech.applet.common.http.HttpClientUtil; import org.apache.commons.lang3.StringUtils;/*** 獲取公眾號access_token*/ public class AccessTokenUtil {private static String ACCESSTOKENURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";/*** 獲取access_Token** @return*/public static String getAccessToken(String appId, String appSecret) throws Exception {ACCESSTOKENURL = ACCESSTOKENURL.replace("{appId}", appId).replace("{appSecret}", appSecret);String result = HttpClientUtil.post(ACCESSTOKENURL, null, "UTF-8");if (StringUtils.isNotBlank(result)) {return JSONObject.parseObject(result).getString("access_token");}return null;} }redis接口:
public interface IRedisService {void setKeyNoExpire(String var1, String var2); }redis實現(xiàn)類:?
@Service public class RedisService implements IRedisService {@Resourceprivate RedisTemplate redisTemplate;public RedisService() {}public void setKeyNoExpire(String key, String value) {ValueOperations<String, String> ops = this.redisTemplate.opsForValue();ops.set(key, value);} }五、根據(jù)access_token生成ticket
/*** 創(chuàng)建二維碼ticket** @param accessToken 微信access_token* @param objectId 項目id* @param openId 微信用戶openid* @param path 掃碼后小程序跳轉(zhuǎn)的頁面路徑* @param thumbMediaId 小程序展示的縮略圖id* @param picId 海報圖片id* @return* @throws Exception*/public String generateTicket(String accessToken, String objectId, String openId, String path, String thumbMediaId, String picId) throws Exception {LOG.info("************* 創(chuàng)建二維碼ticket*************");String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;Map<String, Object> params = new HashMap<>();params.put("expire_seconds", 604800);params.put("action_name", "QR_STR_SCENE");Map<String, Object> actionInfoMap = new HashMap<>();Map<String, Object> sceneMap = new HashMap<>();Map<String, Object> sceneValMap = new HashMap<>();sceneValMap.put("objectId", objectId);sceneValMap.put("openId", openId);sceneValMap.put("path", path);sceneValMap.put("thumbMediaId", thumbMediaId);sceneValMap.put("picId", picId);sceneMap.put("scene_str", JSON.toJSONString(sceneValMap));LOG.info("*********scene_str:*************" + JSON.toJSONString(sceneValMap));actionInfoMap.put("scene", sceneMap);params.put("action_info", actionInfoMap);LOG.info("*********json參數(shù):*************" + JSON.toJSONString(params));return HttpClientUtil.postJson(TICKET_URL, JSON.toJSONString(params), "UTF-8");}
? ? ?* @param objectId ? ? 項目id
? ? ?* @param openId ? ? ? 微信用戶openid
? ? ?* @param path ? ? ? ? 掃碼后小程序跳轉(zhuǎn)的頁面路徑
? ? ?* @param thumbMediaId 小程序展示的縮略圖id
? ? ?* @param picId ? ? ? ?海報圖片id
這些參數(shù)在生成ticket的時候放入scene_str中,根據(jù)ticket來生成二維碼,掃碼的時候可以從推送事件中獲取這些參數(shù)。
詳見:生成帶參數(shù)的二維碼
六、根據(jù)ticket獲取二維碼
/*** 根據(jù)ticket生成二維碼** @param ticketResult 票據(jù)生成結(jié)果* @param picId* @param response*/public static Object generateQRcodeByTicket(String ticketResult, String picId, HttpServletResponse response) {String ticket = JSONObject.parseObject(ticketResult).getString("ticket");LOG.info("*********ticket:********" + ticket);//生成帶參數(shù)二維碼if (StringUtils.isNotBlank(ticket)) {String GET_QRCORE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;//生成二維碼,和海報背景合成新的圖片createPicture(picId, GET_QRCORE_URL, response);} else {return ServiceResultHelper.genResultWithFaild("生成二維碼失敗", -1);}return null;}七、二維碼和海報背景合成新的圖片
/*** 二維碼和海報背景合成新的圖片** @param picId 海報背景圖在mongodb上的id* @param url 生成帶參數(shù)二維碼接口路徑* @param response* @return*/public static void createPicture(String picId, String url, HttpServletResponse response) {BufferedImage img = new BufferedImage(550, 978, BufferedImage.TYPE_INT_RGB);//創(chuàng)建圖片BufferedImage bg;//讀取海報圖try {bg = ImageIO.read(new URL("http://112.74.186.131:8080/zpzc_ms/file/downloadFile?file_id=" + picId));//讀取微信生成的帶參數(shù)二維碼BufferedImage qRCodeImg = ImageIO.read(new URL(url));Graphics g = img.getGraphics();//開啟畫圖g.drawImage(bg.getScaledInstance(550, 978, Image.SCALE_DEFAULT), 0, 0, null); // 繪制縮小后的圖g.drawImage(qRCodeImg.getScaledInstance(126, 126, Image.SCALE_DEFAULT), 47, 817, null); // 繪制縮小后的圖g.setColor(Color.black);g.dispose();ImageIO.write(img, "JPEG", response.getOutputStream());} catch (IOException e) {LOG.error(e.getMessage());}}八、生成帶參數(shù)二維碼接口
/*** 生成帶參數(shù)二維碼** @param objectId 項目id* @param openId 微信用戶openid* @param path 掃碼后小程序跳轉(zhuǎn)的頁面路徑* @param thumbMediaId 小程序展示的縮略圖id* @param picId 海報背景圖在mongodb上的id* @return* @throws Exception*/@RequestMapping(value = "/generateQRCodeWithParams", method = {RequestMethod.POST, RequestMethod.GET})public Object generateQRCodeWithParams(String objectId, String openId, String path, String thumbMediaId, String picId, HttpServletResponse response) throws Exception {String accessToken = redisService.getValue("access_token");//如果token不存在則生成if (StringUtils.isEmpty(accessToken)) {accessToken = saveToken2Redis();}LOG.info("*********生成帶參數(shù)二維碼方法generateQRCodeWithParams********");LOG.info("objectId:" + objectId + " openId:" + openId + " path:" + path + " thumbMediaId:" + thumbMediaId + " picId:" + picId);//創(chuàng)建二維碼ticketString ticketResult = generateTicket(accessToken, objectId, openId, path, thumbMediaId, picId);LOG.info("*********ticketResult:*******" + ticketResult);String errcode = StringUtils.isNotBlank(ticketResult) ? JSONObject.parseObject(ticketResult).getString("errcode") : null;if (StringUtils.isNotBlank(errcode) && String.valueOf(40001).equals(errcode)) {//如果token不是最新的,重新獲取accessToken = saveToken2Redis();LOG.info("*********token不是最新的,重新獲取********");ticketResult = generateTicket(accessToken, objectId, openId, path, thumbMediaId, picId);generateQRcodeByTicket(ticketResult, picId, response);} else {generateQRcodeByTicket(ticketResult, picId, response);}return null;}/*** 生成token并保存到redis** @return* @throws Exception*/public String saveToken2Redis() throws Exception {String accessToken = AccessTokenUtil.getAccessToken(appid, secret);LOG.info("****access_token:******" + accessToken);if (StringUtils.isNotBlank(accessToken)) {redisService.setKeyNoExpire("access_token", accessToken);}return accessToken;}最終在用戶關(guān)注公眾號后推送的小程序如下:
總結(jié)
以上是生活随笔為你收集整理的微信生成带参数的二维码,合成海报,扫码后推送小程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PostgreSQL下载地址
- 下一篇: XiaoHu日志 9/7~9/17