J加粗样式ava后台流程图

微信三方登录工作流程
1.客户端发送授权码请求
2.当扫码成功之后,服务器向客户端返回对应的授权码(微信服务器调用回调接口)
3.通过授权码获取token(java程序发送的get请求)
4.通过token获取资源
官方给的API文档

授权码接口地址 :
“https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect”
接口地址需要替换APPID(应用ID)、REDIRECT_URI(回调域名)申请账号的时候会有
个人无法申请账号,只有企业才能申请
我们可以将它定义为一个常量 : WechatConstant.REDIRECT_URI

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

注意:回调地址要改成本机的地址 C:\Windows\System32\drivers\etc
hosts文件 加上一段 127.0.0.1 填上回调域名

# For example:
# localhost name resolution is handled within DNS itself.
#	127.0.0.1       localhost
#	//填上回调域名  
    127.0.0.1   bugtracker.********.cn

后台controller层代码 ,前端发送请求到 /wechat/tologin到这个接口

@Controller
@RequestMapping("/wechat")
public class WechatController {
    @Autowired
    private IWechatService wechatService;
     //拉起二维码
    @RequestMapping("/tologin")
    public String toLogin() {
        String codeURl = WechatConstant.CODE_URL.replace("APPID", WechatConstant.APPID)
                .replace("REDIRECT_URI", WechatConstant.REDIRECT_URI);
        System.out.println("这是拉取二维码的接口");
        //重定向跳转地址,二维码就出来了
        return "redirect:" + codeURl;
    }
//替换的回调地址,
public static final String REDIRECT_URI="http://bugtracker.********.cn/wechat/callback";

用户扫描二维码点击确认成功后,会跳转到本机127.0.0.1/wechat/callback接口,并且携带授权码Code

 //二维码扫描成功后,跳转到回调接口,并且携带授权码
    @RequestMapping("/callback")
    public String callback(String code) {
        System.out.println(code + "后台获取到的COde");
        return "redirect:http://localhost:8080/callback.html?code=" + code;
        //可以跳指定网址
        //return "redirect:http://www.baidu.com";
    }

前端创建callback.html页面,页面用到了一个方法,获取?后面的数据 相当于获取Code授权码

 * js动态获取?后面的参数,并且封装成一个json对象
 * @returns {Object}
 */
function getParam(){
    var url=location.search;
    var param = new Object();
    if(url.indexOf("?")!=-1){
        var str = url.substr(1)
        strs = str.split("&");
        for(var i=0;i<strs.length;i++){
            param[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]);
        }
    }
    return param;
}

let param = getParam(); 就已经拿到授权码Code 发送请求到/wechat/handleCallback接口

<script type="text/javascript">
    new Vue({
        el:"#app",
        mounted(){
            //获取当前页面?后面的参数,最终封装成一个json对象
            let param = getParam();
            //处理回调接口
            this.$http.post("/wechat/handleCallback", param).then(res => {
                let {success, message, resultObj} = res.data;
                //如果success为true,并且openid有值,就证明它要跳转到绑定界面,用户和微信进行绑定
                if(success&&resultObj.openid){
                    location.href = "/binder.html?openid="+resultObj.openid
                }else if(success&& resultObj.token){//代表已经登录了
                    //把登录用户保存到浏览器里面
                    localStorage.setItem("token", resultObj.token);
                    localStorage.setItem("loginUser", JSON.stringify(resultObj.loginUser));
                    location.href = "/index.html";
                }

            });
        }
    })
</script>

后台wechat/handleCallback接口

    //处理回调接口
    @PostMapping("/handleCallback")
    @ResponseBody //这里需要返回json数据
    public AjaxResult handleCallback(@RequestBody Map<String, String> param) {
        try {
            //处理回调接口,拿到授权码Code 进行业务逻辑
            Map<String, Object> map = wechatService.handleCallback(param.get("code"));
            //先不看下面的代码
            Object openid = map.get("openid");
            System.out.println(openid+"返回值openid,看看是否有值");
            return AjaxResult.me().setResultObj(map);
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(e.getMessage());
        }
    }

