Spring Boot+Vue前后端分离微信公众号网页授权解决方案

一、引言

全网最全的前后端分离微信网页授权解决方案。如果有更好的优化方案,欢迎多多交流

二、网页授权的步骤

  • 1 第一步:用户同意授权,获取code

  • 2 第二步:通过code换取网页授权access_token

  • 3 第三步:刷新access_token(如果需要)

  • 4 第四步:拉取用户信息(需scope为 snsapi_userinfo)

  • 5 附:检验授权凭证(access_token)是否有效

注意:这里的access_token属于网页授权access_token,而非普通授权的access_token,官方给出的解释如下:

关于网页授权access_token和普通access_token的区别 1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息; 2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。

但是没有讲得很明白。其实两者的区别就是:

  • 第一,网页授权access_token只要用户允许后就可以获取用户信息,可以不关注公众号,而普通access_token没有关注公众号,获取用户信息为空;

  • 第二,两者的每日限制调用频次不同,普通access_token每日2000次,获取网页授权access_token不限次数,获取用户信息每日5万次。

Spring Boot+Vue前后端分离微信公众号网页授权解决方案

 

三、后端接入

后端采用开源工具weixin-java-tools

Spring Boot+Vue前后端分离微信公众号网页授权解决方案

 

3.1 pom.xml引入jar包

<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>3.8.0</version></dependency>

3.2 application.yml添加配置

这里换成自己的appid和appsecret

# 微信公众号wechat:  mpAppId: appid  mpAppSecret: appsecret

3.3 新建读取配置文件WechatMpProperties.java

package com.hsc.power.dm.wechat.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * 微信公众号配置文件 * * @author liupan * @date 2020-05-26 */@Data@Component@ConfigurationProperties(prefix = "wechat")public class WechatMpProperties {    private String mpAppId;    private String mpAppSecret;}

3.4 新建自定义微信配置WechatMpConfig.java

