微信小程序如今被廣泛使用,微信小程序按照微信官網的定義來說就是:
微信小程序是一種全新的連接用戶與服務的方式,它可以在微信內被便捷地獲取和傳播,同時具有出色的使用體驗。
這就是微信小程序的魅力所在,有的時候我們不需要去下載過多的app,只要打開微信,就可以應用每一個服務,甚至連注冊都變得簡單起來,具有特別貼近人心的用戶體驗。
最近在做一個微信小程序的服務端,主要就是實現登錄、業務賬號和微信號綁定的基本功能,接下來總結一下如何實現微信小程序的服務端代碼。
要去實現服務端的代碼,就要先去了解客戶端的請求。
微信小程序開發平台開發了很全面、功能很強大的開發API供我們使用:
https://developers.weixin.qq.com/miniprogram/dev/api/
我們來看下api中的登錄功能:

這個是微信小程序登錄的時序圖,非常明顯地告知了我們微信小程序登錄的請求服務調用的過程
1.添加微信小程序固定信息
在注冊的時候,這個小程序的appId和appSecret已經知道了,還有固定的url,然后我們可以將其做為配置文件放入后台的代碼中去:

2.根據小程序信息和code獲取openId和session_key
在前端代碼中,每當我們剛進入小程序的時候,都會去調用wx.login()方法,會得到一個code,然后發送給我們,然后,我們通過code和這些已知的參數,去調用微信的接口,去獲取openId和session_key,作為登錄態的標識
/**
* 獲取微信小程序的session_key和openid
*
* @author hengyang4
* @param code 微信前端login()方法返回的code
* @return jsonObject
*
* */
public JSONObject getSessionKeyAndOpenId(String code)throws Exception{
//微信登錄的code值
String wxCode = code;
//讀取屬性文件
ResourceBundle resourceBundle = ResourceBundle.getBundle("weixin");
//服務器端調用接口的url
String requestUrl = resourceBundle.getString("url");
//封裝需要的參數信息
Map<String,String> requestUrlParam = new HashMap<String,String>();
//開發者設置中的appId
requestUrlParam.put("appid",resourceBundle.getString("appId"));
//開發者設置中的appSecret
requestUrlParam.put("secret",resourceBundle.getString("appSecret"));
//小程序調用wx.login返回的code
requestUrlParam.put("js_code", wxCode);
//默認參數
requestUrlParam.put("grant_type", "authorization_code");
JSONObject jsonObject = JSON.parseObject(sendPost(requestUrl,requestUrlParam));
return jsonObject;
}
/**
* 向指定 URL 發送POST方法的請求
*
* @param url 發送請求的 URL
* @return 所代表遠程資源的響應結果
*/
public String sendPost(String url, Map<String, ?> paramMap) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
String param = "";
Iterator<String> it = paramMap.keySet().iterator();
while(it.hasNext()) {
String key = it.next();
param += key + "=" + paramMap.get(key) + "&";
}
try {
URL realUrl = new URL(url);
// 打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
// 設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 發送POST請求必須設置如下兩行
conn.setDoOutput(true);
conn.setDoInput(true);
// 獲取URLConnection對象對應的輸出流
out = new PrintWriter(conn.getOutputStream());
// 發送請求參數
out.print(param);
// flush輸出流的緩沖
out.flush();
// 定義BufferedReader輸入流來讀取URL的響應
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
log.error(e.getMessage(),e);
}
//使用finally塊來關閉輸出流、輸入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
3. 將openId和session_key生成sessionId返回客戶端
第三方服務器端拿到請求回來的session_key和openid,先留着,不能給客戶端;然后用操作系統提供的真正隨機數算法生成一個新的session,叫session_id
· 所以,每次前端wx.login()后調用的服務端的controller我們就應該這樣寫:
@RequestMapping(value = "/loginByWeixin", produces = "application/json;charset=UTF-8")
public String loginByWeixin(@RequestParam("code") String code) throws Exception{
//得到用戶的openId + sessionKey
JSONObject jsonObject = getSessionKeyAndOpenId(code);
log.info(jsonObject.toString());
System.out.println(jsonObject);
String openId = jsonObject.getString("openid");
String sessionKey = jsonObject.getString("session_key");
//組裝結果
Map<String,Object> resMap = new HashMap<String,Object>();
//判斷openId是否存在用戶表中
UserInfo userInfo = userInfoMapper.selectByUserName(openId);
if(ValidateUtil.isEmpty(userInfo)){
//不存在該用戶關聯關系
log.info("驗證是否綁定微信","未登錄");
resMap.put("code",ErrorCode.ERR_WEIXIN_USER_EMPTY.getErrorCode());
resMap.put("desc",ErrorCode.ERR_WEIXIN_USER_EMPTY.getErrorMessage());
}else {
//組裝結果
log.info("驗證是否綁定微信", "用戶查詢成功");
//0:操作成功
resMap.put("code", ErrorCode.ERR_SUCCEED.getErrorCode());
resMap.put("desc", ErrorCode.ERR_SUCCEED.getErrorMessage());
resMap.put("userInfo", userInfo);
}
//將通過md5生成sessionId(一般是用個操作系統提供的真正隨機數算法生成新的session)
String sessionId = MD5.EncodeByMd5(openId+sessionKey+ DateUtil.getNowTime());
jedisCache.hashSet(sessionId,"ses",openId);
resMap.put("sessionId",sessionId);
return JSONConvertor.toJSON(resMap);
}
4. 使用過濾器過濾請求頭中含有session_id的請求
然后我們將session_id為key,微信服務端返回的openId為值,保存起來,這里我們用了redis去緩存這個session信息,openId為我們系統用戶與微信綁定的標識
接下來,我們每次請求的時候,都會將session_id放入請求頭中去,然后判斷在redis中是否有key為該值得鍵值對,從而判斷用戶session是否失效
我們這里使用了過濾器來攔截用戶請求
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException,ServletException{
//設置跨域請求
HttpServletResponse resp = (HttpServletResponse)response;
resp.setHeader("Access-Control-Allow-Origin", "*");
HttpServletRequest req = (HttpServletRequest)request;
//獲取請求時的sessionId
String sessionId = req.getHeader("sessionId");
if(StrUtil.IsNullOrEmpty(sessionId)){
//該請求不需要驗證session,直接通過
log.info("sessionId過濾","該請求不需要過濾,通過");
chain.doFilter(request,response);
return;
}else {
//只有在緩存中存在該sessionId才能進行請求
if (!jedisCache.existKey(sessionId)) {
// 登錄信息已過期,請重新登錄
log.info("sessionId過濾", "登錄信息失效,請重新登錄");
response.getWriter().write("登錄信息失效,請重新登錄");
return;
}
log.info("sessionId過濾", "session驗證成功");
chain.doFilter(request, response);
}
}
5. 在數據庫中建立用戶與微信用戶唯一標識的關聯關系
每當用戶注冊或者登陸時,請求中都會含有session_id,然后我們將session_id作為key去redis中查找,得到value值,也就是我們之前存的openId,然后我們將openId和用戶信息進行數據庫表中的關聯,之后,我們調用登陸方法的時候,如果該openId有關聯的用戶信息,則不需要去登錄,直接給前端返回用戶信息即可,就是我們之前的那段代碼:

在這里,我們的微信小程序服務端代碼就基本實現了,可以用於微信小程序與業務系統的登錄,微信小程序的功能還有很多,之后我們有機會多去試一下其他功能,感受小程序的強大和快捷。
