共计 6802 个字符,预计需要花费 18 分钟才能阅读完成。
微信小程序登录,单个程序用 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<String, Object> 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<String, Object> getUserInfoMap(WechatLoginRequestDTO dto) throws Exception {Map<String, Object> 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<String, Object> 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<String, String> param = new HashMap<>();
param.put("appName", dto.getAppName());
String appIdAndSecret = baseDao.getAppInfo(param);
Map<String, String> 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("-", "");
}
正文完