一起学习网站开发之基于Spring boot的微信登录开发流程和知识点

大城市里の小女人 提交于 2020-01-28 23:57:59

一起学习网站开发之基于Spring boot的微信登录开发流程和知识点

前言

前一段时间刚入门了springboot2.0x基础,但在学完后感觉很空,学得很泛,有种蜻蜓点水的感觉(可能是我太菜了,哈哈),于是就想找个项目练练手,看看自己的水平能做些什么。。。。话不多说,接下来我将从我的角度介绍微信扫一扫登录的开发流程和知识点,但仅仅是开发流程和知识点,因为本人并没有开发出最终的应用场景,原因很悲凉,就是在前期准备上,我无能为力!

开发前期准备

官方文档原话:

网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。 在进行微信

OAuth2.在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已

审核通过的网站应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。

总结:要想开发微信OAuth2.0授权登录

1、在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用。

2、获得相应的AppID和AppSecret,申请微信登录且通过审核后。

具体请参照

可大多数入手该项目的新手都会倒在第一步,就是微信开放平台注册开发者帐号,因为注册开发者帐号需要企业资料,但一般新手何来有企业资料,因此这点对新手是非常不友好的,但这不应该成为阻碍我们继续学习的动力对吧,所以我们就当我们拥有了相应的AppID和AppSecret,然后先知道是怎么用的。

做大家都熟悉的事情

使用微信扫码登录,我相信这是大家都做过的事情。下面我将用一张图结合我们平时”表面“的操作来和大家一起了解微信OAuth2.0实际的交互流程。

应用情景:一次,我女朋友诸葛大力无意点进豆瓣网站,然后看上了一把书,想要购买,但要先登录,于是就有了图1的操作:

在这里插入图片描述

经过上图的简单操作,我女朋友诸葛大力就完成登录了,但第三方平台又是怎么确认诸葛大力的用户身份的(如何拿到她的用户信息),从上图无从知晓,那我们就接着看下图2:

在这里插入图片描述

哇,通过看完图2,你是不是已经。。。。。。对,你还是懵逼(哈哈),回归正题。

1、拉起第三方应用,或重定向第三方应用,这个场景你一定不陌生,就是你扫码登录后页面自动跳转回第三方平台的页面,至于是网站首页还是其他页面,根据业务的实际需求实现就好,具体如何实现,不急,后面会讲,现在先了解概念。

2、什么code?code是授权凭证,如 豆瓣(A) -> 微信开发平台(B) 发起授权,想获取授权⽤户信息,那A必须携带授权码,才可以向B获取授权信息。

3、什么access_token? 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。也就是我们平时登录后所显示的头像、微信号等信息,要通过access_token来进行接口调用才能得到。

以上,整体概念我就都抛出来了,后面的实战也会相当的精彩,以上内容如有错误或不当描述,欢迎讨论指正!

实操整体流程:

网站应用

主要分为四大步

​ 1、请求获取Code

​ 2、用户同意授权与否(同意就带code返回,不同意就不带)

​ 3、获取access_token

​ 4、通过access_token调用接口获取用户个人信息(UnionID机制)

精彩的实战

首先,我们要先配置好AppID和Appsecret,重定向的url,微信开放平台⼆维码连接 。

1、配置好AppID和Appsecret是因为要用它们换取access_token,不然第三方平台怎么拿的到用户的基本个人信息呢。

2、配置好重定向的url,是为登录成功后微信平台能主动跳转到我们后台所写好的接口,方便我们拿到code。(这个如果你没接触过的话可能有点懵,没关系,自信且勇敢地看下去,下面我会尽力助你明白)

3、配置好微信开放平台⼆维码连接url,是因为要通过这个url去获取登录的二维码,且这个url含有AppID。

话不多说,看代码
  1. 配置好AppID和Appsecret

application.properties

#微信开放平台配置
wxopen.appid=wx025575eac69a2d5b
wxopen.appsecret=deeae310a387fa9d3e8f3830ce64caac
#重定向url
wxopen.redirect_url=http://api/v1/wechat/user/callback

说明:这里的重定向url是随便写的,仅是演示,实际开发再确认实际的url。

