微信小程序后端开发流程
微信小程序后端开发流程根据官网总结为两个步骤
1、前端调用 wx.login 返回了code,然后调用wx.getUserInfo获取到用户的昵称 头像 2、服务端根据code去微信获取openid, 接口地址:developers.weixin.qq.com/miniprogram…
微信小程序后端接口开发
controller层
public class OauthController {
@Autowired
private WeChatService weChatService;
/**
* 微信授权用js_code换取openId
* @param code
* @return
*/
@GetMapping("/code2Session")
public BaseResponse code2Session(String code) {
log.info("code2Session,code={}", code);
if (StringUtil.isEmpty(code)) {
return BaseResponse.buildFail("参数异常");
}
Code2SessionResponse res = weChatService.code2Session(code);
log.info("code2Session,res={}", res);
if (!res.isSuccess()) {
return BaseResponse.buildFail(res.getErrCode(), res.getErrMsg());
}
return BaseResponse.buildSuccess(res);
}
/**
* 解密获取手机号
* @param request
* @param response
* @param param
* @return
*/
public BaseResponse decryptGetPhone(HttpServletRequest request, HttpServletResponse response,
@RequestBody OauthParam param) {
if (!StringUtil.isEmpty(param.getOpenId())) {//微信授权登录
String sessionKey = weChatService.getSessionKey(param.getOpenId());
if (StringUtil.isEmpty(sessionKey)) {
return BaseResponse.buildFail("会话不存在");
}
Sha1Utils sha = new Sha1Utils();
// 获取用户信息
log.debug("微信登陆 sessionKey = {}", sessionKey);
String userInfoStr = sha.decryptWXAppletInfo(sessionKey, param.getEncryptedData(), param.getIv());
if (StringUtil.isEmpty(userInfoStr)) {
return BaseResponse.buildFail("无法获取用户信息");
}
JSONObject json = JSONObject.parseObject(userInfoStr);
//绑定微信的手机号
String tel = json.getString("purePhoneNumber");
Assert.isTrue(!StringUtils.isEmpty(tel), "无法获取用户手机号");
BaseResponse baseResponse=new BaseResponse();
baseResponse.setResultInfo(tel);
baseResponse.setState(0);
return baseResponse;
}
}
}
复制代码
接口
public interface WeChatService {
/**
* 用code换取openid
*
* @param code
* @return
*/
Code2SessionResponse code2Session(String code);
/**
* 获取凭证
*
* @return
*/
String getAccessToken();
/**
* 获取凭证
*
* @param isForce
* @return
*/
String getAccessToken(boolean isForce);
String getSessionKey(String openId);
}
复制代码
实现类
public class WeChatServiceImpl implements WeChatService {
//获取配置文件数据
@Value("${wechat.miniprogram.id}")
private String appId;
@Value("${wechat.miniprogram.secret}")
private String appSecret;
@Reference
private SysUserService sysUserService;
@Override
public Code2SessionResponse code2Session(String code) {
String rawResponse = HttpClientUtil
.get(String.format(WechatConstant.URL_CODE2SESSION, appId, appSecret, code));
log.info("rawResponse====={}", rawResponse);
Code2SessionResponse response = JSON.parseObject(rawResponse, Code2SessionResponse.class);
if (response.isSuccess()) {
cacheSessionKey(response);
}
return response;
}
private void cacheSessionKey(Code2SessionResponse response) {
RedisCache redisCache = RedisCache.getInstance();
String key = RedisCacheKeys.getWxSessionKeyKey(response.getOpenId());
redisCache.setCache(key, 2147483647, response.getSessionKey());
}
@Override
public String getAccessToken() {
return getAccessToken(false);
}
@Override
public String getAccessToken(boolean isForce) {
RedisCache redisCache = RedisCache.getInstance();
String accessToken = null;
if (!isForce) {
accessToken = redisCache.getCache(RedisCacheKeys.getWxAccessTokenKey(appId));
}
if (StringUtil.isNotEmpty(accessToken)) {
return accessToken;
}
String rawResponse = HttpClientUtil
.get(String.format(WechatConstant.URL_GET_ACCESS_TOKEN, appId, appSecret));
AccessTokenResponse response = JSON.parseObject(rawResponse, AccessTokenResponse.class);
log.info("getAccessToken:response={}", response);
if (response.isSuccess()) {
redisCache.setCache(RedisCacheKeys.getWxAccessTokenKey(appId), 7000, response.getAcessToken());
return response.getAcessToken();
}
return null;
}
@Override
public String getSessionKey(String openId) {
RedisCache redisCache = RedisCache.getInstance();
String key = RedisCacheKeys.getWxSessionKeyKey(openId);
String sessionKey = redisCache.getCache(key);
return sessionKey;
}
}
复制代码
用到的解密工具类
public class Sha1Utils {
public static String decryptWXAppletInfo(String sessionKey, String encryptedData, String iv) {
String result = null;
try {
byte[] encrypData = Base64.decodeBase64(encryptedData);
byte[] ivData = Base64.decodeBase64(iv);
byte[] sessionKeyB = Base64.decodeBase64(sessionKey);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyB, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] doFinal = cipher.doFinal(encrypData);
result = new String(doFinal);
return result;
} catch (Exception e) {
//e.printStackTrace();
log.error("decryptWXAppletInfo error",e);
}
return null;
}
}
复制代码
网络请求工具类
public class HttpClientUtil {
// utf-8字符编码
public static final String CHARSET_UTF_8 = "utf-8";
// HTTP内容类型。
public static final String CONTENT_TYPE_TEXT_HTML = "text/xml";
// HTTP内容类型。相当于form表单的形式,提交数据
public static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";
// HTTP内容类型。相当于form表单的形式,提交数据
public static final String CONTENT_TYPE_JSON_URL = "application/json;charset=utf-8";
// 连接管理器
private static PoolingHttpClientConnectionManager pool;
// 请求配置
private static volatile RequestConfig requestConfig;
private static CloseableHttpClient getNewHttpClient() {
CloseableHttpClient httpClient = HttpClients.custom()
// 设置连接池管理
.setConnectionManager(pool)
// 设置请求配置
.setDefaultRequestConfig(getRequestConfig())
// 设置重试次数
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();
return httpClient;
}
/**
* 发送 post请求
*
* @param httpUrl
* 地址
*/
public static String post(String httpUrl) {
// 创建httpPost
HttpPost httpPost = new HttpPost(httpUrl);
return request(httpPost);
}
public static byte[] postRaw(String httpUrl) {
// 创建httpPost
HttpPost httpPost = new HttpPost(httpUrl);
return requestRaw(httpPost);
}
/**
* 发送 get请求
*
* @param httpUrl
*/
public static String get(String httpUrl) {
// 创建get请求
HttpGet httpGet = new HttpGet(httpUrl);
return request(httpGet);
}
/**
* 发送 post请求(带文件)
*
* @param httpUrl
* 地址
* @param maps
* 参数
* @param fileLists
* 附件
*/
public static String post(String httpUrl, Map<String, String> maps, List<File> fileLists,
String fileName) {
HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
MultipartEntityBuilder meBuilder = MultipartEntityBuilder.create();
if (maps != null) {
for (String key : maps.keySet()) {
meBuilder.addPart(key, new StringBody(maps.get(key), ContentType.TEXT_PLAIN));
}
}
if (fileLists != null) {
for (File file : fileLists) {
FileBody fileBody = new FileBody(file);
meBuilder.addPart(fileName, fileBody);
}
}
HttpEntity reqEntity = meBuilder.build();
httpPost.setEntity(reqEntity);
return request(httpPost);
}
public static String post(String httpUrl, Map<String, String> maps, List<File> fileLists) {
return post(httpUrl, maps, fileLists, "file");
}
public static String post(String httpUrl, List<File> fileLists) {
return post(httpUrl, Collections.emptyMap(), fileLists, "file");
}
/**
* 发送 post请求
*
* @param httpUrl
* 地址
* @param params
* 参数(格式:key1=value1&key2=value2)
*
*/
public static String post(String httpUrl, String params) {
HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
try {
// 设置参数
if (params != null && params.trim().length() > 0) {
StringEntity stringEntity = new StringEntity(params, "UTF-8");
stringEntity.setContentType(CONTENT_TYPE_FORM_URL);
httpPost.setEntity(stringEntity);
}
} catch (Exception e) {
e.printStackTrace();
}
return request(httpPost);
}
/**
* 发送 post请求
*
* @param maps
* 参数
*/
public static String post(String httpUrl, Map<String, String> maps) {
String param = convertStringParamter(maps);
return post(httpUrl, param);
}
/**
* 发送 post请求 发送json数据
*
* @param httpUrl
* 地址
* @param content
*
*
*/
public static String post(String httpUrl, String content, String contentType) {
// HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
// try {
// // 设置参数
// if (StringUtils.isNotEmpty(content)) {
// StringEntity stringEntity = new StringEntity(content, "UTF-8");
// stringEntity.setContentType(contentType);
// httpPost.setEntity(stringEntity);
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// return request(httpPost);
return new String(postRaw(httpUrl, content, contentType), StandardCharsets.UTF_8);
}
public static byte[] postRaw(String httpUrl, String content, String contentType) {
HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
try {
// 设置参数
if (StringUtils.isNotEmpty(content)) {
StringEntity stringEntity = new StringEntity(content, "UTF-8");
stringEntity.setContentType(contentType);
httpPost.setEntity(stringEntity);
}
} catch (Exception e) {
e.printStackTrace();
}
return requestRaw(httpPost);
}
/**
* 发送 post请求 发送json数据
*
* @param httpUrl
* 地址
* @param paramsJson
* 参数(格式 json)
*
*/
public static String postJson(String httpUrl, String paramsJson) {
return post(httpUrl, paramsJson, CONTENT_TYPE_JSON_URL);
}
public static byte[] postJsonRaw(String httpUrl, String paramsJson) {
return postRaw(httpUrl, paramsJson, CONTENT_TYPE_JSON_URL);
}
/**
* 发送 post请求 发送xml数据
*
* @param url 地址
* @param paramsXml 参数(格式 Xml)
*
*/
public static String postXml(String url, String paramsXml) {
return post(url, paramsXml, CONTENT_TYPE_TEXT_HTML);
}
/**
* 将map集合的键值对转化成:key1=value1&key2=value2 的形式
*
* @param parameterMap
* 需要转化的键值对集合
* @return 字符串
*/
public static String convertStringParamter(Map parameterMap) {
StringBuilder parameterBuffer = new StringBuilder();
if (parameterMap != null) {
Iterator iterator = parameterMap.keySet().iterator();
String key = null;
String value = null;
while (iterator.hasNext()) {
key = (String) iterator.next();
if (parameterMap.get(key) != null) {
value = (String) parameterMap.get(key);
} else {
value = "";
}
parameterBuffer.append(key).append("=").append(value);
if (iterator.hasNext()) {
parameterBuffer.append("&");
}
}
}
return parameterBuffer.toString();
}
/**
* 发送请求
*
* @param request
* @return
*/
public static byte[] requestRaw(HttpRequestBase request) {
CloseableHttpClient httpClient;
CloseableHttpResponse response = null;
// 响应内容
// String responseContent = null;
byte[] rawResponse = null;
try {
// 创建默认的httpClient实例.
httpClient = getNewHttpClient();
// 配置请求信息
request.setConfig(requestConfig);
// 执行请求
response = httpClient.execute(request);
// 得到响应实例
HttpEntity entity = response.getEntity();
// 可以获得响应头
// Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
// for (Header header : headers) {
// System.out.println(header.getName());
// }
// 得到响应类型
// System.out.println(ContentType.getOrDefault(response.getEntity()).getMimeType());
// 判断响应状态
if (response.getStatusLine().getStatusCode() >= 300) {
throw new Exception("HTTP Request is not success, Response code is "
+ response.getStatusLine().getStatusCode());
}
if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
rawResponse = EntityUtils.toByteArray(entity);
// responseContent = EntityUtils.toString(entity, CHARSET_UTF_8);
EntityUtils.consume(entity);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return rawResponse;
}
private static String request(HttpRequestBase req) {
return new String(requestRaw(req), StandardCharsets.UTF_8);
}
private static RequestConfig getRequestConfig() {
if (requestConfig == null) {
synchronized (HttpClientUtil.class) {
if (requestConfig == null) {
try {
//System.out.println("初始化HttpClientTest~~~开始");
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
// 配置同时支持 HTTP 和 HTPPS
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf).build();
// 初始化连接管理器
pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// 将最大连接数增加到200,实际项目最好从配置文件中读取这个值
pool.setMaxTotal(200);
// 设置最大路由
pool.setDefaultMaxPerRoute(2);
// 根据默认超时限制初始化requestConfig
int socketTimeout = 10000;
int connectTimeout = 10000;
int connectionRequestTimeout = 10000;
requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(connectionRequestTimeout)
.setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
// 设置请求超时时间
requestConfig = RequestConfig.custom().setSocketTimeout(50000)
.setConnectTimeout(50000).setConnectionRequestTimeout(50000).build();
}
}
}
return requestConfig;
}
}
复制代码
常量
public interface WechatConstant {
Integer OK_STATUS = 0;
String URL_CODE2SESSION = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
String URL_GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
String URL_GET_IMAGE = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";
/**
* 给公众号发送信息。参考https://mp.weixin.qq.com/advanced/tmplmsg?action=faq&token=708366329&lang=zh_CN
*/
String URL_SEND_TO_CHANNEL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";
String URL_SEND_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s";
/**
* 发送模板消息。参考https://developers.weixin.qq.com/miniprogram/dev/api-backend/sendMiniTemplateMessage.html
*/
String URL_SEND_TEMPLATE_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=%s";
String URL_QR_CODE_UNLIMTED = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s";
String URL_QR_CODE = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s";
/**
* 获取标签下粉丝列表
*/
String URL_ALL_FANS_OPENID = "https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s";
/**
* 获取公众号已创建的标签
*/
String URL_ALL_TAGS = "https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s";
}
复制代码
使用到的实体类
public class Code2SessionResponse implements Serializable {
public static Integer RESPONSE_OK = 0;
@JSONField(name = "openid")
private String openId;
@JSONField(name = "session_key")
private String sessionKey;
@JSONField(name = "unionid")
private String unionId;
@JSONField(name = "errcode")
private Integer errCode;
@JSONField(name = "errmsg")
private String errMsg;
public boolean isSuccess() {
return this.errCode == null || RESPONSE_OK.equals(this.errCode);
}
}
复制代码
总结:微信小程序的后端开发主要就是对用户进行授权 , 1、前端调用 wx.login 返回了code,然后调用wx.getUserInfo获取到用户的昵称 头像 2.首先通过微信授权用js_code换取openId,来获取openId,前端传微信的参数 code字段 3.然后解密获取手机号 前端需要传openId encryptedData iv 等字段来获取用户的的授权手机号
这些信息都获取后 接着就是调用后端的登陆接口,登陆接口如果只有授权登录就是我们将接口参数为下图最后三个字段为前端必填字段
主要步骤是根据前端的openId获取sessionKey 然后根据sessionKey 和其他参数进行解密获取用户手机号
通过解密获取授权登录的手机号,然后根据自己的业务逻辑处理即可,这样我们就可以根据授权的手机号进行授权登录
作者:CoderZS
链接:https://juejin.im/post/5dcaa988f265da4d2125e0bf
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
微信小程序后端开发流程的更多相关文章
- 微信小程序注册开发流程
开篇: 微信小程序 很多刚学的同学都不太清楚如何去申请这个小程序的appid 现在呢我就一步步的告诉大家这个流程: 首先第1步,百度搜索:微信公众平台-点击右上角的立即注册 第2步:可以看到有4大注册 ...
- 微信小程序后端开发(Java语言)笔记
前言: 因为是第一次真正接触后端开发,从编码到部署服务器到上线,所以做个笔记,也供和我一样的开发小白一些参考. 一.前期工作:开发环境与工具: 1. 编程语言:Java #笔者还没学PHP,只想 ...
- 关于开发微信小程序后端linux使用xampp配置https
关于开发微信小程序后端linux使用xampp配置https 背景 由于最近开发微信小程序,前后端交互需要使用https协议,故需要配置https服务 服务器环境 服务器系统 ubuntu 环境 xa ...
- 微信小程序从开发到上线流程
一.微信小程序从开发到上线流程 注册小程序 1.登录微信公众平台 https://mp.weixin.qq.com 2.在微信公众平台>立即注册>小程序中注册微信小程序 3.在邮箱中激活并 ...
- 微信小程序云开发-从0打造云音乐全栈小程序
第1章 首门小程序“云开发”课程,你值得学习本章主要介绍什么是小程序云开发以及学习云开发的重要性,并介绍项目的整体架构,真机演示项目功能,详细介绍整体课程安排.课程适用人群以及需要掌握的前置知识.通过 ...
- 小程序语音红包开发中 汉字转拼音的问题 微信小程序红包开发遇到的坑
公司最近在开发微信小程序的红包功能,语音红包需要用到文字转拼音的功能. 之前介绍过怎么将中文的汉字转为拼音的,具体看下面这篇文章. 微信语音红包小程序开发如何提高精准度 红包小程序语音识别精准度 微信 ...
- 第一章 “我要点爆”微信小程序云开发之项目建立与我的页面功能实现
第一章 “我要点爆”微信小程序云开发之项目建立与我的页面功能实现 开发环境搭建 使用自己的AppID新建小程序项目,后端服务选择小程序·云开发,点击新建,完成项目新建. 新建成功后跳转到开发者工具界面 ...
- 校园表白墙、微信表白墙、校园墙 微信小程序 JAVA 开发记录与分享
目录 最新版表白墙博客地址 1.微信小程序前台展示 2.功能介绍 3.后台管理 4.后端语言采用 JAVA 开发 5.体验此微信小程序 扫描下方二维码 6.如何联系我或需要源码进行联系 最新版表白墙博 ...
- 微信小程序登入流程
微信小程序登入流程 一.首先前端先传code去后端 wx.login({ success(res) { if (res.code) { //发起网络请求 wx.request({ url: app.g ...
随机推荐
- WeChall_Prime Factory (Training, Math)
Your task is simple:Find the first two primes above 1 million, whose separate digit sums are also pr ...
- Go语言实现:【剑指offer】把字符串转换成整数
该题目来源于牛客网<剑指offer>专题. 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数. 输入描述: 输入一个字符串,包括数字字母符号,可以为空. 输出描述: 如果是合 ...
- centos 7安装reids
一.reids下载 下载地址: https://redis.io/ 二.解压安装 ① 解压:tar -zxvf redis-5.0.5.tar.gz ② 安装环境:yum install gcc-c ...
- 给 iTerm 终端设置代理
本文介绍如何为自己的终端设置代理,从而实现在命令行中访问Google. 1. 背景 当你使用SS FQ时,大部分浏览器都可以成功访问Google,但是在命令行下执行curl https://www.g ...
- Maven项目pom文件的节点释意
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- 一键安装apache-2.4.38脚本
[root@lamp scripts]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) [root@lamp scripts ...
- Day2前端学习之路——HTML基本知识
课程目标: 通过制作自己的简历,更加清楚地了解HTML是什么,HTML5是什么.学习基本的HTML标签,理解HTML语义化概念 任务一:回答问题 1.HTML是什么,HTML5是什么? HTML是一种 ...
- CVE-2020-0668-Windows服务跟踪中的普通特权升级错误
CVE-2020-0668-Windows服务跟踪中的普通特权升级错误 在这里中,我将讨论在Windows Service跟踪中发现的任意文件移动漏洞.从我的测试来看,它影响了从Vista到10的所有 ...
- Windows-server-2008-R2安装Oracle-11g-R2-dataguard
一.安装环境 1.服务器环境:Windows server 2008 R2 x64 Standard 两台 CPU:8核 内存:8G 硬盘空间:1060G 2.软件:oracle 11g R2 二.安 ...
- 处理jquery 中 给disabled属性不传值的问题
问题:审核页面加入不可编辑的判断后,点击[审核]按钮,报错,form表单的数据没有传递过去. 下面是js中加入的代码,用来判断是否是审核页面的,去掉此代码,点击[审核]按钮能正常传递数据,加入的话,无 ...