php免费企业网站模板桐庐网站制作
什么是三方登录
用户可以在三方主流平台(微信,qq,支付宝。。)登录,然后自己平台(pethome)就不需要登录了。基于用户在主流平台上已有的账号和密码来快速完成己方应用的登录或者注册的功能。而这里的主流平台,一般是已经拥有大量用户的平台,国外的比如Facebook,Twitter等,国内的比如微博、微信、QQ等。 第三方登录的目的是使用用户在其他平台上频繁使用的账号,来快速登内录己方产品,也可以实现不用注册就能登录,好处就是登录比较快捷,不用注册。
一、前期准备流程
 1、注册邮箱账号。
2、根据邮箱账号注册微信开放平台账号,完善开发者资料。
3、申请开发者资质认证、填写相关资料、填写发票、支付认证金额。提交并等待认证结果
4、认证成功后,创建网站应用,填写基本信息、下载网站信息登记表填写并上传扫描件、填写授权回调域等。提交审核等待结果。
5、认证成功后,创建移动应用,至少选择安卓、IOS、WP8其中一种平台
6、创建应用成功后,申请微信登陆,等待审核结果,待审核通过后,可进行微信登陆的开发。
注:创建应用和开发者资质认证可同时进行

准备工作大致流程图
二、具体实现步骤
 1、注册邮箱账号
 支持网易邮箱、QQ邮箱等常规邮箱。此邮箱将成为日后登陆开放平台的账号。
2、注册微信开放平台账号、完善开发者资料
 1)填写邮箱进行注册、开放平台将发送邮件到填写邮箱中,点击邮件上的地址进行开发者资料的完善。(开放平台注册地址:https://open.weixin.qq.com/cgi-bin/readtemplate?t=regist/regist_tmpl&lang=zh_CN )
2)开发者资料完善:主要填写注册人信息。如真实姓名、真实手机号码、联系地址、身份证号码。使用注册人的微信扫码绑定为管理员。提交信息。(邮件信息包含地址,点击后进行资料完善)
3)完善资料后,根据邮箱及密码进行登录
3、申请开发者资质认证
 1)申请开发者资质认证
 登录后,点击右上角邮箱号进入“基本资料”,点击“开发者资质认证”,显示未认证,点击“现在申请”。
认证成功后,这里将变成认证成功:

认证成功后,这里将变成认证成功:

OAUTH协议为用户资源的授权提供了一个安全的,开放而简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。oAuth是Open Authorization的简写,目前的版本是2.0版。
为了让大家理解OAuth的适用场合,下面我先举一个栗子。
有一个“云打印”的网站,可以将用户存储在QQ的照片打印出来。用户为了使用该服务,必须让“云打印”读取自己存储在QQ上的照片。

问题是,只有得到用户的授权,QQ才同意“云打印”读取这些照片。那么“云打印”如何才能获得QQ用户的授权呢?有比较传统的方法,用户自己将QQ的用户名和密码告诉“云打印”,后者就可以读取用户的照片了。但是,这么做会有以下几个严重的缺点:
- “云打印”为了后续的服务,会保存用户的用户名和密码,这样很不安全。
 - “云打印”拥有了获取用户在QQ上所有资料的权利,用户没有办法限制“云打印”的访问资 源的权限和有限期。
 - 用户只有修改密码,才能收回“云打印”的权限。但是这么做,会使得其他所有获得用户授权 的第三方应用程序全部失效。
 - 只要有一个第三方应用程序被破解,就会导致用户密码泄露,以及所有被这个密码保护的数据 也会泄露
 