●为了方便取值,我们可以增加一个配置类,这里命名为WebChatConfig

WebChatConfig

/**
 * 微信配置类
 */
@Configuration   
//与application.properties文件映射
@PropertySource(value = "classpath:application.properties") 
public class WeChatConfig {

   /**
     * 开发平台appid
     */
    @Value("${wxopen.appid}")//等同于取application.properties中wxopen.appid的值,$是取值的意思
    private String openAppid;

    /**
     * 开放平台appsecret
     */
    @Value("${wxopen.appsecret}")
    private String openAppsecret;

    /**
     * 开放平台回调url
     */
    @Value("${wxopen.redirect_url}")
    private String openRedirectUrl;

    /**
     * 微信开放平台二维码连接
     */
    private final static String OPEN_QRCODE_URL=
            "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect";

    
    public String getOpenAppid() {
        return openAppid;
    }

    public void setOpenAppid(String openAppid) {
        this.openAppid = openAppid;
    }

    public String getOpenAppsecret() {
        return openAppsecret;
    }

    public void setOpenAppsecret(String openAppsecret) {
        this.openAppsecret = openAppsecret;
    }

    public String getOpenRedirectUrl() {
        return openRedirectUrl;
    }

    public void setOpenRedirectUrl(String openRedirectUrl) {
        this.openRedirectUrl = openRedirectUrl;
    }

}

● 通过增加配置类WebChatConfig,之后在开发过程中要使用appid和appsecret时,我们只需要调用相应的get方法就可以实现了,相当于做了一层简单的封装。

● 还有你发现,我已经配置好了微信开放平台二维码连接的url,先来看看官方文档

第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在PC端打开以下链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与审核时填写的授权域名不一致或scope不为snsapi_login。

其他的先不用看(都是小case),我们关注重点:可以通过在PC端打开以下链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect ,意思就是我们要向微信开发平台发出以上这个链接请求,就可以获得微信开放平台二维码(用户就是通过扫描该二维码登录)。

我们来观察这个链接包含了那些参数:

参数 	                 是否必须 	                   说明
appid 	                 是 	                    应用唯一标识

redirect_uri 	         是 	             请使用urlEncode对链接进行处理

response_type 	         是 	                      填code

scope 	                 是 	           应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前                                                仅填写snsapi_login即

state 	                 否 	         用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可                                       用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,                                       可设置为简单的随机数加session进行校验

显然,我们是要以链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 为模板,然后把APPID、REDIRECT_URl、STATE#wechat_redirect换成我们自己独有的数据参数(其他两个为默认参数),再发送链接请求,即可获得登录二维码, 一切是不是已经豁然开朗了?不,你可能会问,那redirect_uri和state到底是什么?它们如何来?我们又要怎么使用?继续看下去

柯南推理

柯南:我已经知道什么是redirect_uri了,请看图:
在这里插入图片描述

注意:要使用urlEncode对redirect_uri进行处理。

再说说什么是state,其实state就是一个回调的页面链接。

场景演示一(未登录):

在这里插入图片描述

场景演示二(登录成功后):

在这里插入图片描述

●综上你会发现,登录成功后网页会自动跳转到第三方网站首页,这是怎么做的呢,这就是state的作用,state的参数就是登录成功后回调的网页链接。

说了这么多,相信你也迫不及待看代码(你心想:这人废话真多)

定义一个JsonData类:为的是规范前、后端协议,告诉前端请求的状态、数据、描述
public class JsonData implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private Integer code; // 状态码 0 表示成功,1表示处理中,-1表示失败
    private Object data; // 数据
    private String msg;// 描述

    public JsonData() {
    }

    public JsonData(Integer code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    // 成功,传入数据
    public static JsonData buildSuccess() {
        return new JsonData(0, null, null);
    }

    // 成功,传入数据
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, null);
    }

    // 失败,传入描述信息
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }

    // 失败,传入描述信息,状态码
    public static JsonData buildError(String msg, Integer code) {
        return new JsonData(code, null, msg);
    }

    // 成功,传入数据,及描述信息
    public static JsonData buildSuccess(Object data, String msg) {
        return new JsonData(0, data, msg);
    }

    // 成功,传入数据,及状态码
    public static JsonData buildSuccess(Object data, int code) {
        return new JsonData(code, data, null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "JsonData [code=" + code + ", data=" + data + ", msg=" + msg
                + "]";
    }

}

