vue+spring boot项目实现PC端微信登录

一、实现微信登录在编码前需要在微信开放平台注册开发者账号和安装NatApp获取域名。
微信开放平台注册开发者账号可参考:https://blog.csdn.net/weixin_45001200/article/details/119495080?spm=1001.2014.3001.5502
安装NatApp获取域名可参考:https://blog.csdn.net/weixin_45001200/article/details/119495198?spm=1001.2014.3001.5502

二、微信登录步骤:
1、点击登录页面的微信注册登录
2、页面跳转到微信登录的页面,后台以appid和appsecret为参数调用微信api获取access_token
3、以access_token为参数调用微信api获取本地微信登录二维码ticket,同时传随机字符串scene_str,该参与可用于程序验证是否扫码成功
4、通过ticket获取二维码成功,页面显示,同时前台开始进行js定时任务,监听是否扫码成功
5、用户微信扫码成功,如果是第一次微信扫码,则弹出微信注册的框用户绑定手机号和密码进行注册
6、微信服务器端回调本地服务器签名验证接口验证签名和回调处理
7、回调处理确定扫码成功,缓存插入本地扫码成功记录
8、前台定时任务获取到扫码成功记录或者后台主动通知,进行扫码成功后业务处理。

三、前端Vue代码实现
登录页面的代码:

//login.vue中的代码
<template>
    <router-link type="info" to='/loginWX'>微信注册登录</router-link>
</template>

登录页面的效果如下的红色框:
在这里插入图片描述
微信登录页面的代码:

//loginWX.vue中的代码
<template>
    <el-form class="div1">
      <p>请扫描二维码</p>
      <img v-bind:src="qrCodeImgUrl" v-show="qrCodeImgFlag" class="div3">
    </el-form>
    <el-form class="div2" v-show="registerFlag">
      <p class="div02">微信绑定</p>
        <el-input class="div12" type="text"  v-model="phone" placeholder="手机号"></el-input>
        <el-input class="div22" type="password"  v-model="password" placeholder="密码"></el-input>
        <el-button class="div32" type="primary" style="width:48%;" @click.native.prevent="register" >提 交</el-button>
    </el-form>
</template>    

1)mounted()方法中调用了this.getQrCode(),在页面一加载进来的时候,就自动执行this.getQrCode()获取二维码的方法,二维码生成成功后,调用扫描二维码的方法。
2)微信扫描二维码的方法getOpenId1()设置了一个定时器,未扫码的时候getOpenId1()一直调用,当扫码完成之后定时器清除,getOpenId1()方法停止被调用。
3)微信扫描二维码即微信登录,扫描二维码获取用户微信的openId,在后台进行判断,如果没有该用户的openId,则弹出微信绑定的框,进行微信绑定,绑定成功后,直接跳到系统的主页面完成登录。如果存在该用户的openId,则直接跳到系统的主页面完成登录。