OAuth就是为了解决上面这些问题而诞生的。 我们平台需要保存三方平台账号和密码
名词定义
在详细介绍OAuth2.0之前,需要了解几个专有名词,这些名词经常出现在各种应用场合,对于我们理解RCF 6749的内容至关重要。
(1)Third-party application:第三方应用程序,本文中又称"客户端"(client),即栗子中的"云打印"。
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的QQ。
(3)Resource Owner:资源所有者,本文中又称"用户"(user)。
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器了解了上面的名词,就不难理解OAuth的作用就是让“客户端”安全可控的获取“用户”的授权,与“服务提供商”进行互动。
OAuth的思路
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层使用的是令牌(token),与用户的密码不同。用户可以在登录的时候,指定令牌的权限范围和有效期。
"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
运行流程
OAuth 2.0的运行流程如下图,摘自RCF 6749。

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
上述六个步骤中,B是关键,即用户怎样才能给客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭借令牌获取资源。
客户端授权 - 授权码模式
客户端必须得到用户的授权,才能获得令牌,OAuth2.0定义了四种授权方式:
- 授权码模式(authorization code)
 - 简化模式(implicit)
 - 密码模式(resource owner password credentials)
 - 客户端模式(client credentials)
 
这里,我们主要介绍一下授权码模式:
授权码模式是功能最完整,流程最严密的授权模式。它的特点是通过客户端的后台服务器,与“服务提供商”的认证服务器进行互动,先看下面一张图。

- 用户访问客户端,后者将前者导向认证服务器。
 - 用户选择是否给予客户端授权。
 - 假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI",同时附上一个授权 码。
 - 客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后 台的服务器上完成的,对用户不可见。
 - 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和 更新令牌(refresh token)。
 
- 为什么选择微信作为三方登录服务提供方
 -  
三方登录产品有很多,比如微信登录,qq登录,支付宝登录,新浪微博登录.但是都是遵循oauth2.0协议,只要学会一个其他就触类旁通.
微信,在做的各位都使用很频繁的社交软件,老少皆宜。今天我们以微信为例进行讲解。
微信开放平台供开发者可以基于微信做很多事情:
https://open.weixin.qq.com/

自己的网站可以接入网站应用开发,为用户提供了微信登录功能,降低了注册门槛,并可在用户授权后,获取用户基本信息,包括头像、昵称、性别、地区。出于安全考虑,网站应用的微信登录,需通过微信扫描二维码来实现。
 
数据库设计

第三方登录流程分析

工具类的封装
使用httpclient组件发送http请求
 *   get:现在只用到get
 *   post
 */