调用业务层方法 传入Code授权码 wechatService.handleCallback(param.get(“code”));
业务层通过授权码Code获取token 令牌
创建获取token令牌的地址url 常量
public static final String TOKEN_URL

 //token对应的url地址
    "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";

需要替换APPID(应用ID)、SECRET(密钥)、CODE(授权码),这三个参数我们都有
密钥申请账号的时候会给,保存好
这里用到了一个工具类,HttpClientUtils,使用java发送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;
    }
}

业务层代码 获取令牌token,获取个人信息
需要引一个jar包,json字符串和对象互转,HttpClientUtils发送请求获取到的是json字符串,需要转成Json对象取值 附上代码

 <!--处理json-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

代码看着多,一步一步来就好了,通过授权码 才能拿到令牌和openid(微信用户唯一标志)

 @Override
    public Map<String,Object> handleCallback(String code) {

        //获取token的url地址
        String tokenUrl = WechatConstant.TOKEN_URL.replace("APPID", WechatConstant.APPID)
                .replace("SECRET", WechatConstant.SECRET)
                .replace("CODE", code);
        //通过授权码Code获取token令牌,json字符串需要转成对象才能取值
        String tokenJsonStr = HttpClientUtils.httpGet(tokenUrl);
        //把json字符串转为json对象
        JSONObject jsonObject = JSONObject.parseObject(tokenJsonStr);
        //获取token
        String access_token = jsonObject.getString("access_token");
        //微信用户唯一标志
        String openid = jsonObject.getString("openid");
      

此时已经拿到 token令牌access_token 和openid(微信唯一标志)
下面就要获取微信用户资源
拿到地址
WechatConstant.USERINFO_URL=
“https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN”;
替换ACCESS_TOKEN 和 OPENID 发送请求

 //通过token获取微信用户资源
        String userinfo_url = WechatConstant.USERINFO_URL.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid);
        //发送get请求
        String userinfoJsonstr = HttpClientUtils.httpGet(userinfo_url);
        //把用户信息数据转为json对象
        jsonObject = JSONObject.parseObject(userinfoJsonstr);
        //通过openid查询数据库中是否有对应的数据(注意:联表查询, t_wxuser和t_logininfo表)
        Wechat wechat = wechatMapper.loadByOpenid(openid);

        //创建一个map集合,封装数据,然后返回给前端,方便绑定微信用户
        Map<String, Object> map = new HashMap<>();

        //如果Wechat为空,就证明是第一次扫码,第一次扫码就要先添加微信用户
        if (wechat == null) {
            wechat = new Wechat();
            //设置属性值  唯一标志
            wechat.setOpenid(openid);
            //设置昵称
            wechat.setNickname(jsonObject.getString("nickname"));
            //设置性别 
            wechat.setSex(jsonObject.getInteger("sex"));
            //设置地址  国家、省份、城市
            wechat.setAddress(jsonObject.getString("country")+" " +
                    jsonObject.getString("province")+" "
            +jsonObject.getString("city"));
            //设置头像
            wechat.setHeadimgurl(jsonObject.getString("headimgurl"));
            //保存微信用户基本信息
            wechatMapper.save(wechat);
            //返回某个状态,然后前端根据这个状态跳转到绑定界面
            map.put("openid", openid);
            return map;  
        }else{//如果wechat不为空,就证明不是第一次扫码
            LoginInfo loginInfo = wechat.getLoginInfo();
            //如果loginInfo为空,依然跳转到绑定界面
            if (loginInfo == null) {
                //返回某个状态,然后前端根据这个状态跳转到绑定界面
                map.put("openid", openid);
                return map;
            }
             //使用redisTemplate先要引入 redis jar包 下面附上了
            //loginInfo不为空,就证明是已经绑定过了,直接登录即可
            String token = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES);
            map.put("token", token);
            map.put("loginUser", loginInfo);
            return map;
        }
    }