<script>
  export default {
    name: "LoginWX",
    data(){
      return{
        qrCodeImgUrl: '',
        qrCodeImgFlag: false,
        registerFlag: false,
        timer: '',
        phone: '',
        password: '',
        openId1: ''
      }
    },
    methods: {
      //微信扫描二维码 
      getOpenId1: function (x) {
        this.timer = setInterval(() => {
          let userInfo = {eventKey: x.sceneStr}
          this.$api.login.getOpenId(JSON.stringify(userInfo)).then((res) => {
            if (res.code == 200) {
              clearInterval(this.timer)
              let userInfo1 = {openId: res.data.openId}
              this.$api.login.wXLogin(JSON.stringify(userInfo1)).then((res) => {
                if (res.code == 200) {
                  console.log("微信登录成功!");
                  sessionStorage.setItem('userInfo', JSON.stringify(res.data)) // 保存用户到本地会话
                  sessionStorage.setItem('token', res.data.token) // 保存用户到本地会话
                  this.$router.push('/SelectRole')
                }else if(res.code==300){
                  //注册微信
                  this.registerFlag = true;
                  this.register(res.data);
                }
              }).catch(function (res) {
                //alert(res);
                //this.$message({message: '微信登录失败, ' + res.msg, type: 'error'})
              });
            }
          }).catch(function (res) {
            //alert(res);
          });
        }, 3000)
      },

      // 获取登录二维码
      getQrCode: function () {
        this.$api.login.getQrCode().then((res) => {
          console.log("getQrCode的res.data: " + res.data);
          if (res.code == 200) {
            console.log("生成二维码成功!")
            this.qrCodeImgUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + res.data.ticket;
            this.qrCodeImgFlag = true;
            this.getOpenId1(res.data);
          } else {
            // console.log("生成二维码失败!");
          }
        }).catch(function (res) {
          alert(res);
        });
      },
      //微信绑定
      register: function(x){
          if(x.openId!=null){
            this.openId1 = x.openId
          }
          if(this.phone!=null&&this.password!=null&&this.openId1!=null){
            clearInterval(this.timer)
            let userInfo = {phone: this.phone,password:this.password,openId:this.openId1}
            this.$api.login.wxRegister(JSON.stringify(userInfo)).then((res) => {
              if (res.code == 200) {
                this.$message({message: '微信注册成功! '})
                this.$router.push('/SelectRole')
              } else {
                // this.$message({message: '微信注册失败! '})
              }
            }).catch(function (res) {
              alert(res);
            });
          }
      }
    },
    mounted()
    {
      console.log('come in loginWX vue.....');
      this.getQrCode()
    }
  }
</script>

微信登录页面效果如下:
在这里插入图片描述
四、spring boot后端代码实现
LoginController层次实现了微信登录和微信注册功能

/**
 *
 * @author ztt
 * @param sysUserNewVo
 * @return com.qcby.device.manage.util.WXResultJson
 * @description: 入参:openId phone password
 */
@RequestMapping("wxRegister")
public WXResultJson wxRegister(@RequestBody SysUserNewVo sysUserNewVo){
    log.info("wXRegister入参openId:"+sysUserNewVo.getOpenId());
    log.info("wXRegister入参phone+password:"+sysUserNewVo.getPhone()+";"+sysUserNewVo.getPassword());
    Boolean x = loginService.wxRegister(sysUserNewVo);
    if(x){
        return WXResultJson.ok("微信注册成功!");
    }
    return WXResultJson.error("微信注册失败!");
}
/**
 * @author ztt
 * @param
 * @return com.qcby.teaching.msgmanage.common.web.ResultJson
 * @description: 微信扫码登录, 入参openId
 */
@RequestMapping("wXLogin")
public WXResultJson wXLogin(@RequestBody WxPcLoginKey wxPcLoginKey){
    String openId = wxPcLoginKey.getOpenId();
    log.info("wXLogin入参openId:"+openId);
    SysUserNewVo sysUserNewVo = loginService.getUserInfoByOpenId(openId);
    if(sysUserNewVo==null){
        SysUserNewVo sysUserNewVo1 = new SysUserNewVo();
        sysUserNewVo1.setOpenId(openId);
        return WXResultJson.error("请注册微信!",sysUserNewVo1);
    }
    log.info("微信登录成功!");
    String token = JwtUtil.createToken(sysUserNewVo.getAccount());
    sysUserNewVo.setToken(token);
    List<SysRole> roleList = sysRoleService.getRoleById(sysUserNewVo.getId());
    sysUserNewVo.setRoleList(roleList);
    redisUtil.set("SysUser",JSON.toJSONString(sysUserNewVo));
    SysUserNewVo xxx = JSON.parseObject(String.valueOf(redisUtil.get("SysUser")), SysUserNewVo.class);
    redisUtil.set("sysUserAccount",sysUserNewVo.getAccount());
    return WXResultJson.ok(sysUserNewVo);
}

RestWxPcLoginController层实现了获取登录二维码、 获取accessToken、验证签名、回调方法和获取用户openId方法。
这里需要注意的一个点是:一个系统有多个用户进行微信登录,而eventKey作为每个用户是否扫码成功的验证,因此为了区分不同的用户登录,需要将其key值即eventKey以及对应用户的openId即value值存到map中。

@Slf4j
@RestController
@RequestMapping(GlobalConstant.REST_URL_PREFIX +"/qrCodeFirstLogin")
public class RestWxPcLoginController {

    @Autowired
    private WxConfig wxConfig;

    @Autowired
    private LoginService loginService;
    
    // 模拟数据库存储或者缓存存储
    Map<String, WxPcLoginKey> loginMap = new ConcurrentHashMap<>(64);

    /**
     *  获取登录二维码
     * @return
     */
    @GetMapping("/getQrCode")
    private WXResultJson getQrCode(){
        log.info("getQrCode入参!");
        try {
            // 获取token开发者
            String accessToken =getAccessToken();
            String getQrCodeUrl = wxConfig.getQrCodeUrl().replace("TOKEN", accessToken);
            // 这里生成一个带参数的二维码,参数是scene_str
            String sceneStr = CodeLoginUtil.getRandomString(8);
            String json="{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\"" +", \"action_info\": {\"scene\": {\"scene_str\": \""+sceneStr+"\"}}}";
            String result  = HttpClientUtil.doPostJson(getQrCodeUrl,json);

            JSONObject jsonObject = JSONObject.parseObject(result);
            jsonObject.put("sceneStr",sceneStr);
            log.info("getQrCode出参成功: "+jsonObject);
            return WXResultJson.ok(jsonObject);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("getQrCode出参失败!");
            return WXResultJson.error(e.getMessage());
        }
    }
    /**
     *  获取accessToken
     * @return
     */
    public String getAccessToken(){
        log.info("getAccessToken入参!");
        String accessToken = null;
        String getTokenUrl = wxConfig.getTokenUrl().replace("APPID", wxConfig.getAppId()).replace("SECRET", wxConfig.getAppSecret());
        String result = HttpClientUtil.doGet(getTokenUrl);
        JSONObject jsonObject = JSONObject.parseObject(result);
        accessToken = jsonObject.getString("access_token");
        log.info("getAccessToken出参成功:"+accessToken);
        return accessToken ;
    }

