首页 > 技术文章 > JAVA项目实战-微信小程序服务端开发(一)

zhucj-java 2019-11-15 09:22 原文

微信小程序越来越普遍,本章将分享给大家,实现微信小程序授权,推送微信通知消息。具体内容可参考微信小程序开发文档服务端(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>
 
 

推荐阅读