此时数据库wxuser表已经添加了微信用户的信息
在这里插入图片描述

前端根据返回的值 openid 进行判断,success为true,并且openid有值,但token没有值
这就代表没有微信绑定用户 需要和用户进行绑定,然后就跳转页面
/binder.html?openid="+resultObj.openid 携带openid 过去

如果token有值,就证明后台查询出来的wechat不为空,并且wechat对象里面的logininfo对象也不为空,返回的map就携带有token和登陆对象,前台根据响应结果 ,直接返回登陆状态就不需要进行绑定了,前台设置直接跳转到/index.html
使用redisTemplate,先要引入 redis jar包

   <!--redis工具包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

callback.html

//处理回调接口
            this.$http.post("/wechat/handleCallback", param).then(res => {
                let {success, message, resultObj} = res.data;
                //如果success为true,并且openid有值,就证明它要跳转到绑定界面,用户和微信进行绑定
                if(success&&resultObj.openid){
                    location.href = "/binder.html?openid="+resultObj.openid
                }else if(success&& resultObj.token){//代表已经登录了
                    //把登录用户保存到浏览器里面
                    localStorage.setItem("token", resultObj.token);
                    localStorage.setItem("loginUser", JSON.stringify(resultObj.loginUser));
                    location.href = "/index.html";
                }

            });
        }

下面的代码都是围绕第一次扫码来写的
创建一个页面 binder.html 用来绑定用户 ,

在这里插入图片描述发送验证码上一篇文章已经讲过了
binder.html 跳转到这个页面是携带有openid 微信唯一标志的

   //准备参数
                let param = {"phone": this.user.phone};
                //发送短信请求
                this.$http.post("/code/sendBinderCode", param).then((res) => {
                    let {success, message} = res.data;
                    //提示用户一分钟以内不能连续发送多次
                    if(!success){
                        this.errorMsg = message;
					}
                });

