小程序向公眾號發送消息開發實戰(一)


之前寫的小程序項目有一個向用戶推送消息的功能,一開始打算使用小程序提供的模板消息
但是后來發現小程序發送模板消息有很大的局限性,小程序發送模板消息需要收集用戶提交表單生成的formId,而這個formId只能使用一次,且有效時間為七天,也就是說你不能主動通過微信向用戶發送消息,只有當用戶主動觸發表單事件,你拿到生成的fomId才能調用API發送消息,所以用起來很不方便。
所以改用小程序綁定公眾號,通過公眾號來發送模板消息,不過需要用到公眾號與用戶對應的openid,這個openid和你在小程序里獲得的openid不一樣,小程序得到的openid是用戶與你的小程序對應的唯一id,所以調用公眾號發送模板消息接口自然要使用用戶對應公眾號的openid。
那么怎么在小程序里獲得用戶對應公眾號的openid呢?
這里需要在小程序里用web-view組件打開公眾號授權網頁點擊查看公眾號網頁授權文檔
授權有兩種方式:靜默授權和手動授權。區別請查看網頁授權文檔
授權后會攜帶參數到你設置的redirect_uri頁面,我直接把redirect_uri填寫成我的后台接口接收code,你也可以寫個頁面來接收,不過需要在小程序里配置業務域名
我的大致流程就是用戶在小程序里完成登錄后會直接打開公眾號授權頁面,因為是靜默授權,所以用戶只能看到空白頁面,此后我后台的接口就可以接收到授權成功傳給我的參數,但是在小程序里就無法跳回主界面了,我們使用了頁面加載定時器,在授權頁面打開后三秒跳回小程序主界面

`Page({

data: {
url: ‘https://open.weixin.qq.com/connect/oauth2/authorize?appid=公眾號APPID&redirect_uri=后台接口或頁面&response_type=code&scope=snsapi_base&state=123#wechat_redirect’
},

onLoad:function(){
console.log(‘我返回了’)
setTimeout(function () {
wx.reLaunch({
url: ‘…/info/info’,
})
}, 3000)
},

})`

這里需要在公眾號配置授權回調頁面域名,你的redirect_uri必須在此域名下
重點來了,你必須在微信開放平台創建賬號並將你的小程序和服務號綁定在其下,這樣你就可以獲取unionid,unionid對應開放平台下的小程序和公眾號是唯一的,也就是說一個微信用戶在你的小程序中的onionid和在你公眾號下的onionid是相同的,這樣我們就可以在用戶登錄小程序時獲得用戶的openid和unionid,在完成授權后又可以調用接口獲得openid2和unionid,通過unionid我們就可以將小程序用戶和公眾號用戶關聯起來,換句話說就是你可以通過查詢unionid知道完成網頁授權的用戶是你的哪個小程序用戶了,在之后的發送模板消息功能就可以獲得你小程序用戶的openid2(用戶對應公眾號的openid)來調接口了

獲得小程序openid和unionid:

@SuppressWarnings("unchecked")//獲取小程序用戶的openid,unionid
private Map<String, String> getSessionByCode(String code) {//code是從小程序調用wx.login拿到的code
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId1 + "&secret=" + appSecret1 + "&js_code=" + code + "&grant_type=client_credential";
// 發送請求
String data = HttpUtil.get(url);
ObjectMapper mapper = new ObjectMapper();
Map<String, String> json = null;
try {
json = mapper.readValue(data, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
// 形如{"session_key":"6w7Br3JsRQzBiGZwvlZAiA==","openid":"oQO565cXXXXXEvc4Q_YChUE8PqB60Y"}的字符串
return json;
}

拿到openid和unionid就存入你的用戶表中

獲得公眾號openid2和unionid:

@RequestMapping(value="/getOpenid2")//獲得公眾號openid,此接口為授權回調接口redirect_uri
public @ResponseBody void getOpenid(HttpServletRequest request) throws Exception{
String code=request.getParameter("code");//公眾號授權code
String openid2=getOpenidByCode(code).get("openid");//獲得openid2
Map<String,String> userinfo=WxTemplate.getUserinfo(openid2,appId2,appSecret2);//獲取用戶詳細信息
String unionid=userinfo.get("unionid");//unionid
loginService.addOpenid2ByUnionid(openid2,unionid);//根據unionid將openid2存入用戶表中
}

HttpUtil:

public class HttpUtil {

private static final String Charset = "utf-8";


/**
* 發送請求,如果失敗,會返回null
* @param url
* @param map
* @return
*/
public static String post(String url, Map<String, String> map) {
// 處理請求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);

// 添加參數
List<NameValuePair> params = new ArrayList<NameValuePair>();
for (String str : map.keySet()) {
params.add(new BasicNameValuePair(str, map.get(str)));
}
post.setEntity(new UrlEncodedFormEntity(params, Charset));
// 執行請求
HttpResponse response = client.execute(post);

if (response.getStatusLine().getStatusCode() == 200) {
// 處理請求結果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
// 關閉流
if (in != null)
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}

return buffer.toString();
} else {
return null;
}
} catch (Exception e1) {
e1.printStackTrace();
}
return null;

}

/**
* 發送請求,如果失敗會返回null
* @param url
* @param str
* @return
*/
public static String post(String url, String str) {
// 處理請求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str, Charset));
// 執行請求
HttpResponse response = client.execute(post);

if (response.getStatusLine().getStatusCode() == 200) {
// 處理請求結果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}

} finally {
// 關閉流
if (in != null)
in.close();
}

return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;

}

/**
* 發送GET方式的請求,並返回結果字符串。
* <br>
* 時間:2017年2月27日,作者:http://wallimn.iteye.com
* @param url
* @return 如果失敗,返回為null
*/
public static String get(String url) {
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpGet get = new HttpGet(uri);
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}

} finally {
if (in != null)
in.close();
}

return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;

}
}

WxTemplate:

public class WxTemplate {
@SuppressWarnings("unchecked")
public static Map<String,String> getUserinfo(String openid2,String appId2,String appSecret2) {//獲得用戶關於公眾號的詳細信息
String access_token=getAccess_token(appId2,appSecret2);
String userInfo=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/user/info?access_token="+access_token+"&openid="+openid2+"&lang=zh_CN");
ObjectMapper mapper2 = new ObjectMapper();
Map<String, String> json2=null;
try {
json2 = mapper2.readValue(userInfo, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
return json2;
}
@SuppressWarnings("unchecked")
public static void sendMsg(String openid2,String id,String type,String checkTime,String appId2,String appSecret2) {//發送模板消息
WxMssVo wx=new WxMssVo();//發送模板消息請求參數封裝對象
wx.setTouser(openid2);
wx.setTemplate_id("1FePKYxjLfISyDLvuzDXjOmIk3QmG3EMbusfNQD76Lk");
Map<String,String> miniprogram=new HashMap<>();
miniprogram.put("appid","你的小程序appid");
miniprogram.put("pagepath","pages/checked/checked?id="+id);
wx.setMiniprogram(miniprogram);
Map<String,String> first=new HashMap<>();
Map<String,String> keyword1=new HashMap<>();
Map<String,String> keyword2=new HashMap<>();
Map<String,String> remark=new HashMap<>();
first.put("value","您的"+type+"申請已通過審核");
first.put("color","#173177");
keyword1.put("value","申請通過");
keyword1.put("color","#173177");
keyword2.put("value",checkTime);
keyword2.put("color","#173177");
remark.put("value","點擊查看詳情");
remark.put("color","#173177");
Map<String, Map<String,String>> map= new HashMap<>();
map.put("first",first);
map.put("keyword1",keyword1);
map.put("keyword2",keyword2);
map.put("remark",remark);
wx.setData(map);
String jsonString = JSON.toJSONString(wx);
String access_token=getAccess_token(appId2,appSecret2);
String data= HttpUtil.post("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+access_token,jsonString);
System.out.println(data);
}
@SuppressWarnings("unchecked")
private static String getAccess_token(String appId2,String appSecret2) {//獲得公眾號access_token
String access_token=CacheManager.get("access_token");//從緩存中獲取access_token
if(access_token==null){
access_token=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId2+"&secret="+appSecret2);
ObjectMapper mapper = new ObjectMapper();
Map<String, String> json;
try {
json = mapper.readValue(access_token, Map.class);
access_token=json.get("access_token");
CacheManager.set("access_token",access_token,7200*1000);//將access_token存入緩存,設置過期時間為兩個小時
} catch (Exception e) {
e.printStackTrace();
}
}
return access_token;
}
}

發送模板消息請求參數封裝對象:

public class WxMssVo {
private String touser;//openid2
private String template_id;//模板id
private Map<String,String> miniprogram;//小程序appid
private Map<String, Map<String,String>> data;//數據

public Map<String, String> getMiniprogram() {
return miniprogram;
}

public void setMiniprogram(Map<String, String> miniprogram) {
this.miniprogram = miniprogram;
}

public String getTouser() {
return touser;
}

public void setTouser(String touser) {
this.touser = touser;
}

public String getTemplate_id() {
return template_id;
}

public void setTemplate_id(String template_id) {
this.template_id = template_id;
}

public Map<String, Map<String, String>> getData() {
return data;
}

public void setData(Map<String, Map<String, String>> data) {
this.data = data;
}
}

緩存管理類:

public class CacheManager {

@SuppressWarnings("rawtypes")
private static Map<String, CacheData> cache = new ConcurrentHashMap();

/**
* 啟動定時任務清理過期緩存,避免內存溢出
*/
static {
Timer t = new Timer();
t.schedule(new ClearTimerTask(cache), 0, 7000 * 1000);
}

/**
* 設置緩存,不過期
* @param key
* @param t
*/
public static <T> void set(String key, T t) {
cache.put(key, new CacheData(t, 0));
}

/**
* 設置緩存,指定過期時間expire(單位毫秒)
* @param key
* @param t
* @param expire 過期時間
*/
public static <T> void set(String key, T t, long expire) {
cache.put(key, new CacheData(t, expire));
}

/**
* 根據key獲取指定緩存
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
CacheData<T> data = cache.get(key);
if(null == data) {
return null;
}
if(data.isExpire()) {
remove(key);
return null;
}
return data.getData();
}

/**
* 移除指定key緩存
* @param key
*/
public static void remove(String key) {
cache.remove(key);
}

/**
* 移除所有緩存
*/
public static void removeAll() {
cache.clear();
}

private static class CacheData<T> {
// 緩存數據
private T data;
// 過期時間(單位,毫秒)
private long expireTime;

public CacheData(T t, long expire) {
this.data = t;
if(expire <= 0) {
this.expireTime = 0L;
} else {
this.expireTime = Calendar.getInstance().getTimeInMillis() + expire;
}
}
/**
* 判斷緩存數據是否過期
* @return true表示過期,false表示未過期
*/
public boolean isExpire() {
if(expireTime <= 0) {
return false;
}
if(expireTime > Calendar.getInstance().getTimeInMillis()) {
return false;
}
return true;
}

public T getData() {
return data;
}
}

/**
* 清理過期數據定時任務
*/
private static class ClearTimerTask extends TimerTask {

@SuppressWarnings("rawtypes")
Map<String, CacheData> cache;

@SuppressWarnings("rawtypes")
public ClearTimerTask(Map<String, CacheData> cache) {
this.cache = cache;
}

@Override
public void run() {
Set<String> keys = cache.keySet();
for(String key : keys) {
CacheData<?> data = cache.get(key);
if(data.expireTime <= 0) {
continue;
}
if(data.expireTime > Calendar.getInstance().getTimeInMillis()) {
continue;
}
cache.remove(key);
System.out.println("remove"+key);
}
}
}
}
模板id需要去模板庫中挑選或者自己申請新的模板,不過審核時間有點長

代碼有不足之處請各位大佬指出


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM