集成微信登陆

wynnyo 2020年07月17日 1,307次浏览

功能描述

在前后端分离实现微信登陆, vue + .net core

整体思路

  • 微信登陆按钮
  • 通过后端获得二维码地址, 并通过 iframe 展示二维码
  • 微信用户扫码, 确认
  • 微信官方跳到指定的指定的页面(前端路由)
  • 在该页面的初始化时去后端验证该微信用户是否存在
    • 存在: 直接登陆, 登陆成功后返回 token
    • 不存在: 返回微信信息
  • 判断返回值如果是 token, 直接把 token 设置为 cookie
  • 如果是 微信信息, 则加载手机绑定页面
  • 输入手机信息, 调用后端绑定方法, 并登陆, 然后返回 token, 在前端设置 cookie

具体实现

前端

  • 在原有的 login.vue 新增 微信扫码按钮和展示二维码的div

    <div class="title-code" v-if="!showQRCode" @click="onTipClick(true)">
    	<div class="title-code-tip">扫码登陆</div>
    	<img :src="barcode" />
    </div>
    ...
    <div class="qrcode" v-show="showQRCode"></div>
    
  • 点击微信扫码按钮切换当前登陆是密码登陆还是密码登陆, 如果是扫码登陆, 去后端获取二维码地址并使用 iframe 加载

    onTipClick(flag) {
    	this.showQRCode = flag;
    	if (flag) {
      	axios({
     		method: "get",
    		url: "/api/platform/wechat/qrcode/url"
    	}).then(res => {
    		if (res) {
    			let oIframe = document.createElement("iframe");
    			oIframe.style.border = 0;
    			oIframe.style.width = "400px";
    			oIframe.style.height = "400px";
    			oIframe.src = res.data;
    			let qrElement = document.getElementsByClassName("log_qrcode")[0];
    			qrElement.innerHTML = "";
    			qrElement.appendChild(oIframe);
    		}
    	});
    }
    
  • 添加新组件 wechat.vue , 在生命周期函数 beforeCreate 里, 调用后端的 CallBack 函数进行微信验证

    axios({
    	method: "get",
    	url: "/api/platform/wechat/callback",
    	params: {
    	code: this.$route.query.code,
    	state: this.$route.query.state
    	}
    })
    .then(res => {
    	if (res) {
      	let result = res.data;
    		if (result.WxExist) {
    			let token = result.TokenInfo;
           ... 设置对应的 token
           this.isShow = false;
    		} else {
    			let wxAccessToken = result.WeixinAccessToken;
    			this.openId = wxAccessToken.openid;
    			this.accessToken = wxAccessToken.access_token;
    			this.isShow = true;
    		}
    	}
    })
    
  • 添加 子组件 wechat-form.vue, 表单提交方法, 调用后端 BindPhone

    axios({
    	method: "post",
    	url: "/api/platform/wechat/bindphone",
    	data: {
    		Phone: phone,	
    		ValidCode: validCode,
    		OpenId: this.openId,
    		AccessToken: this.accessToken
    	}
    })
    .then(res => {
    	if (res) {
      let token = res.data;
    	... 设置对应的 token
    	}
    });
    

后端

  • 在 appsetting.json 定义微信相关信息

    {
      "WeixinOpen": {
    		"AppId": "*******",
    		"AppSecret": "*******",
    		"RedirectUrl": "http://*****/wechat" // 前端对应的组件地址
    	},
    }
    
  • 新建 WeChatController.cs, 定义通用的 微信相关地址

    private string WeChatOpenUrl = @"https://open.weixin.qq.com/connect/qrconnect";
    private string WeChatAccessTokenUrl = @"https://api.weixin.qq.com/sns/oauth2/access_token";
    private string WeChatUserInfoUrl = @"https://api.weixin.qq.com/sns/userinfo";
    
  • 获取二维码地址

    /// <summary>
    /// 获得 生成二维码 url 地址
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Route("qrcode/url")]
    [AllowAnonymous]
    public async Task<string> GetQrCodeUrl()
    {
        var appId = ConfigHelper.GetWeixinSetting("AppId");
        var redirectUrl = System.Web.HttpUtility.UrlEncode(ConfigHelper.GetWeixinSetting("RedirectUrl"));
        var state = DateTime.Now.ToString("yyyyMMdd") + IdUtil.GetId().ToString().Substring(0, 6);
        return WeChatOpenUrl +
               $"?appid={appId}&scope=snsapi_login&redirect_uri={redirectUrl}&state={state}&response_type=code&scope=snsapi_login";
    }
    
  • 跳转页面后的回调函数

    /// <summary>
    /// 跳转页面的回调函数
    /// </summary>
    /// <param name="code"></param>
    /// <param name="state"></param>
    /// <returns></returns>
    [HttpGet]
    [Route("callback")]
    [AllowAnonymous]
    public async Task<WeixinResult> CallBack(string code, string state)
    {
        var appId = ConfigHelper.GetWeixinSetting("AppId");
        var appSecret = ConfigHelper.GetWeixinSetting("AppSecret");
        if (string.IsNullOrWhiteSpace(appId))
        {
            throw ExceptionHelper.NewMessageErrorException("NOT_FOUND_WEIXIN_APPID", "没有找到微信 APPID");
        }
    
        var url =
            WeChatAccessTokenUrl +
            $@"?appid={appId}&secret={appSecret}&code={code}&grant_type=authorization_code";
        var result = await RequestGet(url);
    
        var obj = JsonConvert.DeserializeObject<JObject>(result);
        if (!string.IsNullOrWhiteSpace(obj["errcode"]?.ToString()))
        {
            throw ExceptionHelper.NewMessageErrorException(obj["errcode"]?.ToString(),
                obj["errmsg"]?.ToString());
        }
    
        var accessToken = JsonConvert.DeserializeObject<WeixinAccessToken>(result);
        return await Login(accessToken); // 这个方法会去系统查询是否存在微信信息, 存在返回 token, 不存在返回微信信息
    }
    
  • 微信信息绑定现有用户

    /// <summary>
    /// 微信信息绑定现有用户
    /// </summary>
    /// <param name="phoneUserInfo"></param>
    /// <returns></returns>
    [HttpPost]
    [Route("bindphone")]
    [AllowAnonymous]
    public async Task<TokenInfo> BindPhone(WeixinPhoneUserInfo phoneUserInfo)
    {
        try
        {
            var userUrl = WeChatUserInfoUrl +
                          $"?access_token={phoneUserInfo.AccessToken}&openid={phoneUserInfo.OpenId}";
            var userResult = await RequestGet(userUrl);
            var userObj = JsonConvert.DeserializeObject<JObject>(userResult);
            if (!string.IsNullOrWhiteSpace(userObj["errcode"]?.ToString()))
            {
                throw ExceptionHelper.NewMessageErrorException(userObj["errcode"]?.ToString(),
                    userObj["errmsg"]?.ToString());
            }
            var wxUser = JsonConvert.DeserializeObject<WeixinUserInfo>(userResult);
            // 在人员里查找
            var user = await db.Queryable<UserEntity>()
                .FirstAsync(e => e.LoginId == phoneUserInfo.Phone);
            if (user == null)
            {
                throw ExceptionHelper.NewMessageErrorException("PHONE_USER_NOT_EXIST", "该手机用户不存在");
            }
    
            user.OpenId = wxUser.openid;
            user.UnionId = wxUser.unionid;
    
            ... //这里登陆, 获得 token
    
            await db.Updateable(user).ExecuteCommandAsync();
            return token;
        }
        catch (Exception e)
        {
            throw ExceptionHelper.NewMessageErrorException("", e.Message);
        }
    }
    

效果展示

初始登陆

image-20200717113547447

image-20200717113651506

image-20200717113724170

image-20200717113923316

第二次登陆

  • 第二次登陆不会再出现绑定手机号操作, 扫码成功后直接跳转到首页