public class HttpClientUtils {
     /**
     * 发送get请求
     * @param url 请求地址
     * @return 返回内容 json
     */
    public static String httpGet(String url){
         // 1 创建发起请求客户端
        try {
            HttpClient client = new HttpClient();
             // 2 创建要发起请求-tet
            GetMethod getMethod = new GetMethod(url);
//            getMethod.addRequestHeader("Content-Type",
//                    "application/x-www-form-urlencoded;charset=UTF-8");
            getMethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf8");
             // 3 通过客户端传入请求就可以发起请求,获取响应对象
            client.executeMethod(getMethod);
             // 4 提取响应json字符串返回
            String result = new String(getMethod.getResponseBodyAsString().getBytes("utf8"));
             return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
         return null;
    }
}
常量封装
//微信登录相关常量
public class WxConstants {
     public static final String APPID = "wxd853562a0548a7d0";
     public static final String SECRET = "4a5d5615f93f24bdba2ba8534642dbb6";
     public static final String GET_ACK_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
     public static final String GET_USER_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
}
检查配置回调域名hosts配置

核心代码实现
<!--处理json-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
跳转授权界面
Login.html
<script type="text/javascript">
           //创建vue是需要给他传递一个对象
          new Vue({
               el: "#loginMain", //id在hmtl模板中要对应
              data: { //数据模型
                  loginForm: {
                       username: '',
                       password: ''
                  },
                   wxAuthUrl:' https://open.weixin.qq.com/connect/qrconnect?appid=wxd853562a0548a7d0' +
                   '&redirect_uri=http://bugtracker.itsource.cn/callback.html&response_type=code&scope=snsapi_login&state=1#wechat_redirect'
              },

回调页面
<!DOCTYPE html>
<html lang="en">
<head>
     <meta charset="UTF-8">
     <title>回调</title>
     <!--集成vue和axios,还要axios全局配置(导入common.js)-->
    <!--script src方式引入vue和axios-->
    <script src="js/plugins/vue/dist/vue.js"></script>
     <script src="js/plugins/axios/dist/axios.js"></script>
     <!--全局配置,以后只要用vue+axios的页面都引入common.js-->
    <script src="js/common.js"></script>
</head>
<body>
     <div id="myDiv">
     </div>
     <script type="text/javascript">
         new Vue({
             el:"#myDiv",
             mounted(){
                 //解析参数对象
                let url = location.href;
                 let paramObj = parseUrlParams2Obj(url);
                 //获取发送请求参数
                let binderUrl = "http://bugtracker.itsource.cn/binder.html"
                let params = {"code":paramObj.code,"binderUrl":binderUrl};
                 //发起微信登录请求
                this.$http.post("/login/wechat",params)
                    .then(result=>{
                        result = result.data;
                         if(result.success){ //已经关联了
                            //做登录
                            //提示
                            alert("登录成功!")
                             //把token和loginInfo存放到localStorage
                            let {token,loginInfo} = result.resultObj;
                             localStorage.setItem("token",token);
                             //把对象转换为json字符串存放
                            localStorage.setItem("loginInfo",JSON.stringify(loginInfo));
                             //跳转主页
                            location.href = "/index.html";
                        }else{ //没有关联跳转关联页面
                            let url = result.resultObj;
                             location.href = url;
                        }
                    })
                    .catch(result=>{
                         alert("系统错误");
                         console.log(result);
                    })
            }
        });
     </script>
</body>
</html>
跳转后台微信登录
LoginInfocontroller 判断是否已经绑定,如果绑定就免密登录,否则返回一个未绑定错误,配合前端跳转到绑定页面
@PostMapping("/wechat")
public AjaxResult loginWechat(@RequestBody Map<String,String> params){
     try {
         return  loginInfoService.loginWechat(params);
    } catch (Exception e) {
        e.printStackTrace();
         return AjaxResult.me().setMessage("系统错误!"+e.getMessage());
    }
}
LoginInfoServiceImpl
 @Override
public AjaxResult loginWechat(Map<String, String> params) {
     //1 获取code
    String code = params.get("code");
    String binderUrl = params.get("binderUrl");
     //2 获取accessToken
    String getAckUrl = WxConstants.GET_ACK_URL
            .replace("APPID", WxConstants.APPID)
            .replace("SECRET", WxConstants.SECRET)
            .replace("CODE", code);
    String jsonStr = HttpClientUtils.httpGet(getAckUrl);
    JSONObject jsonObject = JSONObject.parseObject(jsonStr);
    String accessToken = jsonObject.getString("access_token");
    String openid = jsonObject.getString("openid"); //就相当于微信号
    //3 判断是否已经关联了
    WxUser wxUser = wxUserMapper.loadByOpenId(openid);
     if(wxUser!=null && wxUser.getUser_id()!=null){
         //查询Logininfo
        LoginInfo loginInfo =loginInfoMapper.loadByUserId(wxUser.getUser_id());
         //3.1 如果关联了实现免密登录
        String token = UUID.randomUUID().toString();
         redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES);
        Map<String,Object> result = new HashMap<>();
        result.put("token",token);
        loginInfo.setSalt(null);
        loginInfo.setPassword(null);
        result.put("loginInfo",loginInfo);
         return AjaxResult.me().setResultObj(result);
    }else{
         //3.2 否则跳转到绑定页面
        binderUrl = binderUrl+"?accessToken="+accessToken+"&openId="+openid;
         return AjaxResult.me().setSuccess(false).setResultObj(binderUrl);
    }
}
绑定页面
<!DOCTYPE html>
<html>
    <head lang="en">
       <meta charset="UTF-8">
       <title>注册</title>
       <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
       <meta name="format-detection" content="telephone=no">
       <meta name="renderer" content="webkit">
       <meta http-equiv="Cache-Control" content="no-siteapp" />
       <link rel="stylesheet" href="./AmazeUI-2.4.2/assets/css/amazeui.min.css" />
       <link href="./css/dlstyle.css" rel="stylesheet" type="text/css">
       <script src="./AmazeUI-2.4.2/assets/js/jquery.min.js"></script>
       <script src="./AmazeUI-2.4.2/assets/js/amazeui.min.js"></script>
       <!--script src方式引入vue和axios-->
      <script src="js/plugins/vue/dist/vue.js"></script>
       <script src="js/plugins/axios/dist/axios.js"></script>
       <!--全局配置,以后只要用vue+axios的页面都引入common.js-->
      <script src="js/common.js"></script>
       <!--一个一个页面配置,搞一个公共common.js,以后只需要引入它就ok-->
      <!--<script type="text/javascript">-->
            <!--//配置axios的全局基本路径-->
            <!--axios.defaults.baseURL='http://localhost:8080/'-->
            <!--// axios.defaults.baseURL='/api' //前端跨域配置-->
            <!--//全局属性配置,在任意组件内可以使用this.$http获取axios对象-->
            <!--Vue.prototype.$http = axios-->
      <!--</script>-->
   </head>
    <body>
       <div class="login-boxtitle">
          <a href="home/demo.html"><img alt="" src="./images/logobig.png" /></a>
       </div>
       <div class="res-banner">
          <div class="res-main">
             <div class="login-banner-bg"><span></span><img src="./images/big.jpg" /></div>
             <div class="login-box">
                   <div class="am-tabs" id="doc-my-tabs">
                      <div class="am-tabs-bd">
                         <div class="am-tab-panel am-active"id="myDiv" >
                            <form method="post">
                  <div class="user-phone">
                             <label for="phone"><i class="am-icon-mobile-phone am-icon-md"></i></label>
                             <input type="tel" name="" id="phone" v-model="phoneUserForm.phone" placeholder="请输入手机号">
                  </div>                                                          
                               <div class="verification">
                                  <label for="code"><i class="am-icon-code-fork"></i></label>
                                  <input type="tel" name="" id="code" v-model="phoneUserForm.verifyCode" placeholder="请输入验证码">
                                  <!--<a class="btn" href="javascript:void(0);" οnclick="sendMobileCode();" id="sendMobileCode">-->
                                    <!--<span id="dyMobileButton">获取</span></a>-->
                                 <button type="button" @click="sendMobileCode">获取</button>
                               </div>
                            </form>
                          <div class="login-links">
                               <label for="reader-me">
                                  <input id="reader-me" type="checkbox"> 点击表示您同意商城《服务协议》
                               </label>
                         </div>
                               <div class="am-cf">
                                  <input type="button" @click="binder" name="" value="绑定授权" class="am-btn am-btn-primary am-btn-sm am-fl">
                               </div>
                         
                            <hr>
                         </div>
                         <script>
                            $(function() {
                                $('#doc-my-tabs').tabs();
                             })
                         </script>
                      </div>
                   </div>
             </div>
          </div>
          
                <div class="footer ">
                   <div class="footer-hd ">
                      <p>
                         <a href="# ">恒望科技</a>
                         <b>|</b>
                         <a href="# ">商城首页</a>
                         <b>|</b>
                         <a href="# ">支付宝</a>
                         <b>|</b>
                         <a href="# ">物流</a>
                      </p>
                   </div>
                   <div class="footer-bd ">
                      <p>
                         <a href="# ">关于恒望</a>
                         <a href="# ">合作伙伴</a>
                         <a href="# ">联系我们</a>
                         <a href="# ">网站地图</a>
                         <em>© 2015-2025 Hengwang.com 版权所有. 更多模板 <a href="http://www.cssmoban.com/" target="_blank" title="模板之家">模板之家</a> - Collect from <a href="http://www.cssmoban.com/" title="网页模板" target="_blank">网页模板</a></em>
                      </p>
                   </div>
                </div>
    </body>
     <script type="text/javascript">
       new Vue({
          "el":"#myDiv",
          data:{
                 phoneUserForm:{
                     phone:"13330964748",
                verifyCode:"",
                accessToken:null,
                openId:null
            }
         },
             methods:{
                 binder(){
                     this.$http.post("/login/binder/wechat",this.phoneUserForm)
                  .then(result=>{
                      result = result.data;
                             //提示
                            alert("登录成功!")
                             //把token和loginInfo存放到localStorage
                            let {token,loginInfo} = result.resultObj;
                             localStorage.setItem("token",token);
                             //把对象转换为json字符串存放
                            localStorage.setItem("loginInfo",JSON.stringify(loginInfo));
                             console.log(result,"fjfjjfjfjfjfjjfjf")
                             //跳转主页
                            location.href = "/index.html";
                  })
                  .catch(result=>{
                       console.log(result,"jjjjj")
                       alert("系统错误!");
                  })
            },
                 sendMobileCode(){
                     //1.判断手机号不为空
                    if(!this.phoneUserForm.phone){
                         alert("手机号不能为空");
                         return;
                    }
                     //2.获取按钮,禁用按钮  发送时灰化不能使用,发送成功倒计时60才能使用,如果发送失败立即可以发送
                    var sendBtn = $(event.target);
                     sendBtn.attr("disabled",true);
                     this.$http.post('/verifycode/smsCode',
                        {"phone":this.phoneUserForm.phone,"type":"binder"}).then((res) => {
                         console.log(res);
                         var ajaxResult = res.data;
                         if(ajaxResult.success){
                             alert("手机验证码已经发送到您的手机,请在3分钟内使用");
                             //3.1.发送成:倒计时
                            var time = 60;
                             var interval = window.setInterval( function () {
                                 //每一条倒计时减一
                                time = time - 1 ;
                                 //把倒计时时间搞到按钮上
                                sendBtn.html(time);
                                 //3.2.倒计时完成恢复按钮
                                if(time <= 0){
                                     sendBtn.html("重发");
                                     sendBtn.attr("disabled",false);
                                     //清除定时器
                                    window.clearInterval(interval);
                                }
                            },1000);
                        }else{
                             //3.3.发送失败:提示,恢复按钮
                            sendBtn.attr("disabled",false);
                             alert("发送失败:"+ajaxResult.message);
                        }
                    });
                }
            },
          mounted(){
             let paramObj =  parseUrlParams2Obj(location.href);
             if(paramObj){
                 this.phoneUserForm.accessToken = paramObj.accessToken;
                 this.phoneUserForm.openId = paramObj.openId;
               }
         }
      })
    </script>
</html>
改造发送短信验证接口
@RestController
@RequestMapping("/verifycode")
public class VerifyCodeController {
     @Autowired
     private IVerifyCodeService verifyCodeService;
     //一定情况下Map能够代替类使用
    @PostMapping("/smsCode") //注册验证码
    public AjaxResult sendSmsCode(@RequestBody Map<String,String> params){
        String phone = params.get("phone");
        String type = params.get("type"); //register binder login
        try {
             verifyCodeService.sendSmsCode(params);
             return AjaxResult.me();
        }
         catch (BusinessException e){
            e.printStackTrace();
             return AjaxResult.me().setMessage("发送失败!"+e.getMessage());
        }
         catch (Exception e) {
             return AjaxResult.me().setMessage("系统错误!"+e.getMessage());
        }
    }
}
VerifyCodeServiceImpl
@Override
     public void sendSmsCode(Map<String,String> params) {
        String phone = params.get("phone");
        String type = params.get("type");
         //1 校验
        //1.1 手机号不能为null
        if (!StringUtils.hasLength(phone))
             throw new BusinessException("请输入手机号!");
         if ("register".equals(type)){ //注册
            //1.2 不能被注册
            User user = userMapper.loadByPhone(phone);
             if (user!=null)
                 throw new BusinessException("用户已经被注册!");
            String businessKey = UserConstants.REGISTER_CODE_PREFIX + phone;
            sendSmsCode(businessKey);
        }else if("binder".equals(type)){ //绑定
            String businessKey = UserConstants.BINDER_CODE_PREFIX + phone;
            sendSmsCode(businessKey);
        }
    }
     private void  sendSmsCode(String businessKey){
         //2 判断原来的是否有效
        Object codeObj = redisTemplate
                .opsForValue().get(businessKey); //code:time
        String code = "";
         //2.1 如果有效
        if (codeObj!=null){
            String codeStr = (String) codeObj;
             //2.1.1 判断是否已过重发时间
            String time = codeStr.split(":")[1]; //114555558888
            long intervalTime = System.currentTimeMillis() - Long.valueOf(time);
             if (intervalTime<=1*60*1000){
                 //2.1.1.1 如果没有过提示错误
                throw new BusinessException("请勿重复发送短信验证码!");
            }
             //2.1.1.2 如果过了,就使用原来验证码
            code = codeStr.split(":")[0];
        }else{
             //2.2 如果没有
            //2.2.1 重新生成验证码
            code = StrUtils.getComplexRandomString(4);
        }
         //3 保存验证码到redis
        redisTemplate.opsForValue().set(businessKey
                ,code+":"+System.currentTimeMillis()
                ,3, TimeUnit.MINUTES);
         //4 调用短信接口发送短信
//        SmsUtil.sendMsg(phone,"您的验证码为:"+code+",请在3分钟之内使用!");
        System.out.println("您的验证码为:"+code+",请在3分钟之内使用!");
    }
绑定实现
LoginController
@PostMapping("/binder/wechat")
public AjaxResult binderWechat(@RequestBody Map<String,String> params){
     try {
         return  loginInfoService.binderWechat(params);
    } catch (Exception e) {
        e.printStackTrace();
         return AjaxResult.me().setMessage("系统错误!"+e.getMessage());
    }
}
LoginInfoService
//前台输入手机号是否有账号,如果有创建wxUser帮上就ok,如果没有创建账号在绑定
@Override
public AjaxResult binderWechat(Map<String, String> params) {
     //参数
    String phone = params.get("phone");
    String verifyCode = params.get("verifyCode");
    String accessToken = params.get("accessToken");
    String openId = params.get("openId");
     //0 验证码比对
    Object codeObj = redisTemplate.opsForValue().get(UserConstants.BINDER_CODE_PREFIX + phone);
     if(codeObj==null){
         return AjaxResult.me().setMessage("请重新获取验证码后再操作!");
    }else{
        String codeStr = (String) codeObj;
        String code = codeStr.split(":")[0];//code:time
        if (!verifyCode.equalsIgnoreCase(code)){
             return AjaxResult.me().setMessage("请输入正确验证码后再操作!");
        }
    }
     //1 获取微信用户信息
    String url = WxConstants.GET_USER_URL
            .replace("ACCESS_TOKEN", accessToken)
            .replace("OPENID", openId);
    String jsonStr = HttpClientUtils.httpGet(url);
     //2通过电话和type获取用户登录信息
    LoginDto loginDto = new LoginDto();
    loginDto.setUsername(phone);
    loginDto.setLoginType(1);
    LoginInfo info = loginInfoMapper.loadByPhone(loginDto);
     //3如果用户登录信息不存在
    User user = null;
     if (info==null){
        user = wxUser2User(phone);
        info = user2LoginInfo(user);
         //3.1 创建loginInfo
        loginInfoMapper.save(info);
         //3.2 创建User
        user.setInfo(info);
         userMapper.save(user);
    }else{
         //4用户存在 查询用户
        user = userMapper.loadByPhone(phone);
    }
     //5把用户和wxUser进行绑定
    WxUser wxUser = wxUserJsonStr2WxUser(jsonStr);
    wxUser.setUser_id(user.getId());
     wxUserMapper.save(wxUser);
     //6做免密登录
    //3.1 如果关联了实现免密登录
    String token = UUID.randomUUID().toString();
     redisTemplate.opsForValue().set(token,info,30, TimeUnit.MINUTES);
    Map<String,Object> result = new HashMap<>();
    result.put("token",token);
    info.setSalt(null);
    info.setPassword(null);
    result.put("loginInfo",info);
     return AjaxResult.me().setResultObj(result);
}
private LoginInfo user2LoginInfo(User user) {
    LoginInfo info = new LoginInfo();
    BeanUtils.copyProperties(user,info); //按照同名原则拷贝属性
    return info;
}
private User wxUser2User(String phone) {
    User user = new User();
    user.setUsername(phone);
    user.setPhone(phone);
    user.setEmail(null);
     //给一个随机密码
    String salt = UUID.randomUUID().toString();
    String password = MD5Utils.encrypByMd5(StrUtils.getComplexRandomString(6)+salt);
    user.setPassword(password);
    user.setSalt(salt);
    user.setState(1);
    user.setCreatetime(new Date());
     return user;
}
private WxUser wxUserJsonStr2WxUser(String jsonStr) {
    JSONObject jsonObject = JSONObject.parseObject(jsonStr);
    WxUser wxUser = new WxUser();
    wxUser.setOpenid(jsonObject.getString("openid"));
    wxUser.setNickname(jsonObject.getString("nickname"));
    wxUser.setSex(jsonObject.getInteger("sex"));
    wxUser.setAddress(null);
    wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
    wxUser.setUnionid(jsonObject.getString("unionid"));
     return wxUser;
}
后台获取登录用户
public class LoginContext {
     /**
     * 获取当前登录用户信息
     * @param request
     * @return
     */
    public static LoginInfo getLoginInfo(HttpServletRequest request){
         //从请求头中获取token
        String token = request.getHeader("token");
         //使用token从redis中获取登录信息
        if (!StringUtils.isEmpty(token)){
             //1 获取spring容器
            WebApplicationContext context = WebApplicationContextUtils
                    .getWebApplicationContext(request.getServletContext());
             //2 通过容器获取bean
            RedisTemplate redisTemplate = (RedisTemplate) context
                    .getBean("redisTemplate");
             //3 获取登录信息
            Object loginInfoObj = redisTemplate.opsForValue().get(token);
             if (loginInfoObj!=null)
                 return (LoginInfo) loginInfoObj;
        }
         return null;
    }
}
大致流程分析
1.在项目的登录页准备微信登录的按钮
 2.当用户点击微信扫码登录,向微信发起获取授权的请求     ①
  3.微信直接展示扫描二维码给用户(询问用户要不要给我们pethome-web项目授权)
  4.用户扫码,确认授权项目可以获取微信用户信息
 5.微信收到确认,生成code(授权码),通过回调域拼接code返回
 6.我们的项目在callback.html页面上就可以获取授权码了
  
 一:微信登录流程
 1.在callback.html页面中,我们定义钩子函数。
    从请求栏中获取code,并且触发调用后端的微信登录接口,将code传送到后端
 2.后端接口收到请求,交给service处理
 3.service业务流
 4.code不能为空
 5.根据code从微信获取token  使用httpClient         ②
 6.拿到token+openId
 7.判断openId是已经存在(查询t_wxUser),
    7.1.如果已经有了并且userid不为空,直接免密登录
    7.2 如果为空,需要让用户绑定个人用户信息
       返回token+openId 前端帮我们跳转到绑定页面
  
 二:微信绑定流程
 1.在callback.html页面的钩子函数中
 2.接收微信登录流程的返回值:
    AjaxResult {success:false,resultObj:"?token=asdfaf$openId=136462315546"}
 3.跳转到binder.html页面
   location.href="binder.html"+resultObj;
 4.binder.html页面解析地址栏参数并且接收用户输入的参数
 5.发起微信绑定流程
    phone  verifyCode   token    openId
 6.后端接收参数交给service处理
 7.service业务流
  一:校验
    1.空校验
    2.判断验证码
   二:判断phone是否被注册 user
    1.如果注册了,那么我们可以直接绑定微信用户了
    2.如果没有注册过,生成t_user + t_loginInfo
   三:通过 token+openId 查询微信信息 wxuser         ③
      生成t_wxuser
   四:绑定user和wxuser
   五:免密登录
