• 尚医通_第14章_微信登录


    尚医通_第14章_微信登录

    第一节、生成微信登录二维码

    一、准备工作

    https://open.weixin.qq.com

    1、注册

    2、邮箱激活

    3、完善开发者资料

    4、开发者资质认证

    准备营业执照,1-2个工作日审批、300元

    5、创建网站应用

    提交审核,7个工作日审批

    6、熟悉微信登录流程

    参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

    获取access_token时序图

    在这里插入图片描述

    二、后端开发service_user

    说明:微信登录二维码我们是以弹出层的形式打开,不是以页面形式,所以做法是不一样的,参考如下链接,上面有相关弹出层的方式

    https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

    如图:

    在这里插入图片描述

    因此我们的操作步骤为:

    第一步我们通过接口把对应参数返回页面;

    第二步在头部页面启动打开微信登录二维码;

    第三步处理登录回调接口;

    第四步回调返回页面通知微信登录层回调成功

    第五步如果是第一次扫描登录,则绑定手机号码,登录成功

    接下来我们根据步骤,一步一步实现

    1、添加配置

    application.properties添加相关配置信息

    # 微信开放平台 appid
    wx.open.app_id=你的appid
    # 微信开放平台 appsecret
    wx.open.app_secret=你的appsecret
    # 微信开放平台 重定向url
    wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    引入依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2、创建常量类

    创建util包,创建ConstantPropertiesUtil.java常量类

    @Component
    //@PropertySource("classpath:application.properties")
    public class ConstantPropertiesUtil implements InitializingBean {
        @Value("${wx.open.app_id}")
        private String appId;
        @Value("${wx.open.app_secret}")
        private String appSecret;
        @Value("${wx.open.redirect_url}")
        private String redirectUrl;
        public static String WX_OPEN_APP_ID;
        public static String WX_OPEN_APP_SECRET;
        public static String WX_OPEN_REDIRECT_URL;
        @Override
        public void afterPropertiesSet() throws Exception {
            WX_OPEN_APP_ID = appId;
            WX_OPEN_APP_SECRET = appSecret;
            WX_OPEN_REDIRECT_URL = redirectUrl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    3、创建controller

    service_user微服务中创建api包

    api包中创建WxApiController,返回微信参数

    @Controller
    @RequestMapping("/api/ucenter/wx")
    public class WeixinApiController {
        @Autowired
        private UserInfoService userInfoService;
        @Autowired
        private RedisTemplate redisTemplate;
        /**
         * 获取微信登录参数
         */
        @GetMapping("getLoginParam")
        @ResponseBody
        public R genQrConnect(HttpSession session) throws UnsupportedEncodingException {
            String redirectUri = URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "UTF-8");
            Map<String, Object> map = new HashMap<>();
            map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
            map.put("redirectUri", redirectUri);
            map.put("scope", "snsapi_login");
            map.put("state", System.currentTimeMillis()+"");//System.currentTimeMillis()+""
            return R.ok().data(map);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    授权url参数说明

    参数是否必须说明
    appid应用唯一标识
    redirect_uri请使用urlEncode对链接进行处理
    response_type填code
    scope应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即
    state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

    三、前端显示登录二维码

    1、封装api请求

    创建api/yygh/wx.js

    import request from '@/utils/request'
    const api_name = `/api/ucenter/wx`
    export default {
      getLoginParam() {
        return request({
          url: `${api_name}/getLoginParam`,
          method: `get`
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2、修改组件

    修改layouts/myheader.vue文件,添加微信二维码登录逻辑

    // 1、引入api
    import weixinApi from '@/api/yygh/wx'
    //2、引入微信js
    mounted() {
        // 注册全局登录事件对象
        window.loginEvent = new Vue();
        // 监听登录事件
        loginEvent.$on('loginDialogEvent', function () {
            document.getElementById("loginDialog").click();
        })
        // 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
        //初始化微信js
        const script = document.createElement('script')
        script.type = 'text/javascript'
        script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
        document.body.appendChild(script)
        // 微信登录回调处理
        let self = this;
        window["loginCallback"] = (name,token, openid) => {
            self.loginCallback(name, token, openid);
        }
    },
    //3、实例化微信JS对象
    //添加微信登录方法
    loginCallback(name, token, openid) {
        // 打开手机登录层,绑定手机号,改逻辑与手机登录一致
        if(openid != '') {
           this.userInfo.openid = openid
           this.showLogin()
        } else {
           this.setCookies(name, token)
        }
    },
    weixinLogin() {
      this.dialogAtrr.showLoginType = 'weixin'
      weixinApi.getLoginParam().then(response => {
        var obj = new WxLogin({
          self_redirect:true,
          id: 'weixinLogin', // 需要显示的容器id
          appid: response.data.appid, // 公众号appid wx*******
          scope: response.data.scope, // 网页默认即可
          redirect_uri: response.data.redirectUri, // 授权成功后回调的url
          state: response.data.state, // 可设置为简单的随机数加session用来校验
          style: 'black', // 提供"black"、"white"可选。二维码的样式
          href: '' // 外部css文件url,需要https
        })
      })
    },
    //说明:微信登录方法已绑定weixinLogin(),查看页面
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    第二节、开发微信扫描回调

    一、准备工作

    1、全局配置的跳转路径
    # 微信开放平台 重定向url
    wx.open.redirect_url=http://回调地址/api/ucenter/wx/callback
    
    • 1
    • 2
    2、修改当前项目启动端口号为8160
    3、测试回调是否可用

    在WxApiController中添加方法

    @GetMapping("callback")
    public String callback(String code, String state, HttpSession session) {
        //得到授权临时票据code
        System.out.println("code = " + code);
        System.out.println("state = " + state);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、后台开发

    1、添加依赖
     
    <dependency>
        <groupId>org.apache.httpcomponentsgroupId>
        <artifactId>httpclientartifactId>
    dependency>
    
    <dependency>
        <groupId>com.google.code.gsongroupId>
        <artifactId>gsonartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2、创建httpclient工具类

    放入util包

    HttpClientUtils.java
    
    • 1
    3、创建回调controller方法

    在WxApiController.java中添加如下方法

    @GetMapping("callback")
    public String callback(String code, String state, HttpSession session) {
        //第一步 获取临时票据 code
        System.out.println("code:"+code);
        //第二步 拿着code和微信id和秘钥,请求微信固定地址 ,得到两个值
        //使用code和appid以及appscrect换取access_token
        //  %s   占位符
        StringBuffer baseAccessTokenUrl = new StringBuffer()
            .append("https://api.weixin.qq.com/sns/oauth2/access_token")
            .append("?appid=%s")
            .append("&secret=%s")
            .append("&code=%s")
            .append("&grant_type=authorization_code");
        String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
                                              ConstantPropertiesUtil.WX_OPEN_APP_ID,
                                              ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
                                              code);
        //使用httpclient请求这个地址
        try {
            String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accesstokenInfo:"+accesstokenInfo);
            //从返回字符串获取两个值 openid  和  access_token
            JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
            String access_token = jsonObject.getString("access_token");
            String openid = jsonObject.getString("openid");
            //判断数据库是否存在微信的扫描人信息
            //根据openid判断
            UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
            if(userInfo == null) { //数据库不存在微信信息
                //第三步 拿着openid  和  access_token请求微信地址,得到扫描人信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
                String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
                String resultInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println("resultInfo:"+resultInfo);
                JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
                //解析用户信息
                //用户昵称
                String nickname = resultUserInfoJson.getString("nickname");
                //用户头像
                String headimgurl = resultUserInfoJson.getString("headimgurl");
                //获取扫描人信息添加数据库
                userInfo = new UserInfo();
                userInfo.setNickName(nickname);
                userInfo.setOpenid(openid);
                userInfo.setStatus(1);
                userInfoService.save(userInfo);
            }
            //返回name和token字符串
            Map<String,String> map = new HashMap<>();
            String name = userInfo.getName();
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getNickName();
            }
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getPhone();
            }
            map.put("name", name);
            //判断userInfo是否有手机号,如果手机号为空,返回openid
            //如果手机号不为空,返回openid值是空字符串
            //前端判断:如果openid不为空,绑定手机号,如果openid为空,不需要绑定手机号
            if(StringUtils.isEmpty(userInfo.getPhone())) {
                map.put("openid", userInfo.getOpenid());
            } else {
                map.put("openid", "");
            }
            //使用jwt生成token字符串
            String token = JwtHelper.createToken(userInfo.getId(), name);
            map.put("token", token);
            //跳转到前端页面
            return "redirect:http://localhost:3000/weixin/callback?token="+map.get("token")+ "&openid="+map.get("openid")+"&name="+URLEncoder.encode(map.get("name"),"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    4、创建UserInfoService方法

    根据openid查询用户信息

    //定义方法
    UserInfo selectWxInfoOpenId(String openid);
    //实现方法
    @Override
    public UserInfo selectWxInfoOpenId(String openid) {
        return baseMapper.selectOne(new QueryWrapper<UserInfo>().eq("openid", openid));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    5、创建UserInfoServiceImpl的登录方法

    绑定手机号操作

    //登录接口
    @Override
    public Map<String, Object> login(LoginVo loginVo) {
        //1 获取输入手机号和验证码
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        //2 手机号和验证码非空校验
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new YyghException(20001,"数据为空");
        }
        //验证码校验
        // 输入的验证码 和 存储redis验证码比对
        String redisCode = redisTemplate.opsForValue().get(phone);
        if(!redisCode.equals(code)) {
            throw new YyghException(20001,"验证码校验失败");
        }
        String openid = loginVo.getOpenid();
        Map<String, Object> map = new HashMap<>();
        if(StringUtils.isEmpty(openid)) {
            //根据手机查询数据库
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
            wrapper.eq("phone",phone);
            UserInfo userInfo = baseMapper.selectOne(wrapper);
            //如果返回对象为空,就是第一次登录,存到数据库登录数据
            if(userInfo == null) {
                userInfo = new UserInfo();
                userInfo.setName("");
                userInfo.setPhone(phone);
                userInfo.setStatus(1);
                baseMapper.insert(userInfo);
            }
            //判断用户是否可用
            if(userInfo.getStatus() == 0) {
                throw new YyghException(20001,"用户已经禁用");
            }
            map = get(userInfo);
        } else {
            //1 创建userInfo对象,用于存在最终所有数据
            UserInfo userInfoFinal = new UserInfo();
            //2 根据手机查询数据
            // 如果查询手机号对应数据,封装到userInfoFinal
            UserInfo userInfoPhone =
                baseMapper.selectOne(new QueryWrapper<UserInfo>().eq("phone", phone));
            if(userInfoPhone != null) {
                // 如果查询手机号对应数据,封装到userInfoFinal
                BeanUtils.copyProperties(userInfoPhone,userInfoFinal);
                //把手机号数据删除
                baseMapper.delete(new QueryWrapper<UserInfo>().eq("phone", phone));
            }
            //3 根据openid查询微信信息
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
            wrapper.eq("openid",openid);
            UserInfo userInfoWX = baseMapper.selectOne(wrapper);
            //4 把微信信息封装userInfoFinal
            userInfoFinal.setOpenid(userInfoWX.getOpenid());
            userInfoFinal.setNickName(userInfoWX.getNickName());
            userInfoFinal.setId(userInfoWX.getId());
            //数据库表没有相同绑定手机号,设置值
            if(userInfoPhone == null) {
                userInfoFinal.setPhone(phone);
                userInfoFinal.setStatus(userInfoWX.getStatus());
            }
            //修改手机号
            baseMapper.updateById(userInfoFinal);
            //5 判断用户是否锁定
            if(userInfoFinal.getStatus() == 0) {
                throw new YyghException(20001,"用户被锁定");
            }
            //6 登录后,返回登录数据
            map = get(userInfoFinal);
        }
        return map;
    }
    private Map<String,Object> get(UserInfo userInfo) {
        //返回页面显示名称
        Map<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name", name);
        //根据userid和name生成token字符串
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("token", token);
        return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    6、回调返回页面

    说明:我们只期望返回一个空页面,然后跟登录层通信就可以了,其实就是一个过渡页面,所以我们要给这个过渡页面定义一个空模板

    (1)添加空模板组件:/layouts/empty.vue

    <template>
    <div>
    <nuxt/>
    div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)创建回调返回页面 /pages/weixin/callback.vue

    <template>
      
      <div>
      div>
      
    template>
    <script>
    export default {
      layout: "empty",
      data() {
        return {
        }
      },
      mounted() {
        let token = this.$route.query.token
        let name = this.$route.query.name
        let openid = this.$route.query.openid
        // 调用父vue方法
        window.parent['loginCallback'](name, token, openid)
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (3)在myheader.vue添加方法

    mounted() {
        // 注册全局登录事件对象
        window.loginEvent = new Vue();
        // 监听登录事件
        loginEvent.$on('loginDialogEvent', function () {
            document.getElementById("loginDialog").click();
        })
        // 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
        //初始化微信js
        const script = document.createElement('script')
        script.type = 'text/javascript'
        script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
        document.body.appendChild(script)
        // 微信登录回调处理
        let self = this;
        window["loginCallback"] = (name,token, openid) => {
            self.loginCallback(name, token, openid);
        }
    },
        
    loginCallback(name, token, openid) {
          // 打开手机登录层,绑定手机号,改逻辑与手机登录一致
          if(openid != '') {
            this.userInfo.openid = openid
            this.showLogin()
          } else {
            this.setCookies(name, token)
          }
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    7、预约挂号页面调整

    修改/pages/hospital/_hoscode.vue组件

    (1)引入cookie

    import cookie from 'js-cookie'
    
    • 1

    (2)修改方法

    schedule(depcode) {
      // 登录判断
      let token = cookie.get('token')
      if (!token) {
        loginEvent.$emit('loginDialogEvent')
        return
      }
      window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    逻辑与手机登录一致
    if(openid != ‘’) {
    this.userInfo.openid = openid
    this.showLogin()
    } else {
    this.setCookies(name, token)
    }
    },

    
    **7、预约挂号页面调整**
    
    修改/pages/hospital/_hoscode.vue组件
    
    **(1)引入cookie**
    
     
    
    ```javascript
    import cookie from 'js-cookie'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)修改方法

    schedule(depcode) {
      // 登录判断
      let token = cookie.get('token')
      if (!token) {
        loginEvent.$emit('loginDialogEvent')
        return
      }
      window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    【操作系统】I/O 管理(一)—— I/O 管理概述
    猿创征文|MYSQL主从复制
    Java进阶之多线程
    熵增定律与软件的熵
    团队管理之高效开发基础认知
    如何配置一台适合oc渲染器的电脑?
    PHP(3)PHP基础语法
    conan入门(二十七):因profile [env]字段废弃导致的boost/1.81.0 在aarch64-linux-gnu下交叉编译失败
    C++类模板实例化与专门化
    新媒体运营-定位及呈现方式篇
  • 原文地址:https://blog.csdn.net/guan1843036360/article/details/127856829