上一章节中,获取的用户信息,用户是否登录这些都是微信处理的,大多数情况,我们希望知道用户是否登录了我们的服务端,我们想将微信里面的用户信息跟我们的服务端同步,这一章就讲讲如何保存用户信息到服务端。
基本流程就是这个图:
有的同学会问,我们已经获取到用户的头像了,昵称了,还有一些其他信息,直接传到服务端不就可以了,首先这么做不安全,其次,我们没有一个始终唯一的标识来标记这个用户,我们要找到用户唯一的东西
确认用户下次授权微信登录的时候我们还能获取到这个唯一的标识,这个唯一标识就是openId,有的人又说openId不是唯一的,是的,unionid才是唯一的,但是openid对单独的应用是唯一的,就比如一个小程序是唯一的,所以用openid就足够了。
如果考虑到有多个小程序或者公众号,想把这个用户在所有应用中都是唯一的,那么就要用unionid了。而且未认证的公众号是不可以获取unionid,所以个人的小程序是无法获取unionid的,所以就用openid吧。
1.微信端
我们还是直接看代码,我们还是那上一章的栗子继续写,上一章只是获取了微信的信息,没有授权到我们的服务端。
wxml:
<view class="container">
<view class='avatar'><image src="{{userIcon}}"></image></view>
<view class="user-name">{{userName}}</view>
<block wx:if="{{!haveLogin}}">
<button bindtap="openSetting" class='btn'>点我登录</button>
</block>
<button bindtap="openSetting" class='btn'>设置</button>
</view>
js:
const app = getApp()
Page({
data:{
userIcon:"../../images/default_icon.gif",
userName:"未登录",
haveLogin:true,
}, onShow:function(){
let that = this;
wx.getSetting({
success(res) {
if (!res.authSetting['scope.userInfo']) {
console.info("用户未授权");
that.setData({
userIcon: "../../images/default_icon.gif",
userName: "未登录",
haveLogin:false
})
} else {
console.info("用户已经授权");
that.setUserInfo();
}
}
})
},setUserInfo:function(){
let that = this;
//微信登录
wx.login({
success: function (loginRes) {
//获取微信用户信息
wx.getUserInfo({
success: function (userRes) {
//这里是我自定义的发送后端ajax请求的方法,主要传code,userData(加密后的信息),iv 这三个参数到服务端
app.ajaxRequest({
url: app.api.wxLogin,//服务端的授权地址
params: {
code: loginRes.code,//从登录信息中获取code,每次登录都不一样
userData: userRes.encryptedData,//从用户信息中获取加密的用户信息
iv: userRes.iv//从用户信息中获取iv
}, callback: function (res) {
//res是服务端返回的信息,包含用户的头像,用户名,还有sessionId
that.setData({
userIcon: res.userInfo.avatarUrl,
userName: res.userInfo.userName,
haveLogin: true
});
//设置sessionId到全局的变量中,后续的请求都在hader里面带上这个JESSIONID,服务端就跟普通的session处理一样就可以了。
app.globalData.header.Cookie = 'JSESSIONID=' + res.sessionId;
app.globalData.haveLogin = true;
}
});
}
});
}
})
},//打开权限设置
openSetting: function () {
let that = this;
wx.openSetting()
}
})
重点看下setUserInfo 方法,里面有跟服务端交互的逻辑,微信官方说的是要跟服务端交互两次,我使用一次也很正常,没有发现什么问题。
这是我封装的微信端的ajax请求:
ajaxRequest: function (config){
var loadType = config.loadType == null ? this.loadType.loading : this.loadType.top;
var url = config.url;
var callback = config.callback;
var params = config.params;
if (loadType == this.loadType.loading){
wx.showLoading({
title: '玩命加载中'
})
} else if (loadType == this.loadType.top){
wx.showNavigationBarLoading();
}
var that = this;
wx.request({
url: getApp().globalData.domain + url,//上线的话必须是https,没有appId的本地请求貌似不受影响
data: params,
method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
header: that.globalData.header, // 设置请求的 header
success: function (res) {
if (loadType == that.loadType.loading) {
wx.hideLoading();
} else if (loadType == that.loadType.top) {
wx.hideNavigationBarLoading();
wx.stopPullDownRefresh();
}
if (res.data.responseCode == that.responseCode.LOGIN_OUT){
that.globalData.haveLogin = false;
wx.showToast({
title: "登录超时,请在'我的'点击登录按钮完成登录",
icon: 'none',
duration: 2000
});
} else if (res.data.responseCode != that.responseCode.SUCCESS && res.data.responseCode != that.responseCode.LOGIN_OUT) {
wx.showToast({
title: res.data.errorMsg,
icon: 'none',
duration: 2000
});
return;
}else{
callback(res.data.data);
}
},
fail: function () {
wx.showToast({
title: "网络连接错误",
icon: 'none',
duration: 2000
});
return;
},
complete: function () {
// complete
}
})
}
注意:在app.js中添加以下内容,不然上面的发送ajax请求的方法会报错哦,这样在各个js中通过app 对象就可以调用了。
1.globalData 对象中加入这么一条哈 header: { 'Cookie': '', "x-requested-with":"XMLHttpRequest"}
2.添加一个数据对象,ajax请求loading类型
loadType:{
top: 0,
loading:1
}
3.添加服务端返回类型
responseCode:{
LOGIN_OUT: 401,//登录超时
SUCCESS:200,//请求成功
ERROR:500//服务异常
}
2.服务端
服务端的一些代码
2.1.AES解密
微信官网提供了一些语言的AES解密的demo,下载地址:https://developers.weixin.qq.com/miniprogram/dev/demo/aes-sample.zip

