微信公衆號和微信小程序結合
1.需要申請一個微信公衆號並且資質審覈通過,申請一個小程序也是要資質審覈通過
2.申請一個微信開放平臺,將上訴兩個準備好的微信公衆號和微信小程序綁定在這個微信開放平臺上
綁定的公衆號和其測試號
綁定的小程序
3.微信開發文檔下載卡券開發資料並解壓,將需要的信息複製到我們的項目中。
將以下幾個類複製到項目中的合適位置
之後引入相應的包
// https://mvnrepository.com/artifact/org.json/json
compile group: 'org.json', name: 'json', version: '20180813'
接下來需要一個處理post請求的工具類
package com.community.utils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Author zhaomengxia
* @create 2019/10/18 16:50
*/
public class HttpRequestUtil {
public static String response(String url,String content) {
String line = "";
String message = "";
String returnData = "";
boolean postState = false;
BufferedReader bufferedReader = null;
try {
URL urlObject = new URL(url);
HttpURLConnection urlConn = (HttpURLConnection) urlObject.openConnection();
urlConn.setDoOutput(true);
/*設定禁用緩存*/
urlConn.setRequestProperty("Cache-Control", "no-cache");
/*維持長連接*/
urlConn.setRequestProperty("Connection", "Keep-Alive");
/*設置字符集*/
urlConn.setRequestProperty("Charset", "UTF-8");
/*設定輸出格式爲json*/
urlConn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
/*設置使用POST的方式發送*/
urlConn.setRequestMethod("POST");
/*設置不使用緩存*/
urlConn.setUseCaches(false);
/*設置容許輸出*/
urlConn.setDoOutput(true);
/*設置容許輸入*/
urlConn.setDoInput(true);
urlConn.connect();
OutputStreamWriter outStreamWriter = new OutputStreamWriter(urlConn.getOutputStream(),"UTF-8");
outStreamWriter.write(content);
outStreamWriter.flush();
outStreamWriter.close();
/*若post失敗*/
if((urlConn.getResponseCode() != 200)){
returnData = "{\"jsonStrStatus\":0,\"processResults\":[]}";
message = "發送POST失敗!"+ "code="+urlConn.getResponseCode() + "," + "失敗消息:"+ urlConn.getResponseMessage();
// 定義BufferedReader輸入流來讀取URL的響應
InputStream errorStream = urlConn.getErrorStream();
if(errorStream != null)
{
InputStreamReader inputStreamReader = new InputStreamReader(errorStream,"utf-8");
bufferedReader = new BufferedReader(inputStreamReader);
while ((line = bufferedReader.readLine()) != null) {
message += line;
}
inputStreamReader.close();
}
errorStream.close();
System.out.println("發送失敗!錯誤信息爲:"+message);
}else{
/*發送成功返回發送成功狀態*/
postState = true;
// 定義BufferedReader輸入流來讀取URL的響應
InputStream inputStream = urlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");
bufferedReader = new BufferedReader(inputStreamReader);
while ((line = bufferedReader.readLine()) != null) {
message += line;
}
returnData = message;
inputStream.close();
inputStreamReader.close();
System.out.println("發送POST成功!返回內容爲:" + returnData);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
return returnData;
}
}
}
其中:WxCardBaseInfo這個類是處理卡券(團購券,代金券,折扣券等)的通用字段爲指定的json格式
WxCard是整理卡券創建的整個json字符串(可以根據不同的卡券類型進行創建相應的卡券),WxCardGroupon類繼承了WxCard,就是創建團購券的。
下面這個運行出來的結果就是我們文檔中看到的創建團購券時的post數據格式實例
import java.util.ArrayList;
import java.util.Calendar;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author jackylian
*/
public class Main {
public static void main(String[] args) {
WxCardGroupon card = new WxCardGroupon();
WxCardBaseInfo baseInfo = card.getBaseInfo();
baseInfo.setLogoUrl("123");
baseInfo.setDateInfoTimeRange(Calendar.getInstance().getTime(), Calendar.getInstance().getTime());
baseInfo.setBrandName("brandname");
baseInfo.setBindOpenid(false);
baseInfo.setCanGiveFriend(false);
baseInfo.setCanShare(true);
baseInfo.setCodeType(WxCardBaseInfo.CODE_TYPE_QRCODE);
baseInfo.setColor("Color010");
baseInfo.setDescription("desc");
baseInfo.setGetLimit(3);
baseInfo.setUseCustomCode(false);
baseInfo.setNotice("notice");
baseInfo.setServicePhone("phone");
baseInfo.addLocationIdList(123123);
baseInfo.addLocationIdList(5345);
baseInfo.setUseLimit(5);
baseInfo.setQuantity(10000000);
System.out.println(baseInfo.toJsonString());
baseInfo.setLogoUrl("435345");
ArrayList<Integer> locationIdList = new ArrayList<Integer>();
locationIdList.add(809809);
locationIdList.add(423532);
card.setDealDetail("團購詳情啊啊啊啊啊!!!");
System.out.println(locationIdList.getClass().isArray());
baseInfo.setLocationIdList(locationIdList);
System.out.println(card.toJsonString());
}
}
再比如我要創建代金券,第一步創建一個類繼承WxCard並添加其專用的字段
package com.community.weixinsdk;
/**
* @Author zhaomengxia
* @create 2019/10/18 17:17
*/
public class WxCardCash extends WxCard {
//代金券
public WxCardCash() {
init("CASH");
}
//代金券專用,表示起用金額(單位爲分),如果無起用門檻則填0
public void setLeastCost(int leastCost) {
m_data.put("least_cost", leastCost);
}
//代金券專用,表示減免金額。(單位爲分)
public void setReduceCost(int reduceCost) {
m_data.put("reduce_cost", reduceCost);
}
}
接下來就可以處理業務層
/**
* 創建代金券
*
* @param accessToken
* @return
*/
public String createCash(String accessToken, CouponCashDTO couponCashDTO) {
String url ="https://api.weixin.qq.com/card/create?access_token=" + accessToken;
WxCardCash card = new WxCardCash();
WxCardBaseInfo baseInfo = card.getBaseInfo();
CardBaseInfoDTO cardBaseInfoDTO = couponCashDTO.getCardBaseInfoDTO();
cardCommon(baseInfo, cardBaseInfoDTO);
//起用金額
card.setLeastCost(couponCashDTO.getLeastCost());
//減少金額
card.setReduceCost(couponCashDTO.getReduceCost());
//處理post請求
String response = HttpRequestUtil.response(url, card.toJsonString());
return response;
}
4.按照微信開發文檔進行開發
5.微信測試號申請流程
6.微信服務器配置url,可以處理好 之後可以處理微信的各種推送消息,拿到我們需要拿到的數據。注意微信公衆號服務器配置會影響微信公衆號的自定義菜單。
package com.community.resource.sign;
import com.community.model.coupon.CouponUserView;
import com.community.repo.coupon.CouponUserRepository;
import com.community.utils.Identities;
import com.community.utils.SerializeXmlUtil;
import com.community.utils.SignUtil;
import com.community.wxmessage.ImageMessage;
import com.community.wxmessage.InputMessage;
import com.community.wxmessage.MsgType;
import com.community.wxmessage.OutputMessage;
import com.thoughtworks.xstream.XStream;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
/**
* @Author zhaomengxia
* @create 2019/10/23 9:03
*/
@RestController
@RequestMapping(value = "/wechat/")
@Api(tags = "微信公衆號服務器配置url")
public class CardSignResource {
@Autowired
private CouponUserRepository couponUserRepository;
@GetMapping(value = "wx")
@RequestMapping(value = "wx",method = {RequestMethod.GET, RequestMethod.POST})
@ApiOperation(value = "微信公衆號服務器配置")
@ResponseBody
public String cardSign(HttpServletRequest request, HttpServletResponse response){
String signature=request.getParameter("signature");
String timestamp=request.getParameter("timestamp");
String nonce=request.getParameter("nonce");
String echostr=request.getParameter("echostr");
System.out.println(signature);
boolean isGet=request.getMethod().toLowerCase().equals("get");
if (isGet) {
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
return echostr;
}
}else {
try {
acceptMessage(request, response);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 處理接收消息
ServletInputStream in = request.getInputStream();
// 將POST流轉換爲XStream對象
XStream xs = SerializeXmlUtil.createXstream();
xs.processAnnotations(InputMessage.class);
xs.processAnnotations(OutputMessage.class);
// 將指定節點下的xml節點數據映射爲對象
xs.alias("xml", InputMessage.class);
// 將流轉換爲字符串
StringBuilder xmlMsg = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
xmlMsg.append(new String(b, 0, n, "UTF-8"));
}
System.out.println(xmlMsg.toString()+"---------");
// 將xml內容轉換爲InputMessage對象
InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString());
String servername = inputMsg.getToUserName();// 服務端
String custermname = inputMsg.getFromUserName();// 客戶端
long createTime = inputMsg.getCreateTime();// 接收時間
Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回時間
// 取得消息類型
String msgType = inputMsg.getMsgType();
String cardId=inputMsg.getCardId();
String userCardCode=inputMsg.getUserCardCode();
String fromUserName=inputMsg.getFromUserName();
String event=inputMsg.getEvent();
String unionId=inputMsg.getUnionId();
//處理領取卡券事件,記錄領取卡券的用戶的openid,code,cardId
if (event.equals("user_get_card")){
CouponUserView couponUserView=new CouponUserView();
couponUserView.setCreateOn(System.currentTimeMillis());
couponUserView.setCouponUserId(Identities.COUPONUSER.identity());
couponUserView.setOpenid(fromUserName);
couponUserView.setUserCardCode(userCardCode);
couponUserView.setCardId(cardId);
couponUserView.setUnionId(unionId);
couponUserRepository.save(couponUserView);
}
// 根據消息類型獲取對應的消息內容
if (msgType.equals(MsgType.Text.toString())) {
// 文本消息
System.out.println("開發者微信號:" + inputMsg.getToUserName());
System.out.println("發送方帳號:" + fromUserName);
System.out.println("消息創建時間:" + inputMsg.getCreateTime() + new Date(createTime * 1000l));
System.out.println("消息內容:" + inputMsg.getContent());
System.out.println("消息Id:" + inputMsg.getMsgId());
StringBuffer str = new StringBuffer();
str.append("<xml>");
str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>");
str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>");
str.append("<CreateTime>" + returnTime + "</CreateTime>");
str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>");
str.append("<Content><![CDATA[你說的是:" + inputMsg.getContent() + ",嗎?]]></Content>");
str.append("</xml>");
System.out.println(str.toString());
response.getWriter().write(str.toString());
}
// 獲取並返回多圖片消息
if (msgType.equals(MsgType.Image.toString())) {
System.out.println("獲取多媒體信息");
System.out.println("多媒體文件id:" + inputMsg.getMediaId());
System.out.println("圖片鏈接:" + inputMsg.getPicUrl());
System.out.println("消息id,64位整型:" + inputMsg.getMsgId());
OutputMessage outputMsg = new OutputMessage();
outputMsg.setFromUserName(servername);
outputMsg.setToUserName(custermname);
outputMsg.setCreateTime(returnTime);
outputMsg.setMsgType(msgType);
ImageMessage images = new ImageMessage();
images.setMediaId(inputMsg.getMediaId());
outputMsg.setImage(images);
System.out.println("xml轉換:/n" + xs.toXML(outputMsg));
response.getWriter().write(xs.toXML(outputMsg));
}
}
}
工具類
package com.community.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @Author zhaomengxia
* @create 2019/10/24 17:06
*/
public class SignUtil {
// 與接口配置信息中的Token要一致
private static String token = "zhaomengxia";
/**
* 驗證簽名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp,
String nonce) {
String[] arr = new String[]{token, timestamp, nonce};
// 將token、timestamp、nonce三個參數進行字典序排序
//Arrays.sort(arr);
sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 將三個參數字符串拼接成一個字符串進行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 將sha1加密後的字符串可與signature對比,標識該請求來源於微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 將字節數組轉換爲十六進制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 將字節轉換爲十六進制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
'B', 'C', 'D', 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
public static void sort(String a[]) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j].compareTo(a[i]) < 0) {
String temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
}
7.微信卡券 分爲兩個模式,自制和代制
8.微信公衆號測試號配置成功之後,在領取卡券的時候如果報該卡券未審覈則可以關注測試號並拿到微信號,將微信號作爲openid調用測試白名單接口既可以進行領取卡券。注意如果測試號沒有跟上訴公衆號和小程序一起綁定在同一個微信開放平臺,會遇到同一個用戶的unionid不一致的情況(小程序拿到的unioid和通過微信服務配置的url拿到的微信的推送信息中的不一致),解決辦法就是在微信開放平臺-綁定公衆號那裏選擇綁定測試號進行綁定該測試號即可解決問題,實現同一個用戶拿到的unioid一致,就可以通過unioid來識別同一用戶。
這裏選擇綁定微信測試號
微信卡券有特別的接口.
第一個即上傳圖片素材。即post請求方式,用form表單方式上傳。
另一個 ,如果採用第三方代制模式,需要上傳授權函,這裏就需要用到素材管理中的新增臨時素材
https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html
請求方式是同樣的。
調用以下工具類即可解決問題 FormUploadUtil.java
package com.community.utils;
import javax.activation.MimetypesFileTypeMap;
import javax.imageio.ImageIO;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
/**
* @Author zhaomengxia
* @create 2019/10/29 11:12
*/
public class FormUploadUtil {
public static String realPath(String filepath,String path) throws IOException {
//現將圖片上傳到服務器指定文件夾,這裏是Windows服務器,linux的話也同樣指定一個文件存放即可
String realPath = "D:\\upload\\image\\";
URL url = new URL(filepath);
// 圖片不是原來的了。
ImageIO.write(ImageIO.read(url), "jpg", new File("image-01.jpg"));
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
input = new BufferedInputStream(url.openStream());
output = new BufferedOutputStream(new FileOutputStream(realPath + path));
for (int d = input.read(); d != -1; d = input.read()) {
output.write(d);
}
} catch (Exception e) {
} finally {
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
return realPath;
}
/**
* 上傳圖片
*
* @param urlStr
* @param fileMap
* @return
*/
public static String formUpload(String urlStr,
Map fileMap) {
String res = "";
HttpURLConnection conn = null;
String BOUNDARY = "---------------------------" + System.currentTimeMillis(); //boundary就是request頭和上傳文件內容的分隔符
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
OutputStream out = new DataOutputStream(conn.getOutputStream());
// file
if (fileMap != null) {
Iterator iter = fileMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
File file = new File(inputValue);
String filename = file.getName();
String contentType = new MimetypesFileTypeMap()
.getContentType(file);
String path = file.getPath();
if (filename.endsWith(".png")) {
contentType = "image/png";
}
if (contentType == null || contentType.equals("")) {
contentType = "application/octet-stream";
}
StringBuffer strBuf = new StringBuffer();
strBuf.append("--").append(BOUNDARY).append("\r\n");
strBuf.append("Content-Disposition: form-data; name=" + inputName + "\"; filename=\"" + filename + "\"\r\n");
strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
out.write(strBuf.toString().getBytes());
if (!file.exists()){
file.mkdirs();
file.createNewFile();
}
DataInputStream in = new DataInputStream(
new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
}
}
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(endData);
out.flush();
out.close();
// 讀取返回數據
StringBuffer strBuf = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
strBuf.append(line).append("\n");
}
res = strBuf.toString();
reader.close();
reader = null;
} catch (Exception e) {
System.out.println("發送POST請求出錯。" + urlStr);
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return res;
}
}
具體實現這裏是先調用本地上傳的接口,這裏是先將圖片上傳到阿里雲服務器拿到返回的圖片鏈接filepath
/**
* 上傳臨時多媒體素材
*
* @param accessToken
* @param type
* @param filepath
* @return
*/
public String uploadMedia(String accessToken, String type, String filepath) throws IOException {
String url1 = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=" + accessToken + "&type=" + type;
String path = "image-03.jpg";
String realPath = FormUploadUtil.realPath(filepath, path);
Map map = new HashMap();
// map.put("type",type);
map.put("media", realPath + path);
String res = FormUploadUtil.formUpload(url1, map);
return res;
}
這裏的filepath同上
/**
* 上傳圖片素材
*
* @param accessToken
* @param filepath
* @return
* @throws IOException
*/
public LogoUrlDTO upload(String accessToken, String filepath) throws IOException {
String urlStr = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=" + accessToken;
Map fileMap = new HashMap();
String path = "image-02.jpg";
String realPath = FormUploadUtil.realPath(filepath, path);
fileMap.put("buffer", realPath + path);
String ret = FormUploadUtil.formUpload(urlStr, fileMap);
LogoUrlDTO logoUrlDTO = JSON.parseObject(ret, LogoUrlDTO.class);
return logoUrlDTO;
}