后台接口/code/sendBinderCode 接收手机号,用来发送验证码
使用的user对象来接收的,这个无所谓 用string 来接收也可以

  //微信绑定手机发送验证码
    @PostMapping("/sendBinderCode")
    public AjaxResult sendBinderCode(@RequestBody User user) {
        try {
            verificationService.sendBinderCode(user.getPhone());
            return AjaxResult.me();
        } catch (DiyException e) {
            e.printStackTrace();
            return new AjaxResult(e.getMessage());
        }

业务层进行逻辑判断
VerificationConstant.USER_BINDER (这个常量代表是绑定验证码)
这里有不明白的 看上一篇文章 配置短信接口,上上篇文章讲过

@Override
    public void sendBinderCode(String phone) throws DiyException {
        //调用方法发送绑定验证码
        sendCode(phone, VerificationConstant.USER_BINDER);
    }
     //发送验证码,不同常量不同短信
    public void sendCode(String phone,String constant) throws DiyException {
        //随机生成4位字符
        String value = StrUtils.getComplexRandomString(4);
        //1分钟以内只能发送1次验证码
        //先通过key 取值 key 就是手机号 加绑定标识常量
        String valueCode = (String) redisTemplate.opsForValue().get(phone + ":" + constant);
        //判断存在redis中的验证码是否为空
        //如果不为空且有效期在一分钟之内
        System.out.println(valueCode + "测试验证码加时间戳");
        if (!StringUtils.isEmpty(valueCode)) {
            //获取验证码第一次创建的时间,截取时间戳
            String beginTime = valueCode.split(":")[1];
            //现在的时间 减去第一次创建的时候 如果小于1分钟之内
            if ((System.currentTimeMillis()) - Long.valueOf(beginTime) <= 60 * 1000) {
                throw new DiyException("一分内不能发送多次验证码");
            }//超过1分钟且小于5分钟

            //还是发送第一次的验证码过去
            value = valueCode.split(":")[0];

        }//如果通过key获取到的验证码为空,证明已经超过5分钟
        redisTemplate.opsForValue().set(phone + ":" + constant,
                value + ":" + System.currentTimeMillis(), 5, TimeUnit.MINUTES);
        //发送验证码
        String str = "尊敬的用户,你的验证码为:" + value + ",请在5分钟之内使用";
        //调用接口发送短信,这是真的发到手机里,需要发送短信就解开注释 ,
        //SendMsgUtils.send(phone, str);
        System.out.println("发送成功" + value);

    }

前台拿到验证码登陆 手机号和验证码全正确并且没有过期的情况,点击注册后发送请求到/wechat/binder 并且携带user对象,user对象有 phone 手机号 code验证码 type账号类型 默认是1,还有openid

	//给注册按钮注册事件
            register(){
                let param = getParam();
                //添加openid属性
                this.user.openid = param.openid;
               //校验
                this.$http.post("/wechat/binder", this.user).then(res => {
                    let {success, message,resultObj} = res.data;
                    if(!success){
                      //这是后台抛出的异常信息
                        this.errorMsg = message;
                    }else{
                        localStorage.setItem("token",resultObj.token);
                       //JSON.stringify是json对象转换成字符串
                       localStorage.setItem("loginUser",JSON.stringify(resultObj.loginUser));
                        location.href = "/index.html";
					}

                });
			}

后台 使用loginInfoDto临时对象接收数据,

@Data
public class LoginInfoDto {
    //手机号
    private String phone;
    //验证码
    private String code;
    //用户名
    private String username;
    //密码
    private String password;
    //重得密码
    private String configpassword;
    //类型 1为门户用户  0为后台管理
    private Integer type;
    //微信用户唯一标志
    private String openid;
}

Controller层,每到一步就测试一步,这样就能看出问题
调用binder方法 传入loginInfoDto对象

  //绑定
    @PostMapping("/binder")
    @ResponseBody
    public AjaxResult binder(@RequestBody LoginInfoDto loginInfoDto) {
        System.out.println(loginInfoDto+"绑定的微信手机号信息");
        try {
            Map<String, Object> map = wechatService.binder(loginInfoDto);
            return AjaxResult.me().setResultObj(map);
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(e.getMessage());
        }

     }

业务层 首写就是要校验传过来的数据 ,这是最基本的
然后通过手机号 和账号类型 去数据库查询 ,加类型是因为手机号 可以是不同类型,比如商家是0,商普通用户是0,返回一个loginInfo对象,再判断loginInfo对象是否为null
如果loginInfo为null 就代表没有绑定过用户,就需要创建新的loginInfo对象
注意创建loginInfo对象后需要返回主键ID,这个ID需要添加进微信用户
再创建user对象,把loginInfo对象的属性复制给user,创建完成后,也要返回主键,
logininfo的id 和user的id 都是要绑定在微信用户上的

@Override
    public Map<String,Object> binder(LoginInfoDto loginInfoDto) throws CustomException {
        //校验数据
        checkData(loginInfoDto);
        //通过手机号码和type在t_logininfo中查询是否有数据
        LoginInfo loginInfo = logininfoMapper.loadByUsernameAndType(loginInfoDto.getPhone(), loginInfoDto.getType());
        //如果为空,就要新建账号
        if (loginInfo == null) {
            //创建LoginInfo对象,创建后返回主键
            loginInfo = createLoginInfo(loginInfoDto);
            //保存logininfo对象
            logininfoMapper.save(loginInfo);
            //创建User对象
            User user = createUser(loginInfo);
            //保存user对象
            userMapper.save(user);
        }
        //绑定微信
        wechatMapper.binder(loginInfo.getId(), loginInfoDto.getOpenid());
        //查询出来的数据为空--》添加logininfo  添加user   绑定微信用户   直接登录
        //查询出来的数据不为空---》直接绑定, 直接登录
        //一下代码就是直接在登录
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES);
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("loginUser", loginInfo);
        return map;

    }

