微信小程序后端(java)開發(fā)流程的詳細(xì)步驟
微信小程序后端開發(fā)流程根據(jù)官網(wǎng)總結(jié)為兩個步驟
1、前端調(diào)用 wx.login 返回了code,然后調(diào)用wx.getUserInfo獲取到用戶的昵稱 頭像
2、服務(wù)端根據(jù)code去微信獲取openid, 接口地址: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html%EF%BC%9B%E5%90%8C%E6%97%B6%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%94%A8%E6%88%B7%E6%98%B5%E7%A7%B0%E5%A4%B4%E5%83%8F%E7%AD%89%E8%B5%84%E6%96%99
微信小程序后端接口開發(fā)
controller層
public class OauthController {
@Autowired
private WeChatService weChatService;
/**
* 微信授權(quán)用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("參數(shù)異常");
}
Code2SessionResponse res = weChatService.code2Session(code);
log.info("code2Session,res={}", res);
if (!res.isSuccess()) {
return BaseResponse.buildFail(res.getErrCode(), res.getErrMsg());
}
return BaseResponse.buildSuccess(res);
}
/**
* 解密獲取手機(jī)號
* @param request
* @param response
* @param param
* @return
*/
public BaseResponse decryptGetPhone(HttpServletRequest request, HttpServletResponse response,
@RequestBody OauthParam param) {
if (!StringUtil.isEmpty(param.getOpenId())) {//微信授權(quán)登錄
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);
//綁定微信的手機(jī)號
String tel = json.getString("purePhoneNumber");
Assert.isTrue(!StringUtils.isEmpty(tel), "無法獲取用戶手機(jī)號");
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);
}
實(shí)現(xiàn)類
public class WeChatServiceImpl implements WeChatService {
//獲取配置文件數(shù)據(jù)
@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;
}
}
網(wǎng)絡(luò)請求工具類
public class HttpClientUtil {
// utf-8字符編碼
public static final String CHARSET_UTF_8 = "utf-8";
// HTTP內(nèi)容類型。
public static final String CONTENT_TYPE_TEXT_HTML = "text/xml";
// HTTP內(nèi)容類型。相當(dāng)于form表單的形式,提交數(shù)據(jù)
public static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";
// HTTP內(nèi)容類型。相當(dāng)于form表單的形式,提交數(shù)據(jù)
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()
// 設(shè)置連接池管理
.setConnectionManager(pool)
// 設(shè)置請求配置
.setDefaultRequestConfig(getRequestConfig())
// 設(shè)置重試次數(shù)
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();
return httpClient;
}
/**
* 發(fā)送 post請求
*
* @param httpUrl
* 地址
*/
public static String post(String httpUrl) {
// 創(chuàng)建httpPost
HttpPost httpPost = new HttpPost(httpUrl);
return request(httpPost);
}
public static byte[] postRaw(String httpUrl) {
// 創(chuàng)建httpPost
HttpPost httpPost = new HttpPost(httpUrl);
return requestRaw(httpPost);
}
/**
* 發(fā)送 get請求
*
* @param httpUrl
*/
public static String get(String httpUrl) {
// 創(chuàng)建get請求
HttpGet httpGet = new HttpGet(httpUrl);
return request(httpGet);
}
/**
* 發(fā)送 post請求(帶文件)
*
* @param httpUrl
* 地址
* @param maps
* 參數(shù)
* @param fileLists
* 附件
*/
public static String post(String httpUrl, Map<String, String> maps, List<File> fileLists,
String fileName) {
HttpPost httpPost = new HttpPost(httpUrl);// 創(chuàng)建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");
}
/**
* 發(fā)送 post請求
*
* @param httpUrl
* 地址
* @param params
* 參數(shù)(格式:key1=value1&key2=value2)
*
*/
public static String post(String httpUrl, String params) {
HttpPost httpPost = new HttpPost(httpUrl);// 創(chuàng)建httpPost
try {
// 設(shè)置參數(shù)
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);
}
/**
* 發(fā)送 post請求
*
* @param maps
* 參數(shù)
*/
public static String post(String httpUrl, Map<String, String> maps) {
String param = convertStringParamter(maps);
return post(httpUrl, param);
}
/**
* 發(fā)送 post請求 發(fā)送json數(shù)據(jù)
*
* @param httpUrl
* 地址
* @param content
*
*
*/
public static String post(String httpUrl, String content, String contentType) {
// HttpPost httpPost = new HttpPost(httpUrl);// 創(chuàng)建httpPost
// try {
// // 設(shè)置參數(shù)
// 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);// 創(chuàng)建httpPost
try {
// 設(shè)置參數(shù)
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);
}
/**
* 發(fā)送 post請求 發(fā)送json數(shù)據(jù)
*
* @param httpUrl
* 地址
* @param paramsJson
* 參數(shù)(格式 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);
}
/**
* 發(fā)送 post請求 發(fā)送xml數(shù)據(jù)
*
* @param url 地址
* @param paramsXml 參數(shù)(格式 Xml)
*
*/
public static String postXml(String url, String paramsXml) {
return post(url, paramsXml, CONTENT_TYPE_TEXT_HTML);
}
/**
* 將map集合的鍵值對轉(zhuǎn)化成:key1=value1&key2=value2 的形式
*
* @param parameterMap
* 需要轉(zhuǎn)化的鍵值對集合
* @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();
}
/**
* 發(fā)送請求
*
* @param request
* @return
*/
public static byte[] requestRaw(HttpRequestBase request) {
CloseableHttpClient httpClient;
CloseableHttpResponse response = null;
// 響應(yīng)內(nèi)容
// String responseContent = null;
byte[] rawResponse = null;
try {
// 創(chuàng)建默認(rèn)的httpClient實(shí)例.
httpClient = getNewHttpClient();
// 配置請求信息
request.setConfig(requestConfig);
// 執(zhí)行請求
response = httpClient.execute(request);
// 得到響應(yīng)實(shí)例
HttpEntity entity = response.getEntity();
// 可以獲得響應(yīng)頭
// Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
// for (Header header : headers) {
// System.out.println(header.getName());
// }
// 得到響應(yīng)類型
// System.out.println(ContentType.getOrDefault(response.getEntity()).getMimeType());
// 判斷響應(yīng)狀態(tài)
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);
// 將最大連接數(shù)增加到200,實(shí)際項(xiàng)目最好從配置文件中讀取這個值
pool.setMaxTotal(200);
// 設(shè)置最大路由
pool.setDefaultMaxPerRoute(2);
// 根據(jù)默認(rèn)超時限制初始化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();
}
// 設(shè)置請求超時時間
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";
/**
* 給公眾號發(fā)送信息。參考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";
/**
* 發(fā)送模板消息。參考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";
/**
* 獲取標(biāo)簽下粉絲列表
*/
String URL_ALL_FANS_OPENID = "https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s";
/**
* 獲取公眾號已創(chuàng)建的標(biāo)簽
*/
String URL_ALL_TAGS = "https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s";
}
使用到的實(shí)體類
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);
}
}
總結(jié):微信小程序的后端開發(fā)主要就是對用戶進(jìn)行授權(quán) , 1、前端調(diào)用 wx.login 返回了code,然后調(diào)用wx.getUserInfo獲取到用戶的昵稱 頭像 2.首先通過微信授權(quán)用js_code換取openId,來獲取openId,前端傳微信的參數(shù) code字段 3.然后解密獲取手機(jī)號 前端需要傳openId encryptedData iv 等字段來獲取用戶的的授權(quán)手機(jī)號
這些信息都獲取后 接著就是調(diào)用后端的登陸接口,登陸接口如果只有授權(quán)登錄就是我們將接口參數(shù)為下圖最后三個字段為前端必填字段
主要步驟是根據(jù)前端的openId獲取sessionKey 然后根據(jù)sessionKey 和其他參數(shù)進(jìn)行解密獲取用戶手機(jī)號
通過解密獲取授權(quán)登錄的手機(jī)號,然后根據(jù)自己的業(yè)務(wù)邏輯處理即可,這樣我們就可以根據(jù)授權(quán)的手機(jī)號進(jìn)行授權(quán)登錄