开发WeChatController

1、拼装微信扫一扫登录url

2、微信扫码登录成功后回调

/**
 *   WechatController之拼装微信扫一扫登录url
 */

@Controller
@RequestMapping("/api/v1/wechat")
public class WechatController {
    
    @Autowired
    private WeChatConfig weChatConfig; //注入WeChatConfig
    
    
    @Autowired
    private UserService userService;//有关微信回调的代码,不用管!后面会讲。

    /**
     * 拼装微信扫一扫登录url
     * @return
     */
    @GetMapping("login_url")
    @ResponseBody
    //需要传入state参数,根据实际请求获取
    public JsonData loginUrl(@RequestParam(value = "access_page",required = true)String accessPage) throws UnsupportedEncodingException {

        String redirectUrl = weChatConfig.getOpenRedirectUrl(); //获取开放平台重定向地址

        String callbackUrl = URLEncoder.encode(redirectUrl,"GBK"); //编码处理为官方要求

        String qrcodeUrl = String.format(weChatConfig.getOpenQrcodeUrl(),weChatConfig.getOpenAppid(),callbackUrl,accessPage); // 格式化weChatConfig.getOpenQrcodeUrl(),把原模板链接的APPID、REDIRECT_URl、STATE替换成我们的。

        return JsonData.buildSuccess(qrcodeUrl);
    }
    
    /**
     * 微信扫码登录回调地址
     */
    //为了方便讲解,先忽略微信回调部分的代码
    //...................................
}

启动application,测试一把,发送一下请求

在这里插入图片描述

默认端口:8080,state参数我随便填了一个www.baidu.com,但实际场景中的state参数是由前端获取传入后端的,简单来说是由前端决定的,这里我们不展开讨论。

再来看看请求的结果:

在这里插入图片描述

可以看到,返回的是我们自定义的JsonData格式,我把data拉出来看看

"https://open.weixin.qq.com/connect/qrconnect?appid=wx025575eac69a2d5b&redirect_uri=http%3A%2F%2Fsunny.viphk.ngrok.org%2Fapi%2Fv1%2Fwechat%2Fuser%2Fcallback&response_type=code&scope=snsapi_login&state=www.baidu.com#wechat_redirect"

可以明显的看到url中有我们自己的appid=wx025575eac69a2d5b,自定义的、经编码处理的redirect_uri=http%3A%2F%2Fsunny.viphk.ngrok.org%2Fapi%2Fv1%2Fwechat%2Fuser%2Fcallback,和回调页面链接state=www.baidu.com#wechat_redirect,到这步,我们拼装好的微信扫一扫登录url了,那你可能会问:那微信二维码呢,有个url有什么用?

而其实这时我们再去访问url链接,奇迹就发生了!

在这里插入图片描述​ (好吧,这是理想情况。。。)

像我这样没有appid等微信认证授权的,结果如下:

在这里插入图片描述

太难了。。。

此处有苦说不出。。。

回归正题

问:假设现在是已经显示出微信二维码,那此时如果我女朋友诸葛大力她打开微信进行扫码,她能登陆成功吗?

显然是不能嘛对吧!

为什么?我们还没完成回调的开发呢!微信那边code就无法回调给我们,那获取access_token就更不用说了。

那就继续第二步:微信扫码登录成功后回调

@Controller
@RequestMapping("/api/v1/wechat")
public class WechatController {
    
    @Autowired
    private WeChatConfig weChatConfig; //注入WeChatConfig
    
    
    @Autowired
    private UserService userService;//有关微信回调的代码,不用管!后面会讲。

