发表于: 2019-11-07 20:57:47

1 1129


今天完成的事情:(一定要写非常细致的内容,比如说学会了盒子模型,了解了Margin)

一、【11:30】
修改后台消息新增接口 已完成

二、【16:30】
继续学习微信登录和微信支付 进行中
                    
三、【20:30】
看spring知识和面试知识。 未完成

继续学习昨天的微信:

先看几个基础的概念:

  1. 什么是授权临时票据(code)? 答:第三方通过code进行获取access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。code的临时性和一次保障了微信授权登录的安全性。第三方可通过使用https和state参数,进一步加强自身授权登录的安全性。(获取code需要app_id和redirect_url,而获取access_token需要app_id和app_secret和code)

  2. 什么是授权作用域(scope)? 答:授权作用域(scope)代表用户授权给第三方的接口权限,第三方应用需要向微信开放平台申请使用相应scope的权限后,使用文档所述方式让用户进行授权,经过用户授权,获取到相应access_token后方可对接口进行调用。

网站应用 授权后接口调用

通过code获取access_token

接口说明

通过code获取access_token的接口。

请求说明

http请求方式: GET
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code



刷新或续期access_token使用(可用可不用)

接口说明

access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:

1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;

2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。

refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权,所以,请开发者在refresh_token即将过期时(如第29天时),进行定时的自动刷新并保存好它。

请求方法

使用/sns/oauth2/access_token接口获取到的refresh_token进行以下接口调用:

http请求方式: GET
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

通过一重判断可以使用refresh_token来延期access_token的有效期。

接口说明

检验授权凭证(access_token)是否有效

在使用access_token和app_id以及app_srcret进行获取用户信息的过程中,token已经保存在网页的header中,我们只需判断这个token是否在有效期,在的话,我们就不用再获取一遍token,我们也可以用这个token获取用户信息,来判断用户有没有再第三方网站进行过注册登录。

请求说明

http请求方式: GET
https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID


获取用户个人信息(UnionID机制)(这个基本必用)

接口说明

此接口用于获取用户个人信息。开发者可通过OpenID来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。请注意,在用户修改微信头像后,旧的微信头像URL将会失效,因此开发者应该自己在获取用户信息后,将头像图片保存下来,避免微信头像URL失效后的异常情况。

请求说明

http请求方式: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID


返回说明

正确的Json返回结果:

 {
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"

}


建议:

开发者最好保存用户unionID信息,以便以后在不同应用中进行用户信息互通。

错误的Json返回示例:

{
"errcode":40003,"errmsg":"invalid openid"
}


这个频率也就限制了并发量。
局限性

网站使用微信授权登录的局限性

虽然微信大家都在使用,微信登录也很方便,但是呢。。。它有个巨大的局限性。即 只能在PC端使用。 如果在手机端使用,必须是 app 才可以,如果是 手机浏览器,那么是没有办法使用微信登录功能滴。

来看看我们的复盘的微信登录流程:

@RequestMapping(value = "/wxapi/userinfo",method = RequestMethod.GET)
public String login(HttpServletResponse response) throws Exception{
   //回调地址,要跟下面的地址能调通
   String backUrl="http://dev.home.qiuligao.xiuzhenyuan.cn/homepage";
   //AuthUtil.APPID微信公众号的appId
   String url ="https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + AuthUtil.APP_ID+
           "&redirect_uri=" + URLEncoder.encode(backUrl,"UTF-8")+
           "&response_type=code" +
           "&scope=snsapi_userinfo" +
           "&state=STATE#wechat_redirect";
   return "redirect:"+url;
}

首先先调用这个接口,进行获取code:

这里用了一个java类:URLEncoder来处理回调的url

encode(String S,String enc)方法:

  • 使用特定的编码方案将字符串转换为application/x-www-form-urlencoded格式。 此方法使用提供的编码方案来获取不安全字符的字节。

将这一连串拼接起来,组成一个url,使用重定向,使用get方法请求这个url,(这里应该可以直接我们自己进行)

但是应为需要用户同意所以会使用这种重定向获取code

用户使用体验为:

1.访问这个dev.home.qiuligao.xiuzhenyuan.cn/b/wxapi/userinfo接口

会出现这个界面:

并且url会重定向到:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd47d15a0d19fe813&redirect_uri=http%3A%2F%2Fdev.home.qiuligao.xiuzhenyuan.cn%2Fhomepage&response_type=code&scope=snsapi_userinfo&state=STATE&uin=NjM1MTMzNDM4&key=142d66df0172e41eb7d1f7577d6155291255ec51c8196d11df4a526b598514794b743c07deb8d9d99d54e1cc7ac4ce70&pass_ticket=aVbWGkd76LJAKebmdoQjSX2Dd5PxfzjC4yfnOq8bGYfILt8C1yGNTmk+ejJ7d0cZOE8xR2kj1Xa3heINRHt3Dw==

