一起学习网站开发之基于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。
话不多说,看代码
- 配置好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权限框架开发流程和知识点》
。。。。
来源:CSDN
作者:糖沁
链接:https://blog.csdn.net/weixin_44745208/article/details/104102540