    /**
     * 拼装微信扫一扫登录url
     * @return
     */
    //为了方便讲解,忽略微信登录部分的代码
    //...............
    
    
   /**
     * 微信扫码登录,回调地址
     * @param code
     * @param state
     * @param response
     * @throws IOException
     */
    @GetMapping("/user/callback") //回调接口
    public void wechatUserCallback(@RequestParam(value = "code",required = true) String code,
                                   String state, HttpServletResponse response) throws IOException {

        //保存用户信息
        User user = userService.saveWeChatUser(code);
        if(user != null){
            //通过jwt生成token返回给前端
            String token = JwtUtils.geneJsonWebToken(user);

            // state 当前用户的页面地址,需要拼接 http://  这样才不会站内跳转
            response.sendRedirect(state+"?token="+token+"&head_img="+user.getHeadImg()+"&name="+URLEncoder.encode(user.getName(),"UTF-8"));
        }

    }


我知道:你可能懵逼了!让我来猜一猜你疑惑的点:

1、回调接口为什么是**@GetMapping("/user/callback")**

2、哪冒出来一个User和userService

3、jwt又是什么?为什么要用jwt

4、state的为什么是这样拼接?意图何在?

解决上面4个问题,基本就收工啦!

插一句:你可能会想:我这人,怎么User、userService和jwt这些知识点都没提前讲,就突然拿出来给我看,都不考虑我的感受的吗,这也太不友好了吧。。。其实不是的,因为我是完全按照我第一次开发的思路来讲解的,现实开发其实也是如此,不可能说一开始就什么都准备好的,很多东西是开发过程中发现需要用到才逐渐加上的,当然,如果你都是老司机了,开发流程、经验都杠杠的,那么像User、userService和jwt等等这些,你可能一开始就会准备好对吧。所以说,看不懂不要慌,很正常,继续看嘛。

不急,我们慢慢看,要自信!

先来看第一个问题:回调接口为什么是**@GetMapping("/user/callback")**

首先,@GetMapping("/api/v1/wechat")不是一个完整的接口,你可能忘了WechatController一开始就定义的@RequestMapping("/api/v1/wechat"),所以我们自定义的完整接口是:/api/v1/wechat/api/v1/wechat,哎?有没有一点似曾相识的感觉?没有的话你可以看回一开始的application.properties所定义的redirect_url,你是不是已经发现我们自定义的接口和redirect_url一模一样,还记得我说redirect_url的作用吗?redirect_url就是把其url的值通过请求的方式发送给微信开发平台,告诉微信开发平台按照这个地址来回调code。这就解释了回调接口为什么是/api/v1/wechat/api/v1/wechat,不然网站后台就接收不到回调的code了。

喘口气~。

来看第二个问题:哪冒出来一个User和userService?

我先喘口气,因为重难点来了!!!这是我认为最难的一个点,但我一定尽力给你讲清楚了!

从代码的注释可以明显的知道 这行代码:

User user = userService.saveWeChatUser(code);//就是保存用户信息的。

但因为我把代码封装得还行,所以单从这行代码来看,你完全不知道这行代码内部在干什么。

我们从这行代码的左边开始看:

1、User

既然是保存信息,那肯定要有一个User的实体类对吧。

终于上代码~。

/**
 * 用户实体类
 */
public class User implements Serializable {

    private Integer id;   //用户id
    private String openid; //不好说,我忘了
    private String name;  //昵称
    private String headImg; //头像地址
    private String phone;   //电话
    private String sign;    //我又忘了
    private Integer sex;    //性别
    private String city;    //城市
    private java.util.Date createTime;  //生成时间,也就是登陆时间

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getHeadImg() {
        return headImg;
    }

    public void setHeadImg(String headImg) {
        this.headImg = headImg;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public java.util.Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}


User实体类就没什么好说的了,只能说还有很大空间优化,但这不是目前的重点,先不用管。

既然有User实体类,那肯定是要有MyBatis框架与数据库映射

上代码~。

Path:src\main\java\com\springbootpay\mapper\UserMapper.java

public interface UserMapper {

     //mybatis参数注意取值⽤ #{} ,别⽤${},因为存在sql注⼊⻛险

    /**
     * 根据主键id查找
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{id}")
    User findByid(@Param("id") int userId);

    /**
     * 根据openid找用户
     * @param openid
     * @return
     */
    @Select("select * from user where openid = #{openid}")
    User findByopenid(@Param("openid") String openid);