分析重定向到的这个url:
https://open.weixin.qq.com/connect/oauth2/authorize?

appid=wxd47d15a0d19fe813

&redirect_uri=http%3A%2F%2Fdev.home.qiuligao.xiuzhenyuan.cn%2Fhomepage

&response_type=code

&scope=snsapi_userinfo

&state=STATE

&uin=NjM1MTMzNDM4&key=142d66df0172e41eb7d1f7577d6155291255ec51c8196d11df4a526b598514794b743c07deb8d9d99d54e1cc7ac4ce70&pass_ticket=aVbWGkd76LJAKebmdoQjSX2Dd5PxfzjC4yfnOq8bGYfILt8C1yGNTmk+ejJ7d0cZOE8xR2kj1Xa3heINRHt3Dw==

前面和代码中有变化的是:redirect_uri的值,因为URLEncoder.encode()这个方法的作用,导致有一些特殊字符进行了转换成十六进制“:”转成了%3A,“/”转成了“%2F”。其他的没有变化,重点变化的是最后的#wechat_redirect转换成了&uin=NjM1MTMzNDM4&key=142d66df0172e41eb7d1f7577d6155291255ec51c8196d11df4a526b598514794b743c07deb8d9d99d54e1cc7ac4ce70&pass_ticket=aVbWGkd76LJAKebmdoQjSX2Dd5PxfzjC4yfnOq8bGYfILt8C1yGNTmk+ejJ7d0cZOE8xR2kj1Xa3heINRHt3Dw==     这里是返回的数据

这里有一个疑问:后面跟的这个东西是什么,我猜测是code。

然后就是按照步骤,在第一步,请求这个url,会让用户同意权限之后才会开始第二步,并且这这后会同时发送一个code。

第二步:


用户同意授权后

如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。

也就是跳转到:

http://dev.home.qiuligao.xiuzhenyuan.cn/homepage/?code=CODE&state=STATE

点击统一,我的界面会变成:

但是url会变成:dev.home.qiuligao.xiuzhenyuan.cn/b/login(这里b不知道为什么隐藏了)我直接访问带b的连接报错。


这里应该是前端经过处理导致的。

再仔细看看这里的网页反应,在点击同意之后,跳转访问了回调网址?code=CODE&state=STATE,然后上面的url变成了之前说的 域名/login。

这刚好证明了之前微信文档的页面将跳转至 redirect_uri/?code=CODE&state=STATE。

code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

这个过程中如果出错,会返回错误码:

我们会判断返回码,如果是200正确,我们才能开始第二步。

@RestController
@RequestMapping("/b")
@Slf4j
public class UserInfoController {
@Reference
UserHomeService userHomeService;
@RequestMapping(value = "/login",method = RequestMethod.GET)
public ResponseData getInfo(HttpServletRequest req, HttpServletResponse res)throws Exception{
String code=req.getParameter("code");
log.info("获取code"+code);
//        return req.getParameter("code");
       /**
        * 获取accesstoken
        */
String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+AuthUtil.APP_ID
+ "&secret="+AuthUtil.APP_SECRET
+ "&code="+code
+ "&grant_type=authorization_code";
JSONObject jsonObject;
Map<String,Object> data=new HashMap<>();
try {
jsonObject = AuthUtil.doGetJson(url);
String openid=jsonObject.getString("openid");
log.info("----------openid"+ openid);
String token=jsonObject.getString("access_token");
log.info("----------token"+ token);
/**
            * 根据openid判断数据库是否已有用户信息
* true 已有 false 没有
*/
if (!userHomeService.userExist(openid)){
//创建新用户,初始化信息
User user=new User();
/**
拉取用户信息
*/
String infoUrl="https://api.weixin.qq.com/sns/userinfo?access_token="+token
+ "&openid="+openid
+ "&lang=zh_CN";
JSONObject userInfo=AuthUtil.doGetJson(infoUrl);//这里的userInfo已经是用户的信息了
user.setOpenid(userInfo.getString("openid"));
user.setWxName(userInfo.getString("nickname"));
String sexStr=userInfo.getString("sex");
int sex=Integer.parseInt(sexStr);
user.setSex(sex);
user.setWxImg(userInfo.getString("headimgurl"));
user.setCreateAt(System.currentTimeMillis());
user.setCredit(0);
user.setPrev(0L);
user.setStatus(1);
user.setDateofsign(0);
log.info("用户信息:openid"+user.getOpenid()+
"nickname"+user.getWxName()+
"create"+System.currentTimeMillis());
//用户信息入库
userHomeService.userRegistry(user);
System.out.println("id-----"+user.getId());
//发送token
String academyToken=JWTUtil.createToken(user);
data.put("user",user);
data.put("atoken",academyToken);
return ResponseDataUtil.buildSuccess(data);
           }else{
//判断用户是否被冻结
User user=userHomeService.getUserByOpenid(openid);
if(user.getStatus()==1) {
//发送token
String academyToken=JWTUtil.createToken(user);
data.put("user",user);
data.put("atoken",academyToken);
return ResponseDataUtil.buildSuccess(data);
               }return ResponseDataUtil.buildError("40000","用户已被冻结");
           }
       } catch (Exception e) {
e.printStackTrace();
return ResponseDataUtil.buildError();
       }
   }
}