大家感受下,对,没有java的,我能说啥,好吧,我提供一个,其实也不复杂

需要一个jar的支持:
maven配置
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.54</version>
</dependency>
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class AES {
public static boolean initialized = false;
/**
* AES解密
* @param content 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void initialize(){
if (initialized) return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
//生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
2.2.登录,自动注册逻辑
@ResponseBody
@RequestMapping("/wxLogin")
public AjaxResponse wxLogin(HttpSession session, String code, String iv, String userData) {
AjaxResponse response = new AjaxResponse();
try {
/**
* 请求微信服务端,请求地址是:https://api.weixin.qq.com/sns/jscode2session 需要传入appid,secret,js_code ,grant_type(固定传这个串:authorization_code)
*/
String url = configInfo.getWxUrlLogin() + "?appid=" + configInfo.getWxAppId() + "&secret=" + configInfo.getWxSecret() + "&js_code=" + code + "&grant_type=" +
configInfo.getWxGrantType();
/**
* 发送http请求
*/
String wxResponse = OKHttpUtils.getRequest(url);
Map<String, String> map = JsonUtils.convertJson2Obj(wxResponse, Map.class);
/**
* 从返回的信息中获取 session_key 和open_id
*/
String session_key = map.get("session_key");
String openid = map.get("openid");
/**
* 用openid查询下自己的数据库,数据库中openid必须是唯一的哦,记得设置唯一主键
* 如果是第一登录,那么就会走自动注册逻辑,如果后续用户登录,那么就会走自动登录流程了。
*/
User loginUser = userService.getUserByWxOpenId(openid);
if (loginUser == null) {
/**
* 用户不存在,那么就要解析加密串中的用户信息
*/
AES aes = new AES();
byte[] resultByte = aes.decrypt(Base64.decodeBase64(userData), Base64.decodeBase64(session_key), Base64.decodeBase64(iv));
if (null != resultByte && resultByte.length > 0) {
String userInfo = new String(resultByte, "UTF-8");
Map<String, String> userInfoData = JsonUtils.convertJson2Obj(userInfo, Map.class);
String nickName = userInfoData.get("nickName");
String userIcon = userInfoData.get("avatarUrl");
User user = new User();
user.setUserIcon(userIcon);
user.setUserName(nickName);
user.setWxOpenId(openid);
/**
* 解析出来后,将用户信息自动注册一下。
*/
loginUser = userService.wxAutoRegister(user);
}
}
/**
* 获取sessionId
*/
String sessionId = session.getId();
/**
* 设置session信息
*/
SessionUser sessionUser = new SessionUser();
sessionUser.setUserName(loginUser.getUserName());
sessionUser.setUserIcon(loginUser.getUserIcon());
sessionUser.setUserId(loginUser.getUserId());
session.setAttribute(Constants.SESSION_USER_KEY, sessionUser);
/**
* 将用户信息返回
*/
WxUserVO userVO = CopyTools.copy(loginUser, WxUserVO.class);
/**
* 自动注册的肯定是没有密码的,所以根据这个判断用户是否绑定账号,绑定账号,就是让用户自己设置一个账号,这样就可以在其他端登录,比如网站
*/
userVO.setBindAccount(StringTools.isEmpty(loginUser.getPassword()) ? false : true);
/*
*返回用户信息,包括基本的用户信息,和sessionId信息
*/
WxLoginUserVO loginUserVO = new WxLoginUserVO();
loginUserVO.setSessionId(sessionId);
loginUserVO.setUserInfo(userVO);
response.setData(loginUserVO);
return response;
} catch (Exception e) {
logger.error("微信登录异常", e);
return AjaxResponse.getExceptionResponseVO();
}
}
以上代码是我的实现,你直接拷贝过去肯定是没法使用的

,一些类和对象是没有的,注释我写的很清楚了,大家可以根据自己的实际场景进行修改哈。
记住要将sessionId返回,在微信端请求服务端的时候,将这个sessionId放到header中就可以了,这样服务端就可以知道用户是否登录了。
好了,服务端登录就讲到这里了,还有不明白的可以在下面留言。