    /**
     * 保存新用户
     * @param user
     * @return
     */
    @Insert("INSERT INTO `user` ( `openid`, `name`, `head_img`, `phone`, `sign`, `sex`, `city`, `create_time`)" +
            "VALUES" +
            "(#{openid},#{name},#{headImg},#{phone},#{sign},#{sex},#{city},#{createTime});")
    @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
    int save(User user);

}

说明:MyBatis的内容我不会过多展开讲(要陪女朋友的嘛,时间有限啊)

1、三部分代码的作用注释已经说明

2、是通过注解的方式映射数据库的增删改查,如@Select、@Insert,只要你学过Mysql,明显的Mysql语句嘛。

3、@Options(useGeneratedKeys=true, keyProperty=“id”, keyColumn=“id”),大概的意思就是选择表中字段名为id的值作为主键,自动增加,也就是每保存(增加)一个用户的信息,id自动加1。

**User user = userService.saveWeChatUser(code);**继续往右看

2、userService

service是业务层, 上面介绍MyBatis是属于数据的底层操作,什么是数据的底层操作?就是单纯的完成数据的增删改查,没有任何的业务操作。什么业务?数据的底层操作能单纯的完成数据的增删改查,但是要增加多少个?删除多少个?修改多少个?查找多少个?根据什么条件去增删改查,这就是业务,是要根据用户的实际需求去操作实现的。

因此,一个项目中理应有一个service层。

上代码,service接口类

Path:src\main\java\service\UserService.java

public interface UserService {