我这里理解了,必须第一步用户同意了才会返回一个code。

然后微信会会掉这个地址redirect_uri/?code=CODE&state=STATE。然后就是判断是不是正确,正确就有code。然后进行第二步。

第二步:通过code换取网页授权access_token

首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。

尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。

请求方法

获取code后,请求以下链接获取access_token:  https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

这里需要输入的参数其实只有三个,其中一个就是上一步取得的code,如果上一步失败,这里的code也不会有。

对应代码中我们把code取出来。

String code=req.getParameter("code");
log.info("获取code"+code);

然后开始获取access_token

/**
* 获取accesstoken
*/
String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+AuthUtil.APP_ID
       + "&secret="+AuthUtil.APP_SECRET
       + "&code="+code
+ "&grant_type=authorization_code";
JSONObject jsonObject;
Map<String,Object> data=new HashMap<>();
try {
   jsonObject = AuthUtil.doGetJson(url);
   String openid=jsonObject.getString("openid");
   log.info("----------openid"+ openid);
   String token=jsonObject.getString("access_token");
   log.info("----------token"+ token);

这里就是用了一个json转换

doGetJson(url)就是请求这个url,并将返回的结果转成json对象

public static JSONObject doGetJson(String url) throws Exception {
   JSONObject jsonObject=null;
   //初始化httpClient
   HttpClient client = HttpClientBuilder.create().build();
   //Get方式进行提交
   HttpGet httpGet=new HttpGet(url);
   //发送请求
   HttpResponse response= client.execute(httpGet);
   //获取数据
   HttpEntity entity= response.getEntity();
   //格式转换
   if (entity!=null) {
       String result=EntityUtils.toString(entity,"UTF-8");
       jsonObject=JSONObject.parseObject(result);
   }
   //释放链接
   httpGet.releaseConnection();
   return jsonObject;
}

这里用了HttpClient来发送请求。然后使用HttpResponse接收响应,用HttpEntity来装载响应的数据,然后用EntityUtils来把返回的对象转成String字符串,然后用阿里的fastjson的JSONObject转成json对象。

最后释放连接,把json对象返回。

json对象格式类似:

我们就取出其中的access_token和openid。

错误时微信会返回JSON数据包如下(示例为Code无效错误):

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

然后就是第三步:

第三步:刷新access_token(如果需要),这里我们的项目中没有加。

由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。

请求方法

获取第二步的refresh_token后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

也就是再使用一次上面的doGetJson 方法,只不过这次的url不同,

然后就会返回一个刷新的access_token

然后就是第四步:

第四步:拉取用户信息(需scope为 snsapi_userinfo)

如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。

请求方法

http:GET(请使用https协议) https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

这里我们需要传入的参数是access_token和openid

这里我们复盘的代码是:

 /**
* 根据openid判断数据库是否已有用户信息
* true 已有 false 没有
*/
if (!userHomeService.userExist(openid)){
//创建新用户,初始化信息
User user=new User();
/**
拉取用户信息
*/
String infoUrl="https://api.weixin.qq.com/sns/userinfo?access_token="+token
+ "&openid="+openid
+ "&lang=zh_CN";
JSONObject userInfo=AuthUtil.doGetJson(infoUrl);//这里的userInfo已经是用户的信息了
user.setOpenid(userInfo.getString("openid"));
user.setWxName(userInfo.getString("nickname"));
String sexStr=userInfo.getString("sex");
int sex=Integer.parseInt(sexStr);
user.setSex(sex);
user.setWxImg(userInfo.getString("headimgurl"));
user.setCreateAt(System.currentTimeMillis());
user.setCredit(0);
user.setPrev(0L);
user.setStatus(1);
user.setDateofsign(0);
log.info("用户信息:openid"+user.getOpenid()+
"nickname"+user.getWxName()+
"create"+System.currentTimeMillis());
//用户信息入库
userHomeService.userRegistry(user);
System.out.println("id-----"+user.getId());
//发送token
String academyToken=JWTUtil.createToken(user);
data.put("user",user);
data.put("atoken",academyToken);
return ResponseDataUtil.buildSuccess(data);
       }else{
//判断用户是否被冻结
User user=userHomeService.getUserByOpenid(openid);
if(user.getStatus()==1) {
//发送token
String academyToken=JWTUtil.createToken(user);
data.put("user",user);
data.put("atoken",academyToken);
return ResponseDataUtil.buildSuccess(data);
           }return ResponseDataUtil.buildError("40000","用户已被冻结");
       }
   } catch (Exception e) {
e.printStackTrace();
return ResponseDataUtil.buildError();
   }
}

