微信登录

67次阅读

共计 6802 个字符,预计需要花费 18 分钟才能阅读完成。

微信小程序登录,单个程序用 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<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("-", "");
    }

正文完
 0