微信登录

微信小程序登录,单个程序用openId,多个小程序统一登录时需要用unionId

代码逻辑

  1. 前台获取微信授权用户信息,及会话code
  2. 设置用户信息

    1. 根据前台rawData设置用户基本信息
    2. 根据code、appid、secret获取sessionKey。根据sessionkey、vi(解密向量)解析前台数据encryptedData中的unionid
  3. 根据openId或unionId查询用户信息,判断是否已经注册/登录过。

    1. 未登录:插入用户信息
    2. 登录过:更新用户信息
  4. redis中缓存token信息
  5. 返回前台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("-", "");
    }

你可能感兴趣的:(java,后端)