微信小程序登录,单个程序用openId,多个小程序统一登录时需要用unionId
代码逻辑
- 前台获取微信授权用户信息,及会话code
-
设置用户信息
- 根据前台rawData设置用户基本信息
- 根据code、appid、secret获取sessionKey。根据sessionkey、vi(解密向量)解析前台数据encryptedData中的unionid
-
根据openId或unionId查询用户信息,判断是否已经注册/登录过。
- 未登录:插入用户信息
- 登录过:更新用户信息
- redis中缓存token信息
- 返回前台token信息
参考文章:https://www.jianshu.com/p/fbe...
代码
controller
@RestController
@RequestMapping("api/wechat/user")
@Api(tags="微信登录")
public class TbWechatUserController {
@Autowired
private TbWechatUserService tbWechatUserService;
@PostMapping
@ApiOperation(value = "登录", httpMethod = "POST")
public Result login(@RequestBody WechatLoginRequestDTO dto) throws Exception {
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
Map userInfoMap = tbWechatUserService.getUserInfoMap(dto);
if (Optional.ofNullable(userInfoMap).isPresent()) {
return new Result().ok(userInfoMap);
} else {
return new Result().error("未授权小程序,请先授权");
}
}
}
DTO
@Data
@ApiModel(value = "小程序用户表")
public class WechatLoginRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "code不能为空", groups = AddGroup.class)
@ApiModelProperty(value = "微信code", required = true)
private String code;
@NotNull(message = "appName不能为空", groups = AddGroup.class)
@ApiModelProperty(value = "小程序名称,判断从哪个小程序登录", required = true)
private String appName;
@NotNull(message = "rawData不能为空",groups = AddGroup.class)
@ApiModelProperty(value = "用户非敏感字段")
private String rawData;
@ApiModelProperty(value = "签名")
private String signature;
@NotNull(message = "encryptedData不能为空", groups = AddGroup.class)
@ApiModelProperty(value = "用户敏感字段")
private String encryptedData;
@NotNull(message = "iv不能为空", groups = AddGroup.class)
@ApiModelProperty(value = "解密向量")
private String iv;
}
service
@Override
public Map getUserInfoMap(WechatLoginRequestDTO dto) throws Exception {
Map userInfoMap = new HashMap<>();
// 1、调用微信接口,获取sessionKey、openId
JSONObject sessionKeyOpenId = getSessionKeyOrOpenId(dto);
// 判断sessionKeyOpenId不能为空
// AssertUtils.isNull(sessionKeyOpenId, ModuleErrorCode.AUTHORIZE_INVALID);
if (!Optional.ofNullable(sessionKeyOpenId).isPresent()) {
logger.error("sessionKeyOpenId is null...");
return null;
}
// 获取openId、sessionKey
String openId = sessionKeyOpenId.getString("openid");
// AssertUtils.isNull(openId, ModuleErrorCode.AUTHORIZE_INVALID);
if (StringUtils.isBlank(openId)) {
logger.error("openId is null ...");
return null;
}
String sessionKey = sessionKeyOpenId.getString("session_key");
// 2、获取微信端用户信息
// TODO 手机号获取
TbWechatUserEntity userNew = buildWechatUserDO(dto, sessionKey, openId);
// TODO unionid为空时,表示没有授权 多个小程序统一登录时用
// if (StringUtils.isBlank(userNew.getUnionId())) {
// return null;
// }
// 单个小程序用
if (StringUtils.isBlank(userNew.getOpenId())) {
return null;
}
Map param = new HashMap<>();
// TODO 多个小程序统一登录时用
// param.put("unionid", userNew.getUnionId());
// 单个小程序用
param.put("unionid", userNew.getOpenId());
// 3、根据openId或unionId查询用户信息,判断是否已经注册/登录过。未登录过:插入用户信息 登录过:更新用户信息
TbWechatUserEntity userEntity = baseDao.getUserInfo(param);
if (userEntity == null) {
userNew.setToken(generateToken());
baseDao.insert(userNew);
} else {
String token = userEntity.getExpireDate().getTime() < System.currentTimeMillis() ? generateToken() : userEntity.getToken();
userNew.setToken(token);
userNew.setId(userEntity.getId());
baseDao.updateById(userNew);
}
// 4、redis中缓存token信息
JSONObject sessionObj = new JSONObject();
sessionObj.put("openId", openId);
sessionObj.put("sessionKey", sessionKey);
sessionObj.put("unionid", userNew.getUnionId());
tbWechatUserRedis.set(Constant.WECHAT_TOKEN_PREFIX + userNew.getToken(), sessionObj.toString(), EXPIRE);
// 5、返回前台token信息
userInfoMap.put("token", userNew.getToken());
return userInfoMap;
}
/**
* 调用微信接口,获取sessionKey、openId
* @param dto 前台获取到的微信用户数据
* @return
* @throws Exception
*/
private JSONObject getSessionKeyOrOpenId(WechatLoginRequestDTO dto) throws Exception {
Map param = new HashMap<>();
param.put("appName", dto.getAppName());
String appIdAndSecret = baseDao.getAppInfo(param);
Map requestUrlParam = new HashMap<>();
// 小程序appId,自己补充
requestUrlParam.put("appid", appIdAndSecret.substring(0, appIdAndSecret.indexOf("_")));
// 小程序secret,自己补充
requestUrlParam.put("secret", appIdAndSecret.substring(appIdAndSecret.indexOf("_") + 1));
// 小程序端返回的code
requestUrlParam.put("js_code", dto.getCode());
// 默认参数
requestUrlParam.put("grant_type", "authorization_code");
// 发送post请求读取调用微信接口获取openid用户唯一标识
String result = HttpClientUtils.doPost("https://api.weixin.qq.com/sns/jscode2session", requestUrlParam);
return JSON.parseObject(result);
}
/**
* 获取微信端用户信息
* @param dto 前台获取到的微信用户数据
* @param sessionKey sessionKey
* @param openId openId
* @return 用户信息
*/
private TbWechatUserEntity buildWechatUserDO(WechatLoginRequestDTO dto, String sessionKey, String openId){
TbWechatUserEntity wechatUserDO = new TbWechatUserEntity();
wechatUserDO.setOpenId(openId);
if (dto.getRawData() != null) {
RawDataDO rawDataDO = JSON.parseObject(dto.getRawData(), RawDataDO.class);
wechatUserDO.setNickname(rawDataDO.getNickName());
wechatUserDO.setAvatarUrl(rawDataDO.getAvatarUrl());
// 微信:值为1时是男性,值为2时是女性,值为0时是未知
// 系统: 0-男性、1-女性、2-保密
wechatUserDO.setGender(rawDataDO.getGender() - 1);
if (wechatUserDO.getGender() == -1) {
wechatUserDO.setGender(2);
}
wechatUserDO.setCity(rawDataDO.getCity());
wechatUserDO.setCountry(rawDataDO.getCountry());
wechatUserDO.setProvince(rawDataDO.getProvince());
}
// 解密加密信息,获取unionID
if (dto.getEncryptedData() != null){
JSONObject encryptedData = getEncryptedData(dto.getEncryptedData(), sessionKey, dto.getIv());
if (encryptedData != null){
// TODO 多个小程序时用
// String unionId = encryptedData.getString("unionId");
// 单个小程序用
String unionId = encryptedData.getString("openId");
wechatUserDO.setUnionId(unionId);
}
}
wechatUserDO.setUpdateDate(new Date());
wechatUserDO.setExpireDate(new Date(System.currentTimeMillis() + EXPIRE * 1000));
wechatUserDO.setUpdateDate(new Date());
return wechatUserDO;
}
/**
* 根据sessionkey、iv解密加密的微信用户信息,获取unionID
* @param encryptedData
* @param sessionkey
* @param iv
* @return
*/
private JSONObject getEncryptedData(String encryptedData, String sessionkey, String iv) {
Base64.Decoder decoder = Base64.getDecoder();
// 被加密的数据
byte[] dataByte = decoder.decode(encryptedData);
// 加密秘钥
byte[] keyByte = decoder.decode(sessionkey);
// 偏移量
byte[] ivByte = decoder.decode(iv);
try {
// 如果密钥不足16位,那么就补足.这个if中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + 1;
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, "UTF-8");
return JSONObject.parseObject(result);
}
} catch (Exception e) {
logger.error("解密加密信息报错", e.getMessage());
}
return null;
}
/**
* 生成token
* @return
*/
private String generateToken(){
return UUID.randomUUID().toString().replace("-", "");
}