    //业务:保存用户信息
    User saveWeChatUser(String code);

}

那接口写好了,下一步就是如何来实现这个接口。

在讲解如何实操实现接口之前,我们先来做一些准备:

●既然是保存用户信息,那必定要先拿到assess_token,但在上面的讲解中我们只是实现了:获取了回调的code!但还没去换取assess_token。

实现携带code、appid和appsecret换取assess_token

首先先来学习官网文档(“//”附详细解析):

通过code获取access_token

//获取assess_token的url模板
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

参数说明

参数 	      是否必须 	           说明
appid 	        是 	     应用唯一标识,在微信开放平台提交应用审核通过后获得
secret   	    是 	     应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
code 	        是 	     填写第一步获取的code参数
grant_type 	    是 	     填authorization_code

返回说明

正确的返回:

//返回的是json格式
{ 
"access_token":"ACCESS_TOKEN", 
"expires_in":7200, 
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID", 
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

参数说明

参数 	                   说明
access_token 	     接口调用凭证
expires_in 	     access_token接口调用凭证超时时间,单位(秒)
refresh_token 	    用户刷新access_token
openid 	            授权用户唯一标识
scope 	         用户授权的作用域,使用逗号(,)分隔
unionid 	    当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。

错误返回样例:

{"errcode":40029,"errmsg":"invalid code"}


通过使用HttpClient4.x工具封装get和post方法获取assess_token,上代码~。

Path:D:\src\main\java\com\springbootpay\utils\HttpUtils.java

/**
 * 封装http get和post方法
 */
public class HttpUtils {
    
    //在pom.xml引入Gson依赖,目的是为了把返回的Json数据格式化为Map集合,存储起来,也方便写入数据库
    private static  final Gson gson = new Gson();

    /**
     * get方法
     * @param url
     * @return
     */
    //需要传入一个参数:由code、appid和appsecret拼装好的请求assess_token的url
    public static Map<String,Object> doGet(String url){
        
        //为存储返回的数据作准备,返回的是Json格式,适合用Key-Vaule的形式存储
        Map<String,Object> map = new HashMap<>();
        // 获得Http客户端(可以理解为:得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
        CloseableHttpClient httpClient =  HttpClients.createDefault();

        RequestConfig requestConfig =  RequestConfig.custom()
                //设置连接超时时间(单位毫秒)
                .setConnectTimeout(5000)
                //设置请求超时时间(单位毫秒)
                .setConnectionRequestTimeout(5000)
                // socket读写超时时间
                .setSocketTimeout(5000)
                //设置是否允许重定向(默认为true)
                .setRedirectsEnabled(true)
                .build();
        // 创建Get请求
        HttpGet httpGet = new HttpGet(url);
        // 将上面的配置信息 运用到这个Get请求里
        httpGet.setConfig(requestConfig);

        try{
            // 由客户端执行(发送)Get请求
            HttpResponse httpResponse = httpClient.execute(httpGet);
            // 从响应模型中获取响应实体的状态码,例如:200表示成功
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                
                //把响应的实体类转化为json字符串
                String jsonResult = EntityUtils.toString( httpResponse.getEntity());
                //把json字符串转化为map的key-value形式存储
                map = gson.fromJson(jsonResult,map.getClass());
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //记得关闭客户端
            try {
                if(httpClient != null){
                    httpClient.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        //返回map,后面就可以通过map去取用户的基本信息
        return map;
    }


    /**
     * 封装post(但其实换取assess_token发送的是一个get请求,所以在这里封装post意义不大,但一个网站由很多部分结合的,虽然换取assess_token用不到post请求,但或许别的地方用到的呢对吧(如:微信支付),so还是值得一学的)
     * @return
     */
    public static String doPost(String url, String data,int timeout){
        CloseableHttpClient httpClient =  HttpClients.createDefault();
        //设置
        RequestConfig requestConfig =  RequestConfig.custom()
                .setConnectTimeout(timeout) //连接超时
                .setConnectionRequestTimeout(timeout)//请求超时
                .setSocketTimeout(timeout)  // socket读写超时时间
                .setRedirectsEnabled(true)  //允许自动重定向
                .build();


        HttpPost httpPost  = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        //post请求:数据是放在请求头的。 所以要创建一个Header并配置一些参数
        httpPost.addHeader("Content-Type","text/html; chartset=UTF-8");

        if(data != null && data instanceof  String){ //使用字符串传参
            //把data转成实体类,然后通过UTF-8编码),最后放到post请求里
            StringEntity stringEntity = new StringEntity(data,"UTF-8");
            httpPost.setEntity(stringEntity);
        }

        try{
            //都是套路,参考get方法部分,不多赘述~。
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                String result = EntityUtils.toString(httpEntity);
                return result;
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        return null;

    }
}


以上,我们就封装好的获取assess_token的方法。再看回那行代码

User user = userService.saveWeChatUser(code); 继续往右看

3、saveWeChatUser

code、appid、appsecret以及或获取assess_token方法都准备好了,接下来就是实现saveWeChatUser();

4、saveWechatUser要实现的事情

1、接收code参数,和appid、appsecret拼串成换取assess_token的url。

2、调用封装好的get方法获取含用户基本信息的map(先获取到assess_token,再通过assess_token拼串去那用户信息)。

3、从map里获取用户的基本信息,并通过UserMapper存储到数据库。(要实现两次get请求,第二次的map才含用户的基本信息,然后存储入数据库,第一次get请求的map是为了拿assess_token)

上代码~。

Path:src\main\java\service\impl\UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    
    //自动注入WeChatConfig(为了拿appid、appsecret)、UserMapper(为了通过UserMapper存储到数据库)
    @Autowired
    private WeChatConfig weChatConfig;

    private UserMapper userMapper;


    @Override
    public User saveWeChatUser(String code) {
        
        //拼串成换取assess_token的url,先把url模板getOpenAccessTokenUrl放到WeChatConfig类里
        //获取assess_token的url模板:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        String accessTokenUrl = String.format(WeChatConfig.getOpenAccessTokenUrl(),
                weChatConfig.getOpenAppid(),weChatConfig.getOpenAppsecret(),code);

        //获取assess_token
        Map<String ,Object> baseMap =  HttpUtils.doGet(accessTokenUrl);
        
        //说明没拿到
        if(baseMap == null || baseMap.isEmpty()) {
            return  null;
        }
        //否则,不为null,就是拿到了。然后就拿token、openId(还要通过OpenID来获取用户基本信息)
        String accessToken = (String)baseMap.get("access_token");
        String openId  = (String) baseMap.get("openid");
        
        User dbUser = userMapper.findByopenid(openId);
        
        //如果不为空,说明已登录,直接返回User
        if(dbUser!=null) { //更新用户,直接返回
            return dbUser;
        }

        //拼串成获取用户基本信息url,也要先把url模板getOpenUserInfoUrl放到WeChatConfig类里.
        //用户信息url模板:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
        String userInfoUrl = String.format(WeChatConfig.getOpenUserInfoUrl(),accessToken,openId);
        //获取含用户信息的map
        Map<String ,Object> baseUserMap =  HttpUtils.doGet(userInfoUrl);

        if(baseUserMap == null || baseUserMap.isEmpty()){
            return  null;
        }

        String nickname = (String)baseUserMap.get("nickname"); //获取昵称
        //回调的sex是Double类型
        Double sexTemp  = (Double) baseUserMap.get("sex");     
        int sex = sexTemp.intValue();                          //性别
        String province = (String)baseUserMap.get("province"); //省份
        String city = (String)baseUserMap.get("city");         //城市
        String country = (String)baseUserMap.get("country");   //国家
         //头像地址,显示图片要通过前后端的联调,一般是前端的事
        String headimgurl = (String)baseUserMap.get("headimgurl");
        //中国-广东-广州
        StringBuilder sb = new StringBuilder(country).append("-").append(province).append("-").append(city);
        String finalAddress = sb.toString();

        //解决乱码
        try {
            //这块我本人也不是很懂,我有空问问大力再来告诉你,嘻嘻~。
            nickname = new String(nickname.getBytes("ISO-8859-1"), "UTF-8");
            finalAddress = new String(finalAddress.getBytes("ISO-8859-1"), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        
        //这,常规操作对吧!
        User user = new User();
        user.setName(nickname);
        user.setHeadImg(headimgurl);
        user.setCity(city);
        user.setOpenid(openId);
        user.setSex(sex);
        user.setCreateTime(new Date());
        userMapper.save(user);
        return user;
    }
}



绕了一大圈,终于把用户的基本信息拿到,好像。。。微信登录已经搞定了???(说真的我差点就去吃饭了。。。),给你一个脚丫子,醒醒!!!先看看我们是从那绕出来讨论的:

emmm…就是这里:微信登录的回调

@Controller
@RequestMapping("/api/v1/wechat")
public class WechatController {
    
    @Autowired
    private WeChatConfig weChatConfig; //注入WeChatConfig
    
    
    @Autowired
    private UserService userService;//有关微信回调的代码,不用管!后面会讲。

    /**
     * 拼装微信扫一扫登录url
     * @return
     */
    //为了方便讲解,忽略微信登录部分的代码
    //...............
    
    
   /**
     * 微信扫码登录,回调地址
     * @param code
     * @param state
     * @param response
     * @throws IOException
     */
    @GetMapping("/user/callback") //回调接口
    public void wechatUserCallback(@RequestParam(value = "code",required = true) String code,
                                   String state, HttpServletResponse response) throws IOException {

        //保存用户信息
        User user = userService.saveWeChatUser(code);
        if(user != null){
            //通过jwt生成token返回给前端
            String token = JwtUtils.geneJsonWebToken(user);

            // state 当前用户的页面地址,需要拼接 http://  这样才不会站内跳转
            response.sendRedirect(state+"?token="+token+"&head_img="+user.getHeadImg()+"&name="+URLEncoder.encode(user.getName(),"UTF-8"));
        }

    }


我们从下面一句代码绕了出来:

//保存用户信息

User user = userService.saveWeChatUser(code);

既然已经解决了,那就继续看下去~。

下面,我们先不讨论这段代码,我们最后看,因为这段代码非微信登录开发必要。但我在这里添加肯定有其意义,等会说一下!

    if(user != null){
        //通过jwt生成token返回给前端
        String token = JwtUtils.geneJsonWebToken(user);

我们先看这一段:

回调页面地址。

// state 当前用户的页面地址,需要拼接 http://  这样才不会站内跳转
response.sendRedirect(state+"?token="+token+"&head_img="+user.getHeadImg()+"&name="+URLEncoder.encode(user.getName(),"UTF-8"));

还是拼串:传入state参数,登录成功后就能自动跳转登录前的页面了。但同时还需要附加上一些参数:head_img(头像地址)、name(用户昵称)等,根据实际需求而定。

为什么需要附加上一些参数,相信大家都认识过这样的场景:

大力登录前:
在这里插入图片描述

大力登录后:

在这里插入图片描述

前后对比,我大力的头像就出来了,这就是原因所在:返回给前端,前端通过联调等操作显示图片。

好像。。。。这次真的讲完了!!!奥力给~。

你说什么?上面那段代码还没讲?好!其实微信登录这块是真的已经完成了。

官方文档的时序图:

在这里插入图片描述

注意:并不是最标准的时序图,因为没有说到请求用户信息那块,总的来说就是不完整的。

而这段简短的代码,就要结合网站开发的实际来讨论了。

    if(user != null){
        //通过jwt生成token返回给前端
        String token = JwtUtils.geneJsonWebToken(user);

微信登录与网站开发的结合

上面说了很多,几乎都是如何实现微信扫一扫登录,现在登录方面是完成了,但怎么与网站联系在一起呢?我们的主题是什么? **网站开发之微信登录!**简单来说就是,我们网站如何知道用户通过微信登录了,这点很重要!因为登录成功与否涉及到用户的个人权限的问题,举个例子:你购买了一个学习视频,后台给你的权限是:必须是你的账号或微信登录才能观看。那问题就来了:你通过微信登录了,但网站不知道啊!网站如何知道你登录后的一切操作 就是 你登录后的操作呢?(有点绕,你细读哈~。)

这就涉及一个关于token(核心:类似一张身份证,说明你的身份,登录后的每一次操作都带上它去网站后台验证,以证明你登录过)方法的问题,关于token的具体方面我这里就不展开说了,可以自行理解学习,这里我就展示如何使用JWT实现(JWT也是自行了解)

封装JWT工具类

Path:

package com.springbootpay.utils;

import com.springbootpay.domain.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

/**
 * jwt工具类
 */
public class JwtUtils {

    public static final String SUBJECT = "dali";

    public static final long EXPIRE = 1000*60*60*24*7;//过期时间,毫秒,一周

    //秘钥
    public static final String APPSECRET = "zhugedali";

    /**
     * 生成jwt
     * @param user
     * @return
     */
    public static  String geneJsonWebToken(User user){
        if(user == null || user.getId() == null || user.getName() == null
                || user.getHeadImg() == null){
            return null;
        }
        String token = Jwts.builder().setSubject(SUBJECT)
                .claim("id",user.getId())
                .claim("name",user.getName())
                .claim("img",user.getHeadImg())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRE))
                .signWith(SignatureAlgorithm.HS256,APPSECRET).compact();
        return token;

    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public static Claims checkJWT(String token) {

        try {
            final Claims claims = Jwts.parser().setSigningKey(APPSECRET).
                    parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
        }
        return null;
    }
}


调用封装好的JWT工具的方法,生成token返回给前端
    if(user != null){
        //通过jwt生成token返回给前端
        String token = JwtUtils.geneJsonWebToken(user);

以上,就是我想和你们分享讨论的,关于网站开发的微信登录,完毕!

而我实际做的微信扫一扫登录中还加了本地域名映射工具Ngrock,和登录的拦截开发。

由于这都不是讨论重点,所以我就不展开,本地域名映射工具Ngrock适合像我这样的新手玩玩,毕竟通过映射后的域名不限于本地访问,还挺有意思(但其实自己不说域名装X,也没人知道,哈哈!),登录的拦截开发一般是后台权限管理的工作,开发起来也是很不容易,所以在这就不展开了。

最后还是要强调一下:仅仅是开发流程和一些知识点,如果你问我那数据库那些怎么配置等等?别问,问就是不知道哈!

最后的最后,

有时间我会继续分享以下内容,本人是真的小白,非常欢迎大家指正!

《一起学习网站开发之基于Spring boot的微信支付开发流程和知识点》

《一起学习网站开发之基于Spring boot的shiro权限框架开发流程和知识点》

。。。。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!