这里代码是先判断我们自己,也就是第三方有没有把这个用户的信息录入自己的数据库。也就是所谓的有没有注册过。我们根据openid搜索有没有这个user存入数据库,没有的话,进行创建。

创建新用户之后,我们需要存入用户的信息,这些信息需要从微信获取,我们就进行第四步:拉取微信用户信息。

我们一样进行拼接url之后,使用AuthUtil.doGetJson(url)执行get请求,调取用户信息。返回的json对象就是用户信息:userInfo,而返回的这个json的格式为:


 {   
  "openid":" OPENID",
  " nickname": NICKNAME,
  "sex":"1",
  "province":"PROVINCE"
  "city":"CITY",
  "country":"COUNTRY",
  "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

然后我们取出这些值,set进入我们新创建的user对象。(注意这里取出来的值都是string字符串,需要自己转换成需要的类型)

然后就是把新用户的信息注册进我们自己的数据库,也就是新增user。

然后把user装进JWT里面生成token。这里也只取了id和微信名。

然后就是之前的判断如果该用户已经注册过了,那么我们就进行get操作,利用openid查询出来该用户的信息。

//判断用户是否被冻结
User user=userHomeService.getUserByOpenid(openid);
if(user.getStatus()==1) {
   //发送token
   String academyToken=JWTUtil.createToken(user);
   data.put("user",user);
   data.put("atoken",academyToken);
   return ResponseDataUtil.buildSuccess(data);
}return ResponseDataUtil.buildError("40000","用户已被冻结");

先判断该用户的状态,1代表未冻结,我们就将toke内进行生成,存入jwt。这样一个完整的微信登录流程就完成了。

之后我们在登录的情况下:利用token里面存的id查询出user的信息。

@ResponseBody
public ResponseData logininfo(HttpServletRequest req){
   try{
       String token=req.getHeader("academy-token");
       User user=JWTUtil.pareseToken(token);
       if (userHomeService.userStatus(user.getId())){
           user=userHomeService.getUserInfoById(user.getId());
           return ResponseDataUtil.buildSuccess(user);
       }
       return ResponseDataUtil.buildError("40000","用户被冻结");
   }catch (Exception e){
       return ResponseDataUtil.buildError("40400","用户未登录或登录失效");
   }
}

之后我们的拦截器就只需要判断token里面有没有东西。然后我们用token的id和name组成一个最简单的对象,我们组成成功,这两个token都在,那么我们就是处于登录状态,否则就是token失效。


后面微信有一个自己的教养access_token是否失效的接口:

附:检验授权凭证(access_token)是否有效

请求方法

http:GET(请使用https协议) https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID

这个可用可不用。


微信支付

查看开通微信支付的流程https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Pay/Vendor_Service_Center.html

,流程过完之后是如何进行开发。



明天计划的事情:(一定要写非常细致的内容)解决前台消息的已读未读逻辑,完成前台消息的未读消息数。
遇到的问题:(遇到什么困难,怎么解决的)

今天早上出现新增消息出现bug,报500,原因是:

  /**
    * 根据角色名找到对应的角色信息
    * */
   //TODO 按照名字获取不用动态查询吧,角色名称唯一。我这里改一下返回判断
   @Override
   public Role getByName(String roleName) {
       logger.error("传入的角色名:"+roleName);
       RoleExample example=new RoleExample();
       example.createCriteria().andNameEqualTo(roleName);
       List<Role> roles=roleMapper.selectByExample(example);
       if(roles.size()!=0) {
           return roles.get(0);
       }

      return roles.get(0);
   }

这里在新增的时候我先调用了这个方法来判断到底有没有这个角色名。

//判断角色存在与否
if (roleService.getByName(role.getName())!=null){
   return ResponseDataUtil.buildError("该角色已存在,请更换名字!");
}

但如果是没有这个角色名的话,新增的时候调用这个方法,查询到的roles这个集合就是空(null),那么我返回的roles.get(0)就会报错了。

mapper新增一个方法直接靠名字来查询这个role。

完成修改。



收获:(通过今天的学习,学到了什么知识)学习了微信的登录。



返回列表 返回列表
评论

    分享到