- 詳解Java后端優(yōu)雅驗(yàn)證參數(shù)合法性
- java 后端生成pdf模板合并單元格表格的案例
- Java后端面試題最新整理
- Java后端Cookie實(shí)現(xiàn)(時間戳)代碼實(shí)例
- 微信小程序獲取手機(jī)號,后端JAVA解密流程代碼
- 關(guān)于java后端的詳解
- Java后端產(chǎn)生驗(yàn)證碼后臺驗(yàn)證功能的實(shí)現(xiàn)代碼
- 一個Java程序猿眼中的前后端分離以及Vue.js入門(推薦)
- 詳解使用IntelliJ IDEA新建Java Web后端resfulAPI模板
- Java后端WebSocket的Tomcat實(shí)現(xiàn)
- JAVA后端應(yīng)該學(xué)什么技術(shù)
相關(guān)文章
JavaScript setTimeout使用閉包功能實(shí)現(xiàn)定時打印數(shù)值
這篇文章主要介紹了JavaScript setTimeout使用閉包功能實(shí)現(xiàn)定時打印數(shù)值 的相關(guān)資料,需要的朋友可以參考下2015-12-12
JavaScript實(shí)現(xiàn)移動端帶transition動畫的輪播效果
這篇文章主要介紹了JavaScript原生實(shí)現(xiàn)帶transition動畫的自動+手動輪播效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
深入解析JavaScript中函數(shù)的Currying柯里化
這篇文章主要介紹了JavaScript中函數(shù)的Currying柯里化,Currying 的重要意義在于可以把函數(shù)完全變成"接受一個參數(shù)、返回一個值"的固定形式,需要的朋友可以參考下2016-03-03
JS實(shí)現(xiàn)簡單面向?qū)ο蟮念伾x擇器實(shí)例
這篇文章主要介紹了JS實(shí)現(xiàn)簡單面向?qū)ο蟮念伾x擇器,以完整實(shí)例形式分析了JavaScript基于面向?qū)ο髮?shí)現(xiàn)顏色選擇器的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-04-04
詳解javascript 正則表達(dá)式之分組與前瞻匹配
本文主要講解javascript 的正則表達(dá)式中的分組匹配與前瞻匹配的,需要對正則的有基本認(rèn)識,本人一直對兩種匹配模棱不清,還有不清楚的朋友跟隨腳本之家小編一起看看吧2018-05-05

