微信小程序越來越普遍,本章將分享給大家,實現微信小程序授權,推送微信通知消息。具體內容可參考微信小程序開發文檔服務端(https://developers.weixin.qq.com/miniprogram/dev/api-backend/)。
==========================================================
封裝工具類
==========================================================
package com.sf.vsolution.hb.sfce.util.wechat.appletService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sf.vsolution.hb.sfce.util.wechat.appletService.param.*;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @description: 微信小程序服務端開發
* @author: zhucj
* @date: 2019-11-14 15:10
*/
@Slf4j
public class AppletServiceUtil {
@Value("${applet.appId}")
private String appId;
@Value("${applet.secret}")
private String secret;
@Value("${wx.template.id}")
private String templateId;
@Value("${wx.template.page}")
private String templatePage;
@Autowired
private StringRedisTemplate redis;
@ApiOperation(value = "微信小程序-獲取用戶openId或手機號信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "code",value = "微信授權code",required = true,dataType = "String"),
@ApiImplicitParam(name = "encryptedData",value = "完整用戶信息的加密數據(不傳只獲取openId)",required = false,dataType = "String"),
@ApiImplicitParam(name = "iv",value = "加密算法的初始向量(如獲取更多用戶信息傳)",required = false,dataType = "String")
})
public R getUserInfo(WeChatAuthorizeDto dto){
if (StringUtils.isEmpty(dto.getCode())){
throw new AppletServiceException(AppletServiceConstants.PARAM_ERROR_CODE,AppletServiceConstants.CODE_NULL_ERROR);
}
//封裝請求參數map
Map map = new HashMap();
map.put("appid",appId);
map.put("secret",secret);
map.put("js_code",dto.getCode());
map.put("grant_type","authorization_code");
String resultString = HttpClientUtil.doGet(AppletServiceConstants.AUTH_CODE_API, map);
AuthCodeResponse authCodeResponse = JSON.parseObject(resultString, AuthCodeResponse.class);
//判斷請求返回結果,errcode = 0 請求成功
if (!Objects.equals(authCodeResponse.getErrcode(),AppletServiceConstants.SUCCESS_CODE)){
log.error("小程序授權失敗返回錯誤碼:{},錯誤信息:{}",authCodeResponse.getErrcode(),authCodeResponse.getErrmsg());
throw new AppletServiceException(AppletServiceConstants.PARAM_ERROR_CODE,AppletServiceConstants.AUTHORIZATION_ERROR);
}
HashMap<String,String> respMap = new HashMap<>();
respMap.put("openId",authCodeResponse.getOpenid());
//如果未傳encryptedData和iv 即僅返回openId
if (Objects.isNull(dto.getEncryptedData()) || Objects.isNull(dto.getIv())){
return R.ok(respMap);
}
// 解密
byte[] encrypData = Base64Utils.decodeFromString(dto.getEncryptedData());
byte[] ivData = Base64Utils.decodeFromString(dto.getIv());
byte[] sessionKey = Base64Utils.decodeFromString(authCodeResponse.getSession_key());
try{
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(sessionKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
String string = new String(cipher.doFinal(encrypData), "UTF-8");
JSONObject object = JSONObject.parseObject(string);
//獲取手機號碼
String phone = object.getString("phoneNumber");
respMap.put("phone", phone);
}catch (Exception e){
e.getStackTrace();
log.error("微信小程序手機號碼解密異常:{}",e.getMessage());
throw new AppletServiceException(AppletServiceConstants.EXCEPTION_ERROR_CODE,AppletServiceConstants.ENCTYP_EXECPTION);
}
return R.ok(resultString);
}
@ApiOperation(value = "獲取-小程序accessToken值")
public String getToken() throws AppletServiceException {
//先從緩存中判斷是否存在
String redisString = redis.opsForValue().get(AppletServiceConstants.ACCESS_TOKEN);
if (Objects.nonNull(redisString)){
return redisString;
}
String reqUrl = AppletServiceConstants.ACCESS_TOKEN_API;
Map map = new HashMap();
map.put("grant_type","client_credential");
map.put("appid",appId);
map.put("secret",secret);
String result = HttpClientUtil.doGet(reqUrl, map);
AuthAccessTokenResponse auth = JSON.parseObject(result, AuthAccessTokenResponse.class);
//判斷errcode ==0 即請求成功
if (Objects.equals(auth.getErrcode(),AppletServiceConstants.SUCCESS_CODE)){
log.info("獲取的accessToken值:{}",auth.getAccess_token());
//將accessToken存入到redis 設置過期時間
redis.opsForValue().set(AppletServiceConstants.ACCESS_TOKEN,auth.getAccess_token(),AppletServiceConstants.EXPIRES_IN, TimeUnit.SECONDS);
return auth.getAccess_token();
}else {
log.error("小程序授權失敗返回錯誤碼:{},錯誤信息:{}",auth.getErrcode(),auth.getErrmsg());
throw new AppletServiceException(AppletServiceConstants.PARAM_ERROR_CODE,AppletServiceConstants.ACCESS_TOKRN_ERROR);
}
}
@ApiOperation(value = "推送模板微信模板信息")
@ApiImplicitParams({
@ApiImplicitParam(value = "openId",name = "用戶openId",required = true,dataType = "String")
})
public Boolean sendTemplateMsg(String openId) throws AppletServiceException {
WxTemplate wxTemplate = build(openId);
String str = JSON.toJSONString(wxTemplate);
log.info("微信推送模版消息:{}",str);
String result = HttpClientUtil.doPostJson(AppletServiceConstants.TEMPLATE_MESSAGE_URL+getToken(), str);
CommonResponse commonResponse = JSON.parseObject(result, CommonResponse.class);
if (Objects.equals(commonResponse.getErrcode(),AppletServiceConstants.SUCCESS_CODE)) {
return true;
} else {
return false;
}
}
/**
* 構建消息模板
* @return
*/
private WxTemplate build(String openId) {
//以下是構建消息推送的model,具體情況需要根據模版做更改
WxTemplate template = new WxTemplate();
//模板Id
template.setTemplate_id(templateId);
//openId
template.setTouser(openId);
//此處小程序跳轉鏈接,需要根據需要跳轉拼接
template.setPage(templatePage);
Map<String, TemplateData> param = new HashMap<>();
// 封裝模板數據,依據小程序模板來設置(可設置成參數,傳遞過來)
param.put("first", getTemplateData(xxxx));
param.put("keyword1", getTemplateData("xxxxxx"));
param.put("keyword2", getTemplateData("xxxx"));
param.put("keyword3", getTemplateData("xxxx"));
param.put("keyword4", getTemplateData("xxxx"));
param.put("keyword5", getTemplateData("xxxx"));
param.put("remark", getTemplateData("xxxx"));
template.setData(param);
return template;
}
/**
* 設置 TemplateDate數據
* @param value
* @return
*/
private static TemplateData getTemplateData(String value) {
TemplateData templateData = new TemplateData();
templateData.setValue(value);
templateData.setColor("#173177");
return templateData;
}
}
==================================================================
封裝請求參數,響應參數,常量類,枚舉類和異常類
==================================================================
package com.sf.vsolution.hb.sfce.util.wechat.appletService;
/**
* @Author: zhucj
* @Date: 2019/5/23 13:29
* @apiNote: 微信小程序服務端常量
*/
public class AppletServiceConstants {
//TODO:服務端Api
/**
* 小程序登錄API
*/
public static final String AUTH_CODE_API = "https://api.weixin.qq.com/sns/jscode2session";
/**
* accessToken授權API
*/
public static final String ACCESS_TOKEN_API = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 模板消息URL
*/
public static String TEMPLATE_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=";
/**
* accessToken緩存key
*/
public static final String ACCESS_TOKEN = "ACCESS_TOKEN";
/**
* accessToken緩存過期時間
*/
public static final Integer EXPIRES_IN = 7000;
//TODO:請求狀態碼
public static final Integer PARAM_ERROR_CODE = 400;
public static final Integer EXCEPTION_ERROR_CODE = 500;
public static final Integer SUCCESS_CODE = 0;
//TODO: 異常信息狀態
public static final String CODE_NULL_ERROR = "微信授權碼code不為空";
public static final String AUTHORIZATION_ERROR = "未獲取到openId,授權失敗";
public static final String ENCTYP_EXECPTION = "微信小程序手機號碼解密異常";
public static final String ACCESS_TOKRN_ERROR = "小程序授權獲取accessToken失敗";
}
----------------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService;
/**
* @author :zhucj
* @date :Created in 2019/6/17 17:46
* @description: 小程序服務異常類
*/
public class AppletServiceException extends RuntimeException {
private static final long serialVersionUID = -5317007026578376164L;
/**
* 錯誤碼
*/
private Integer errorCode;
/**
* 錯誤描述
*/
private String errorMsg;
/**
* @param errorCode
* @param errorMsg
*/
public AppletServiceException(Integer errorCode, String errorMsg) {
super(errorMsg);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public Integer getErrorCode() {
return errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
}
----------------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
/**
* @description: get/post請求工具類
* @author: zhucj
* @date: 2019-04-26 10:10
*/
@Slf4j
public class HttpClientUtil {
/**
* 發送get請求
* @param url
* @param param
* @return
*/
public static String doGet(String url, Map<String, String> param) {
log.info("GET請求地址:{},請求參數內容:{}",url, JSON.toJSON(param));
// 創建Httpclient對象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 創建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 創建http GET請求
HttpGet httpGet = new HttpGet(uri);
// 執行請求
response = httpclient.execute(httpGet);
// 判斷返回狀態是否為200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
log.error("網絡出現異常:{}",e.getMessage());
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
log.error("IO出現異常:{}",e.getMessage());
}
}
log.info("GET響應參數:{}",resultString);
return resultString;
}
/**
* 發post請求 傳入xml/json字符串
* @param url
* @param json
* @return
*/
public static String doPostJson(String url, String json) {
log.info("POST請求地址:{},請求參數內容:{}",url,json);
// 創建Httpclient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 創建Http Post請求
HttpPost httpPost = new HttpPost(url);
// 創建請求內容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 執行http請求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
log.error("網絡出現異常:{}",e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
log.error("IO出現異常:{}",e.getMessage());
}
}
log.info("POST響應參數:{}",resultString);
return resultString;
}
}
-------------------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.ToString;
import java.io.Serializable;
/**
* 返回類型
* @author choleece
* @date 2018/9/27
*/
@ApiModel
@ToString
public class R<T> implements Serializable {
private static final long serialVersionUID = -6287952131441663819L;
/**
* 編碼
*/
@ApiModelProperty(value = "響應碼", example = "200")
private int code = 200;
/**
* 成功標志
*/
@ApiModelProperty(value = "成功標志", example = "true")
private Boolean success;
/**
* 返回消息
*/
@ApiModelProperty(value = "返回消息說明", example = "操作成功")
private String msg="操作成功";
/**
* 返回數據
*/
@ApiModelProperty(value = "返回數據")
private T data;
/**
* 創建實例
* @return
*/
public static R instance() {
return new R();
}
public int getCode() {
return code;
}
public R setCode(int code) {
this.code = code;
return this;
}
public Boolean getSuccess() {
return success;
}
public R setSuccess(Boolean success) {
this.success = success;
return this;
}
public String getMsg() {
return msg;
}
public R setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return data;
}
public R setData(T data) {
this.data = data;
return this;
}
public static R ok() {
return R.instance().setSuccess(true);
}
public static R ok(Object data) {
return ok().setData(data);
}
public static R ok(Object data, String msg) {
return ok(data).setMsg(msg);
}
public static R error() {
return R.instance().setSuccess(false);
}
public static R error(String msg) {
return error().setMsg(msg);
}
/**
* 無參
*/
public R() {
}
public R(int code, String msg) {
this.code = code;
this.msg = msg;
}
public R(int code, T data){
this.code = code;
this.data = data;
}
/**
* 有全參
* @param code
* @param msg
* @param data
* @param success
*/
public R(int code, String msg, T data, Boolean success) {
this.code = code;
this.msg = msg;
this.data = data;
this.success = success;
}
/**
* 有參
* @param code
* @param msg
* @param data
*/
public R(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
---------------------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
/**
* @description: 小程序端返回共用參數
* @author: zhucj
* @date: 2019-11-14 16:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CommonResponse {
/**
* 錯誤碼
*/
private Integer errcode;
/**
* 錯誤信息
*/
private String errmsg;
}
-----------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
/**
* @description: 小程序獲取AccessToken返回參數
* @author: zhucj
* @date: 2019-11-14 16:43
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class AuthAccessTokenResponse extends CommonResponse {
/**
* 獲取到的憑證
*/
private String access_token;
/**
* 憑證有效時間,單位:秒。目前是7200秒之內的值。
*/
private Integer expires_in;
}
---------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
/**
* @description: 小程序獲取openId返回參數
* @author: zhucj
* @date: 2019-11-14 16:13
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class AuthCodeResponse extends CommonResponse {
/**
* 用戶唯一標識
*/
private String openid;
/**
* 會話密鑰
*/
private String session_key;
/**
* 用戶在開放平台的唯一標識符,在滿足 UnionID 下發條件的情況下會返回,詳見 UnionID 機制說明。
*/
private String unionid;
}
------------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
/**
* Template
* @author zhucj
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class TemplateData {
/**
* 模板顯示值
*/
private String value;
/**
* 模板顯示顏色
*/
private String color;
}
---------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
/**
* @description: 微信授權參數
* @author: zhucj
* @date: 2019-09-26 15:01
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class WeChatAuthorizeDto {
/**
* 微信授權code
*/
private String code;
/**
* 包括敏感數據在內的完整用戶信息的加密數據
*/
private String encryptedData;
/**
* 加密算法的初始向量,詳見 用戶數據的簽名驗證和加解密
*/
private String iv;
}
-----------------------------
package com.sf.vsolution.hb.sfce.util.wechat.appletService.param;
import lombok.*;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class WxTemplate {
/**
* 微信通知模板ID
*/
private String template_id;
/**
* 微信openId
*/
private String touser;
/** 小程序首頁地址 */
private String page;
/**支付返回的 prepay_id,部分模板必傳*/
private String form_id;
private Map<String, String> miniprogram;
private Map<String, TemplateData> data;
}
=================================================================
yml配置文件
=================================================================
#微信支付參數
wx:
#微信小程序appId
appId: ******
#小程序secret
secret: *******
template:
#模板Id
id: ******
#通知跳轉小程序頁面
page: ******
=================================================================
項目maven支持
===============================================
<!-- swagger API -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.0</version>
</dependency>
<!--htttpclient依賴-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<!--fastjson依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>