全文目录,一步到位
- 1.前言简介
- 1.1 专栏传送门
- 2. 微信小程序公用功能
- 2.1 配置准备工作
- 2.1.1 配置文件准备(单体放`yml`中 微服务放`配置中心`)
- 2.1.2 获取配置文件中的小程序配置
- 2.1.3 设置redis配置
- 2.2 创建不同功能工具类
- 2.2.1 创建微信服务工具类`WechatServiceUtils`
- 2.2.2 创建RedisCache
- 2.3 各个功能传送门
- 2.3.1 获取accessToken
- 2.3.2 获取手机号
- 2.3.3 获取小程序二维码(不限制)
- 2.3.4 获取openId与unionId
- 3. 文章的总结
- 3.1 本文总结
- 3.2 本文统一说明
1.前言简介
本文是微信小程序
公共类
以下功能均使用 此文章 代码
- 获取
accessToken
手机号
小程序二维码
openId与UnionId
最下面有传送门, 传送到每个功能 避免多次封装
远程调用均使用restTemplate (springboot自带, 操作简单)
使用其他请随意…
1.1 专栏传送门
===> 微信小程序相关操作专栏 <===
2. 微信小程序公用功能
2.1 配置准备工作
2.1.1 配置文件准备(单体放yml
中 微服务放配置中心
)
(
特别注意
这里面包含大部分微信小程序配置)
appid
和secret
填写上
ps: 里面部分配置可以删除
- 过期时间
- 启动执行
- 登录url
- 其他业务 如获取手机号, 获取小程序二维码等
# 微信核心参数
wechat:# 小程序mini-app:# 请求urlrequestUrl: /wxMiniLogin# 请求方法requestMethod: POST# appidappId: ?# app秘钥appSecret: ?# 微信基础请求网址wxBaseRequestUrl: https://api.weixin.qq.com# 获取access_token必备参数grantType: client_credential# 微信获取access_token的urlaTokenUrl: ${wechat.mini-app.wxBaseRequestUrl}/cgi-bin/token?grant_type=${wechat.mini-app.grantType}&appid=%s&secret=%s# 要分钟(尽量少于120分钟) 一天上限2000次 这里一天最多请求24次 可增加定时刷新缓存等expiredTime: 70# 是否执行服务启动执行serverStartAutoRun: false# 微信登录提供的接口(GET)wxLoginUrlTemplate: ${wechat.mini-app.wxBaseRequestUrl}/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code# 微信获取手机号接口wxGetPhoneUrl: ${wechat.mini-app.wxBaseRequestUrl}/wxa/business/getuserphonenumber?access_token=%s#微信获取二维码(不限制)post请求wxACodeUnLimitUrl: ${wechat.mini-app.wxBaseRequestUrl}/wxa/getwxacodeunlimit?access_token=%s#微信获取二维码(限制)post请求wxACodeUrl: ${wechat.mini-app.wxBaseRequestUrl}/wxa/getwxacode?access_token=%s# 微信获取二维码(限制)post请求wxAQrcodeUrl: ${wechat.mini-app.wxBaseRequestUrl}/cgi-bin/wxaapp/createwxaqrcode?access_token=%s
2.1.2 获取配置文件中的小程序配置
使用@ConfigurationProperties注解对应
- 具体介绍我没有写: 找了一篇写的比较全的链接
- => 传送门: @ConfigurationProperties使用方式
- 当然 可以使用
@Value
替换上面这种(二选一
)代码如下, 名字对应配置文件名称, 注释自己加一下吧
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 小程序获取配置类** @author pzy* @version 0.1.0* @description TODO*/
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "wechat.mini-app")
public class WechatConfigProperties {private String requestUrl;private String requestMethod;private String appId;private String appSecret;private String wxBaseRequestUrl;private String grantType;private String aTokenUrl;private Integer expiredTime;private Boolean serverStartAutoRun;private String wxLoginUrlTemplate;/*** 获取手机号*/private String wxGetPhoneUrl;/*** 二维码无限制url(主)*/private String wxACodeUnLimitUrl;private String wxACodeUrl;private String wxAQrcodeUrl;/*** 生成微信登录请求地址** @param code code* @return 请求地址*/public String getWxLoginUrl(String code) {return String.format(wxLoginUrlTemplate, appId, appSecret, code);}/*** 生成微信ACCESS_TOKEN请求地址* <p>* 模板样式: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s** @return 请求地址*/public String getATokenUrl() {return String.format(aTokenUrl, appId, appSecret);}/*** 获取手机号url post请求** @param accessToken* @return*/public String getPhoneUrl(String accessToken) {return String.format(wxGetPhoneUrl, accessToken);}/*** 获取不限制的微信二维码url** @param accessToken* @return*/public String getWxACodeUnLimitUrl(String accessToken) {return String.format(wxACodeUnLimitUrl, accessToken);}/*** 获取(限制一)的微信二维码url** @param accessToken* @return*/public String getWxACodeUrl(String accessToken) {return String.format(wxACodeUrl, accessToken);}/*** 获取(限制二)的微信二维码url** @param accessToken* @return*/public String getWxAQrcodeUrl(String accessToken) {return String.format(wxAQrcodeUrl, accessToken);}
}
2.1.3 设置redis配置
spring: redis:# 地址host: 192.168.1.130# 端口,默认为6379port: 6379# 数据库索引database: 0# 密码password: 123456# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 5# 连接池中的最大空闲连接max-idle: 10# 连接池的最大数据库连接数max-active: 50# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: 5000ms
2.2 创建不同功能工具类
2.2.1 创建微信服务工具类WechatServiceUtils
RestTemplate 直接可以
使用
WechatConfigProperties 请见2.1.2
RedisCache 请见2.2.2
/*** 微信服务工具类** @author pzy* @version 0.1.0* @description: TODO*/
@Slf4j
@RequiredArgsConstructor
@Component
public class WechatServiceUtils {/*** 远程调用*/private final RestTemplate restTemplate;/*** redis缓存*/private final RedisCache redisCache;/*** 微信统一配置*/private final WechatConfigProperties wechatConfigProperties;/*** 1. 获取微信登录认证信息** @param wxCommonReqDto* @return*/public Map<String, String> getWxMiniAuth(WxCommonReqDto wxCommonReqDto) {String code = wxCommonReqDto.getWxCode();//秘钥String encryptedIv = wxCommonReqDto.getIv();//加密数据String encryptedData = wxCommonReqDto.getEncryptedData();JSONObject wxAuthResponse = null;String sessionKey = "";String openid = "";try {//1. 想微信服务器发送请求获取用户信息String url = wechatConfigProperties.getWxLoginUrl(code);log.info("===> 请求微信url是: {}", url);//2. 远程调用微信接口String res = restTemplate.getForObject(url, String.class);wxAuthResponse = JSONObject.parseObject(res);//3. 解析返回参数 报错则进行对应处理/*校验1: wx请求不是null*/if (wxAuthResponse != null) {/*校验2: 响应对象是否正确*/CheckUtils.responseCheck(wxAuthResponse);//3.1 获取session_key和openidsessionKey = wxAuthResponse.getString("session_key");openid = wxAuthResponse.getString("openid");log.info("===> openid: {}", openid);/*校验3: 响应信息是否正常*/if (StringUtils.isBlank(sessionKey) || StringUtils.isBlank(openid)) {log.error("小程序授权失败,session_key或open_id是空!");throw new ServiceException("抱歉, 小程序授权失败,缺少关键返回参数!");}log.info("===> 微信回调信息: {}", wxAuthResponse);}} catch (Exception e) {e.printStackTrace();throw new ServiceException("抱歉, 小程序授权失败!");}//4. 获取微信信息并制作token/*校验:(选用)如果获取union_id 需要进行解密*/Map<String, String> map = new HashMap<>();String token = "";if (StringUtils.isNotBlank(encryptedIv) && StringUtils.isNotBlank(encryptedData)) {String decryptResult = "";try {//如果没有绑定微信开放平台,解析结果是没有unionid的。decryptResult = AESUtils.decrypt(sessionKey, encryptedIv, encryptedData);if (StringUtils.hasText(decryptResult)) {//如果解析成功,获取tokenmap.put("type", String.valueOf(1));map.put("decryptResult", decryptResult);}} catch (Exception e) {e.printStackTrace();throw new ServiceException("微信登录失败!");}} else {//如果前端只传wxCode 没有unionId的需求 只要openId的map.put("type", String.valueOf(2));map.put("decryptResult", openid);}/*校验: 数据为空的情况*/if (StringUtils.isBlank(map.get("type")) || StringUtils.isBlank(map.get("decryptResult"))) {throw new ServiceException(ResponseEnum.A10007);}return map;}/*** 2. 获取缓存中的AccessToken* <p>* 没有从微信拉取[可配合定时]** @return accessToken*/public String getRedisCacheAccessToken() {/*校验: 缓存中有accessToken的key*/if (redisCache.hasKey(CacheConstants.WX_ACCESS_TOKEN)) {log.info("二级缓存数据取出accessToken成功!");return redisCache.getCacheObject(CacheConstants.WX_ACCESS_TOKEN);}//这里不用三目(不好看~~)return getWxMiniAccessToken();}/*** 3. 访问微信官方获取两小时的 accessToken** @return accessToken*/public String getWxMiniAccessToken() {Map<String, String> query = new HashMap<>();query.put("grant_type", wechatConfigProperties.getGrantType());//client_credentialquery.put("secret", wechatConfigProperties.getAppSecret());query.put("appid", wechatConfigProperties.getAppId());try {String aTokenUrl = wechatConfigProperties.getATokenUrl();
// ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(aTokenUrl, query, JSONObject.class);ResponseEntity<JSONObject> responseEntity = restTemplate.getForEntity(aTokenUrl, JSONObject.class, query);HttpStatus statusCode = responseEntity.getStatusCode(); //状态码// System.out.println(responseEntity.getHeaders());//获取到头信息/*校验: 如果接口成功 200*/if (Objects.equals(statusCode.value(), 200)) {JSONObject responseJsonBody = responseEntity.getBody();//响应体log.info("[请求微信小程序官方接口] => 获取accessToken请求成功返回值:{}", responseJsonBody);if (responseJsonBody == null) {log.info("微信小程序获取accessToken请求返回result是null!");throw new ServiceException(ResponseEnum.A10005);}//获取accessTokenString accessToken = responseJsonBody.getString("access_token");if (StringUtils.isBlank(accessToken)) {log.info("微信小程序获取accessToken请求返回access_token是null!");throw new ServiceException(ResponseEnum.A10005);}//放入缓存中redisCache.setCacheObject(CacheConstants.WX_ACCESS_TOKEN, accessToken, wechatConfigProperties.getExpiredTime(), TimeUnit.MINUTES);return accessToken;} else {log.error("微信HttpStatus的StatusCode不是200 {}", statusCode.value());throw new ServiceException(ResponseEnum.A10005);}} catch (Exception e) {e.printStackTrace();log.info("微信小程序获取accessToken请求异常信息 {}", e.getMessage());throw new ServiceException(ResponseEnum.A10005);}}/*** 错误码 错误描述 解决方案* -1 system error [系统繁忙,此时请开发者稍候再试]* 40029 code 无效 [js_code无效]* 45011 api minute-quota reach limit mustslower retry [next minute API 调用太频繁,请稍候再试]* 40013 invalid appid [请求appid身份与获取code的小程序appid不匹配]* 错误码** @param code js_code* @return phone*/public String getPhoneByCode(String code) {String phoneUrl = wechatConfigProperties.getPhoneUrl(getRedisCacheAccessToken());Map<String, Object> map = new HashMap<>();map.put("code", code);JSONObject jsonObject = sendPostRestTemplate(phoneUrl, map, JSONObject.class);System.out.println(jsonObject);if (jsonObject.containsKey("errcode")) {/*如果异常码是0 说明正常*/if (!Objects.equals(String.valueOf(jsonObject.get("errcode")), "0")) {log.error("===> 获取手机号的异常信息 : {}", jsonObject + "");throw new ServiceException("获取失败: " + jsonObject.get("errmsg"), (Integer) jsonObject.get("errcode"));}}JSONObject phoneInfo = jsonObject.getJSONObject("phone_info");return phoneInfo.getString("phoneNumber");}/*** 生成小程序带参数二维码* https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html#HTTPS%20%E8%B0%83%E7%94%A8*/@SneakyThrowspublic InputStream getUnlimitedWxQrCode(WxCodeUnlimitedReqDTO wxCodeUnlimitedReqDTO, String accessToken) {Map<String, Object> params = new HashMap<>();params.put("scene", wxCodeUnlimitedReqDTO.getScene());params.put("page", wxCodeUnlimitedReqDTO.getPage());params.put("path", wxCodeUnlimitedReqDTO.getPage());params.put("env_version", wxCodeUnlimitedReqDTO.getEnvVersion());params.put("width", wxCodeUnlimitedReqDTO.getWidth());params.put("auto_color", wxCodeUnlimitedReqDTO.getAutoColor());//自动配置线条颜色ResponseEntity<byte[]> response = restTemplate.postForEntity(wechatConfigProperties.getWxACodeUnLimitUrl(accessToken), JSON.toJSONString(params), byte[].class);System.out.println(JSON.toJSONString(params));byte[] buffer = response.getBody();
// System.out.println(Base64.getEncoder().encodeToString(buffer));assert buffer != null;return new ByteArrayInputStream(buffer);}/*** 远程调用 restTemplate方法 post请求** @param url* @param body* @return*/public <T> T sendPostRestTemplate(String url, Map<String, Object> body, Class<T> responseType) {return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(body, null), responseType).getBody();}
}
2.2.2 创建RedisCache
redis的
增删改查
操作 记得配置序列化与反序列化
文章传送门: ===> redis高级(序列化与反序列化) <===
@Component
public class RedisCache {@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout) {return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));}/*** 获取有效时间** @param key Redis键* @return 有效时间*/public long getExpire(final String key) {if (StringUtils.isBlank(key)) {throw new IllegalArgumentException("key cannot be null");}return redisTemplate.getExpire(key);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key) {ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key) {return Boolean.TRUE.equals(redisTemplate.delete(key));}/*** 删除集合对象** @param collection 多个对象* @return*/public boolean deleteObject(final Collection collection) {return redisTemplate.delete(collection) > 0;}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key) {return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()) {setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key) {return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key) {return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value) {redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey) {HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 删除Hash中的某条数据** @param key Redis键* @param hKey Hash键* @return 是否成功*/public boolean deleteCacheMapValue(final String key, final String hKey) {return redisTemplate.opsForHash().delete(key, hKey) > 0;}/*** 获得缓存的基本对象列表(全部的key)** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern) {return redisTemplate.keys(pattern);}
}
2.3 各个功能传送门
2.3.1 获取accessToken
2.3.2 获取手机号
2.3.3 获取小程序二维码(不限制)
2.3.4 获取openId与unionId
3. 文章的总结
3.1 本文总结
本文使用的技术栈
springboot
相关操作restTemplate
远程调用使用方式- redisTemplate
操作redis
的操作- redis的
序列化与反序列化
3.2 本文统一说明
本篇涵盖大部分的微信小程序操作(无支付), 统一封装
在2.3
中统一传送到具体功能配置
避免多次配置,传送门内的功能细节不在这篇介绍
特别注意
: 文章传送门会在近期完善
, 这是本专栏的第一篇
, 之后也会围绕此篇进行更新, 接入支付等等
作者: pingzhuyan