    /**
     *  验证签名
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/checkSign")
    public String checkSign ( HttpServletRequest request) throws Exception {
        log.info("checkSign入参!");
        //获取微信请求参数
        String signature = request.getParameter ("signature");
        String timestamp = request.getParameter ("timestamp");
        String nonce = request.getParameter ("nonce");
        String echostr = request.getParameter ("echostr");
        //参数排序。 token 就要换成自己实际写的 token
        String [] params = new String [] {timestamp,nonce,"123456"} ;
        Arrays.sort (params) ;
        //拼接
        String paramstr = params[0] + params[1] + params[2] ;
        //加密
        //获取 shal 算法封装类
        MessageDigest Sha1Dtgest = MessageDigest.getInstance("SHA-1") ;
        //进行加密
        byte [] digestResult = Sha1Dtgest.digest(paramstr.getBytes ("UTF-8"));
        //拿到加密结果
        String mysignature = CodeLoginUtil.bytes2HexString(digestResult);
        mysignature=mysignature.toLowerCase(Locale.ROOT);
        //是否正确
        boolean signsuccess = mysignature.equals(signature);
        //逻辑处理
        if (signsuccess && echostr!=null) {
            //peizhi  token
            log.info("checkSign出参失败!");
            return echostr  ;//不正确就直接返回失败提示.
        }else{
            JSONObject jsonObject = callback(request);
            log.info("checkSign出参成功:"+jsonObject.toJSONString());
            return jsonObject.toJSONString();
        }
    }


    /**
     *  回调方法
     * @param request
     * @return
     * @throws Exception
     */
    public JSONObject callback(HttpServletRequest request) throws Exception{
        log.info("callback入参!");
        //request中有相应的信息,进行解析
        WxMpXmlMessage message= WxMpXmlMessage.fromXml(request.getInputStream());//获取消息流,并解析xml
        String messageType=message.getMsgType();                        //消息类型
        String messageEvent=message.getEvent();                             //消息事件
        String fromUser=message.getFromUser();
        //String openid = fromUser;//发送者帐号
        String touser=message.getToUser();                            //开发者微信号
        String text=message.getContent();                             //文本消息  文本内容
        // 生成二维码时穿过的特殊参数
        String eventKey=message.getEventKey();                         //二维码参数
        log.info("回调函数中的eventKey: "+eventKey+"; fromUser:"+fromUser);
        String uuid="";                                              //从二维码参数中获取uuid通过该uuid可通过websocket前端传数据
        String userid="";

        //if判断,判断查询
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code","200");
        if(messageType.equals("event")){
            jsonObject = null;
            if(messageEvent.equals("SCAN")){
                //扫描二维码
                //return "欢迎回来";
                log.info("callback出参失败,欢迎回来!");
            }
            if(messageEvent.equals("subscribe")){
                //关注
                //return "谢谢您的关注";
                log.info("callback出参失败,谢谢您的关注!");
            }
            //没有该用户
            if(jsonObject==null){
                //从微信上中拉取用户信息
                String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" +getAccessToken() +
                        "&openid=" + fromUser +
                        "&lang=zh_CN";
                String result = HttpClientUtil.doGet(url);
                jsonObject = JSONObject.parseObject(result);
                /**
                 * 用户信息处理....
                 */
                log.info("callback出参失败,没有该用户!");
            }
            loginMap.put(eventKey,new WxPcLoginKey(eventKey,fromUser));
            return jsonObject;
        }
        log.info("callback出参成功:"+jsonObject);
        return jsonObject;
        //log.info("消息类型:{},消息事件:{},发送者账号:{},接收者微信:{},文本消息:{},二维码参数:{}",messageType,messageEvent,fromUser,touser,text,eventKey);
    }

    /**
     *  根据二维码标识获取用户openId=>获取用户信息
     * @param
     * @return
     */
    @RequestMapping("getOpenId")
    public WXResultJson getOpenId(@RequestBody WxPcLoginKey wxPcLoginKey) throws IOException {
        String eventKey = wxPcLoginKey.getEventKey();
        eventKey = eventKey.replace("\"","");
        if(loginMap.get(eventKey) == null){
            return WXResultJson.error("未扫码成功!") ;
        }
        WxPcLoginKey wxPCLoginKey = loginMap.get(eventKey);
        String openId = wxPCLoginKey.getOpenId();
        log.info("openId: "+openId);
        if(loginMap.get(eventKey) == null){
            return WXResultJson.error("未扫码成功!") ;
        }
        loginMap.remove(eventKey);
        log.info("扫码成功!");
        WxPcLoginKey wxPcLoginKey1 = new WxPcLoginKey(null,null);
        wxPcLoginKey1.setEventKey(eventKey);
        wxPcLoginKey1.setOpenId(openId);
        return WXResultJson.ok(wxPcLoginKey1);
    }
}

yml中的配置如下,natapp中配制了端口为80,所以后端端口需要改成80
在这里插入图片描述
后端yml配置文件中关于微信的配置内容如下,后四项是固定不变值,统一使用的。

wx:
  appId: wx159ec212eca19da2
  appSecret: d0342aacf5c3a4494e7019d1e2cee123
  server: http://unhhri.natappfree.cc
  qrCodeUrl: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
  tokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET
  openIdUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET
  userInfoUrl: https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
  
server:
  port: 80

注:适用本地域名的不好之处就是natApp.exe中生成的域名经常改变,如果想测试微信登录的话,不要忘记将yml中的域名和微信公众台中的域名换成natApp.exe中最新的。
如果项目上线需要买一个域名,将yml中的域名和微信公众台中的域名换掉即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值