package com.hsc.power.dm.wechat.config;import me.chanjar.weixin.mp.api.WxMpService;import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;import me.chanjar.weixin.mp.config.WxMpConfigStorage;import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.stereotype.Component;/** * 微信公众号配置 * * @author liupan * @date 2020-05-26 */@Componentpublic class WechatMpConfig {    @Autowired    private WechatMpProperties wechatMpProperties;    /**     * 配置WxMpService所需信息     *     * @return     */    @Bean  // 此注解指定在Spring容器启动时,就执行该方法并将该方法返回的对象交由Spring容器管理    public WxMpService wxMpService() {        WxMpService wxMpService = new WxMpServiceImpl();        // 设置配置信息的存储位置        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());        return wxMpService;    }    /**     * 配置appID和appsecret     *     * @return     */    @Bean    public WxMpConfigStorage wxMpConfigStorage() {        // 使用这个实现类则表示将配置信息存储在内存中        WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl();        wxMpDefaultConfig.setAppId(wechatMpProperties.getMpAppId());        wxMpDefaultConfig.setSecret(wechatMpProperties.getMpAppSecret());        return wxMpDefaultConfig;    }}

3.5 新建微信用户Bean

package com.hsc.power.dm.wechat.vo;import lombok.Data;import me.chanjar.weixin.mp.bean.result.WxMpUser;@Datapublic class WechatUser {    public WechatUser(WxMpUser wxMpUser, String accessToken) {        this.setAccessToken(accessToken);        this.setOpenid(wxMpUser.getOpenId());        this.setUnionId(wxMpUser.getUnionId());        this.setNickname(wxMpUser.getNickname());        this.setLanguage(wxMpUser.getLanguage());        this.setCountry(wxMpUser.getCountry());        this.setProvince(wxMpUser.getCity());        this.setCity(wxMpUser.getCity());        this.setSex(wxMpUser.getSex());        this.setSexDesc(wxMpUser.getSexDesc());        this.setHeadImgUrl(wxMpUser.getHeadImgUrl());    }    private String openid;    private String accessToken;    private String unionId;    private String nickname;    private String language;    private String country;    private String province;    private String city;    private Integer sex;    private String sexDesc;    private String headImgUrl;}

3.6 授权接口WechatController.java

  • /auth:获取授权跳转地址

  • /auth/user/info:初次授权获取用户信息

  • /token/user/info:静默授权获取用户信息

package com.hsc.power.dm.wechat.web;import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;import com.hsc.power.core.base.ret.Rb;import com.hsc.power.dm.wechat.vo.WechatUser;import lombok.extern.slf4j.Slf4j;import me.chanjar.weixin.common.api.WxConsts;import me.chanjar.weixin.common.error.WxErrorException;import me.chanjar.weixin.mp.api.WxMpService;import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;import me.chanjar.weixin.mp.bean.result.WxMpUser;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import java.net.URLEncoder;/** * 微信公众号接口 * * @author liupan * @date 2020-05-26 */@Slf4j@RestController@RequestMapping("/wechat")public class WechatController {    @Autowired    private WxMpService wxMpService;    /**     * 获取code参数     *     * @param returnUrl 需要跳转的url     * @return     */    @GetMapping("/auth")    public Rb<String> authorize(@RequestParam String authCallbackUrl, @RequestParam String returnUrl) {        // 暂时将我们的回调地址硬编码在这里,方便一会调试        // 获取微信返回的重定向url        String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(authCallbackUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl));        log.info("【微信网页授权】获取code,redirectUrl = {}", redirectUrl);        return Rb.ok(redirectUrl);    }    /**     * 初次授权获取用户信息     *     * @param code     * @param returnUrl     * @return     */    @GetMapping("/auth/user/info")    public Rb<WechatUser> userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) {        WxMpOAuth2AccessToken wxMpOAuth2AccessToken;        WxMpUser wxMpUser;        try {            // 使用code换取access_token信息            wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);            wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);        } catch (WxErrorException e) {            log.error("【微信网页授权】异常,{}", e);            throw ExceptionUtils.mpe(e.getError().getErrorMsg());        }        // 从access_token信息中获取到用户的openid        String openId = wxMpOAuth2AccessToken.getOpenId();        log.info("【微信网页授权】获取openId,openId = {}", openId);        WechatUser wechatUser = new WechatUser(wxMpUser, wxMpOAuth2AccessToken.getAccessToken());        return Rb.ok(wechatUser);    }    /**     * 静默授权获取用户信息,判断accessToken是否失效,失效即刷新accecssToken     * @param openid     * @param token     * @return     */    @GetMapping("/token/user/info")    public Rb<WechatUser> getUserInfo(@RequestParam String openid, @RequestParam String token) {        WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();        wxMpOAuth2AccessToken.setOpenId(openid);        wxMpOAuth2AccessToken.setAccessToken(token);        boolean ret = wxMpService.oauth2validateAccessToken(wxMpOAuth2AccessToken);        if (!ret) {            // 已经失效            try {                // 刷新accessToken                wxMpOAuth2AccessToken = wxMpService.oauth2refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken());            } catch (WxErrorException e) {                log.error("【微信网页授权】刷新token失败,{}", e.getError().getErrorMsg());                throw ExceptionUtils.mpe(e.getError().getErrorMsg());            }        }        // 获取用户信息        try {            WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);            WechatUser wechatUser = new WechatUser(wxMpUser, wxMpOAuth2AccessToken.getAccessToken());            return Rb.ok(wechatUser);        } catch (WxErrorException e) {            log.error("【微信网页授权】获取用户信息失败,{}", e.getError().getErrorMsg());            throw ExceptionUtils.mpe(e.getError().getErrorMsg());        }    }}

四、前端接入

4.1 路由拦截

noAuth配置是否需要授权页面

router.beforeEach((to, from, next) => {  // 微信公众号授权  if (!to.meta.noAuth) {    // 路由需要授权    if (_.isEmpty(store.getters.wechatUserInfo)) {      // 获取用户信息      if (        !_.isEmpty(store.getters.openid) &&        !_.isEmpty(store.getters.accessToken)      ) {        // 存在openid和accessToken,已经授过权        // 判断accessToken是否过期,过期刷新token,获取用户信息        store.dispatch('getUserInfo')        next()      } else {        // todo 跳转网页授权        // 记录当前页面url        localStorage.setItem('currentUrl', to.fullPath)        next({name: 'auth'})      }    } else {      // todo 已经存在用户信息,需要定期更新      next()    }  } else {    // 路由不需要授权    next()  }})

4.2 授权页面

{  path: '/auth',  name: 'auth',  component: resolve => {    require(['@/views/auth/index.vue'], resolve)  },  meta: {    noAuth: true  }},
<template></template><script>import config from '@/config'import WechatService from '@/api/wechat'export default {  mounted() {    WechatService.auth(config.WechatAuthCallbackUrl).then(res => {      if (res.ok()) {		// 获取授权页面后直接进行跳转        window.location.href = res.data      }    })  }}</script>

4.3 授权store

在vuex中进行授权和存储用户信息

import _ from 'lodash'import WechatService from '@/api/wechat'import localStorageUtil from '@/utils/LocalStorageUtil'export default {  state: {    unionId: '',    openid: '',    accessToken: '',    wechatUserInfo: {}  },  getters: {    unionId: state => {      return state.unionId || localStorageUtil.get('unionId')    },    openid: state => {      return state.openid || localStorageUtil.get('openid')    },    accessToken: state => {      return state.accessToken || localStorageUtil.get('accessToken')    },    wechatUserInfo: state => {      return state.wechatUserInfo || localStorageUtil.get('wechatUserInfo')    }  },  mutations: {    saveWechatUserInfo: (state, res) => {      state.wechatUserInfo = res      // todo 保存到storage,设置一定日期,定期更新      state.unionId = res.unionId      state.openid = res.openid      state.accessToken = res.accessToken      localStorageUtil.set('unionId', res.unionId)      localStorageUtil.set('openid', res.openid)      localStorageUtil.set('accessToken', res.accessToken)      // 保存userInfo,设置有效时间,默认30天      localStorageUtil.set('wechatUserInfo', res, 30)    }  },  actions: {    // 静默授权获取用户信息    async getUserInfo({ commit, getters }) {      const openid = getters.openid      const token = getters.accessToken      if (!_.isEmpty(openid) && !_.isEmpty(token)) {        // 存在openid和accessToken,已经授过权        // 判断accessToken是否过期,过期刷新token,获取用户信息        const res = await WechatService.getUserInfo(openid, token)        if (res.ok()) {          // todo 判断res.data是否有误          commit('saveWechatUserInfo', res.data)        }      }    },    // 初次授权获取用户信息    async getAuthUserInfo({ commit }, { code, state }) {      if (!_.isEmpty(code) && !_.isEmpty(state)) {        const res = await WechatService.getAuthUserInfo(code, state)        if (res.ok()) {          commit('saveWechatUserInfo', res.data)        }      }    }  }}

4.4 自定义存储工具localStorageUtil.js

localStorageUtil.js:用于设置保存有效期

在这里,用户信息设置保存30天,根据前面4.1路由拦截判断,用户信息过期,需要重新进行授权认证。感觉这种方式不太好,但是获取用户信息每月限制5万次,不想每次都去调用接口获取用户信息,这里有更好的方案吗?

import _ from 'lodash'import moment from 'moment'export default {  /**   * 获取session-storage 中的值   * @param {*} key   * @param {*} defaultValue   */  get(key, defaultValue) {    return this.parse(key, defaultValue)  },  /**   * 放入 session-storage 中,自动字符串化 obj   * @param {*} key   * @param {*} obj   * @param {Integer} expires 过期时间:天   */  set(key, obj, expires) {    if (expires) {      const tmpTime = moment()        .add(expires, 'days')        .format('YYYY-MM-DD')      const handleObj = { expires: tmpTime, value: obj }      localStorage.setItem(key, JSON.stringify(handleObj))    } else {      if (_.isObject(obj)) {        localStorage.setItem(key, JSON.stringify(obj))      } else {        localStorage.setItem(key, obj)      }    }  },  /**   * 从 session-storage 中移除key   * @param {*} key   */  remove(key) {    localStorage.removeItem(key)  },  /**   * 从 session-storage 取出key并将值对象化   * @param {*} key   * @param {*} defaultValue   */  parse(key, defaultValue) {    let value = localStorage.getItem(key)    if (_.isObject(value)) {      const valueObj = JSON.parse(value)      if (valueObj.expires) {        // 有过期时间,判断是否过期:在现在时间之前,过期        if (moment(valueObj.expires).isBefore(moment(), 'day')) {          // 删除          this.remove(key)          // 直接返回          return null        }        return valueObj.value      }      // 没有过期时间直接返回对象      return valueObj    }    // 不是对象,返回值    return value || defaultValue  }}

至此大功告成,在微信开发者工具中即可获取用户信息,亲测有效。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 基于 Spring Boot,MyBatis Plus和Vue的开发框架是现代Web应用开发的主要选择之一。Spring Boot是一个轻量级的Java开发框架,提供了快速构建独立应用程序所需的所有功能。它简化了传统的Spring应用程序开发过程,让开发者能够更专注于业务逻辑。 MyBatis Plus是一个对MyBatis进行封装的工具,提供了更简单、更方便的方式来操作数据库。它使用注解和代码生成器来减少开发时间,使得数据库操作更加高效和可维护。 Vue是一个流行的JavaScript框架,用于构建用户界面。它的特点是轻量级、易于学习和使用,并且具有非常高的性能。Vue可以与服务器端的后端框架(如Spring Boot)无缝集成,在前后端分离项目中,提供了极佳的开发体验。 使用基于Spring Boot,MyBatis Plus和Vue的开发框架,开发者可以快速搭建一个完整的Web应用。首先,使用Spring Boot来创建后端应用,配置数据源和集成MyBatis Plus以简化数据库操作。然后,使用Vue来创建用户界面,通过REST API与后端进行通信。 这个开发框架的优点是集成了强大的Java后端和灵活的JavaScript前端,开发者可以使用多种技术栈来完成各种功能。同时,Spring Boot提供了很好的项目结构和配置管理,MyBatis Plus简化了数据库操作,Vue提供了丰富的界面组件和开发工具,整合后的开发框架提供了高效、可维护和可扩展的开发环境。 总之,基于Spring Boot,MyBatis Plus和Vue的开发框架提供了一种现代化、高效和灵活的Web应用开发方式。它适用于各种规模的项目,可以提高开发效率,降低维护成本,并且提供良好的开发体验。 ### 回答2: 基于Spring Boot、MyBatis Plus和Vue框架的开发可以实现一个完整的前后端分离的应用。以下是具体方面的解释: 1. Spring Boot是一个快速开发的Java框架,可以轻松搭建后端服务器。它提供了自动配置、快速开发等功能,大大简化了Java后端开发的步骤。 2. MyBatis Plus是一个基于MyBatis的增强工具,可以简化与数据库的交互。它提供了更简单的API、更强大的查询功能和代码生成等特性,可以极大提高后端开发的效率。 3. Vue是一个流行的前端框架,用于构建用户界面。它具有简单易学、灵活和高性能等特点,可以帮助我们编写交互式、响应式的前端应用。 基于这三个框架的组合,可以实现前后端完全分离的开发模式。后端使用Spring Boot搭建服务器,处理业务逻辑,并与数据库进行交互。MyBatis Plus可以简化与数据库的交互,提供了更简单的CRUD操作和强大的查询功能。 前端使用Vue进行开发,可以实现灵活的用户界面。通过Restful API与后端进行通信,实现数据的交互。 总而言之,基于Spring Boot、MyBatis Plus和Vue的开发模式,可以高效地实现前后端分离的应用。后端使用Spring Boot提供服务,MyBatis Plus简化数据库交互,前端使用Vue构建用户界面。这种组合可以提高开发效率,让开发人员更专注于业务逻辑的实现。 ### 回答3: 基于Spring Boot,MyBatis Plus和Vue的开发,可以实现一个全栈的Web应用程序。 Spring Boot是一个基于Spring框架的快速开发工具,它简化了Spring项目的配置,提供了一套约定大于配置的开发模式。使用Spring Boot可以快速搭建一个Web应用程序的后端,处理数据层和业务逻辑。 MyBatis Plus是基于MyBatis框架之上的增强工具,它进一步简化了在Java应用程序中操作数据库的流程。MyBatis Plus提供了很多常用的功能和特性,如代码生成器、自动填充、分页插件等,可以极大地提高开发效率。 Vue是一种现代化的JavaScript框架,用于构建用户界面。它提供了一套响应式组件系统和强大的工具集,可以帮助开发者快速构建单页应用程序。Vue可以与其他后端技术,如Spring Boot和MyBatis Plus一起使用,实现前后端分离的开发模式。 在基于这些技术的开发过程中,可以将后端的数据处理和业务逻辑实现放在Spring Boot中,使用MyBatis Plus进行数据库操作。前端使用Vue构建用户界面,并通过异步请求与后端进行数据交互。通过这种方式,可以实现一个功能完善的全栈Web应用程序。 使用Spring Boot和MyBatis Plus可以快速搭建后端框架,并提供强大的数据库操作能力。而Vue作为前端框架,可以提供友好的用户界面和交互体验。通过这些技术的结合,可以实现高效、灵活的全栈开发,为用户提供优质的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值