校验数据,难理解的应该就是验证码是否过期,手机发送验证码 设置的有效期为5分钟,我们通过手机号加绑定常量 来取value值的时候 如果为空,就代表已经过期了

    private void checkData(LoginInfoDto loginInfoDto) throws CustomException {
        //判断手机号是否为空
        if(StringUtils.isEmpty(loginInfoDto.getPhone())){
            throw new CustomException("手机号码不为空!!");
        }
        //判断验证码是否为空
        if(StringUtils.isEmpty(loginInfoDto.getCode())){
            throw new CustomException("验证码不能为空!!");
        }
        //判断验证码是否过期,验证码需要通过手机号加上绑定常量来取,
        String codeValue = (String) redisTemplate.opsForValue().get(loginInfoDto.getPhone() + ":" + VerificationConstant.USER_BINDER);
        if(StringUtils.isEmpty(codeValue)){
            throw new CustomException("验证码已过期!!");
        }
        //判断验证码是否正确,通过手机号加常量取的验证码是带有时间戳的,需要分割 取索引0的字符串
        if(!loginInfoDto.getCode().toUpperCase().equals(codeValue.split(":")[0].toUpperCase())){
            throw new CustomException("验证码错误!!");
        }
        //判断账号类型是否为null,openid是否为空,
        if(loginInfoDto.getType()==null || StringUtils.isEmpty(loginInfoDto.getOpenid())){
            throw new CustomException("请完善基本信息!!");
        }
    }

创建loginInfo对象

 /**
     * 创建LoginInfo对象
     * @param loginInfoDto
     * @return
     */
    private LoginInfo createLoginInfo(LoginInfoDto loginInfoDto) {
        LoginInfo loginInfo = new LoginInfo();
        //设置手机
        loginInfo.setPhone(loginInfoDto.getPhone());
        //设置类型
        loginInfo.setType(loginInfoDto.getType());
        //返回对象
        return loginInfo;
    }

创建User对象

 /**
     * 创建用户对象
     * @param loginInfo
     * @return
     */
    private User createUser(LoginInfo loginInfo) {
        User user = new User();
        BeanUtils.copyProperties(loginInfo,user);
        user.setState(PethomeConstant.OK);
        user.setLoginInfo(loginInfo);
        return user;
    }

附上WechatMapper.xml

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.baidu.user.mapper.WechatMapper">

    <resultMap id="WechatResultMap" type="Wechat">
        <id column="id" property="id"/>
        <result column="openid" property="openid"/>
        <result column="nickname" property="nickname"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
        <result column="headimgurl" property="headimgurl"/>
        <association property="loginInfo" javaType="LoginInfo">
            <id column="lid" property="id"/>
            <result column="lusername" property="username"/>
            <result column="lphone" property="phone"/>
            <result column="lemail" property="email"/>
        </association>
    </resultMap>
    <!-- 这里需要联表查询-->
    <select id="loadByOpenid" resultMap="WechatResultMap">
        SELECT w.*,l.id lid,l.phone lphone,l.username lusername,l.email lemail
        FROM t_wxuser w
        LEFT JOIN t_logininfo l ON w.logininfo_id = l.id
        WHERE w.openid=#{openid}
    </select>


    <insert id="save" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO t_wxuser(openid, nickname, sex, address, headimgurl, logininfo_id)
        VALUES (
          #{openid},
          #{nickname},
          #{sex},
          #{address},
          #{headimgurl},
          #{loginInfo.id}
        )
    </insert>

    <update id="binder">
        UPDATE t_wxuser SET logininfo_id=#{logininfoId} WHERE openid=#{openid}
    </update>
</mapper>
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