微信登录
- HttpClient- - 1.1 入门- - 1.1.1 入门案例- 1.2.1 GET方式请求- 1.2.2 POST方式请求- 1.2 微信登录流程- 1.3 需求分析和设计- - 1.3.1 产品原型- 1.3.2 接口设计- 1.3.3 表设计- 1.4 代码开发- - 1.4.1 定义相关配置- 1.4.2 DTO设计- 1.4.3 VO设计- 1.4.4 Controller层- 1.4.5 Service层接口- 1.4.6 Service层实现类- 1.4.7 Mapper层- 1.4.8 编写拦截器- 1.5 功能测试
1. HttpClient
1.1 入门
在进行微信登录之前我们要先知道HttpClient是什么,它是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HttpClient作用:
- 发送HTTP请求
- 接收响应数据
HttpClient应用场景:
当我们在使用扫描支付、查看地图、获取验证码、查看天气等功能时
其实,应用程序本身并未实现这些功能,都是在应用程序里访问提供这些功能的服务,访问这些服务需要发送HTTP请求,并且接收响应数据,可通过HttpClient来实现。
HttpClient的maven坐标:
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency>
HttpClient的核心API:
- HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。
- HttpClients:可认为是构建器,可创建HttpClient对象。
- CloseableHttpClient:实现类,实现了HttpClient接口。
- HttpGet:Get方式请求类型。
- HttpPost:Post方式请求类型。
HttpClient发送请求步骤:
- 创建HttpClient对象
- 创建Http请求对象
- 调用HttpClient的execute方法发送请求
1.1.1 入门案例
1.2.1 GET方式请求
进入到sky-server模块,编写测试代码,发送GET请求。
实现步骤:
- 创建HttpClient对象
- 创建请求对象
- 发送请求,接受响应结果
- 解析结果
- 关闭资源
packagecom.sky.test;@SpringBootTestpublicclassHttpClientTest{/**
* 测试通过httpclient发送GET方式的请求
*/@TestpublicvoidtestGET()throwsException{//创建httpclient对象CloseableHttpClient httpClient =HttpClients.createDefault();//创建请求对象HttpGet httpGet =newHttpGet("http://localhost:8080/user/shop/status");//发送请求,接受响应结果CloseableHttpResponse response = httpClient.execute(httpGet);//获取服务端返回的状态码int statusCode = response.getStatusLine().getStatusCode();System.out.println("服务端返回的状态码为:"+ statusCode);HttpEntity entity = response.getEntity();String body =EntityUtils.toString(entity);System.out.println("服务端返回的数据为:"+ body);//关闭资源
response.close();
httpClient.close();}}
在访问http://localhost:8080/user/shop/status请求时,需要提前启动项目。
测试结果:
1.2.2 POST方式请求
在HttpClientTest中添加POST方式请求方法,相比GET请求来说,POST请求若携带参数需要封装请求体对象,并将该对象设置在请求对象中。
实现步骤:
- 创建HttpClient对象
- 创建请求对象
- 发送请求,接收响应结果
- 解析响应结果
- 关闭资源
/**
* 测试通过httpclient发送POST方式的请求
*/@TestpublicvoidtestPOST()throwsException{// 创建httpclient对象CloseableHttpClient httpClient =HttpClients.createDefault();//创建请求对象HttpPost httpPost =newHttpPost("http://localhost:8080/admin/employee/login");JSONObject jsonObject =newJSONObject();
jsonObject.put("username","admin");
jsonObject.put("password","123456");StringEntity entity =newStringEntity(jsonObject.toString());//指定请求编码方式
entity.setContentEncoding("utf-8");//数据格式
entity.setContentType("application/json");
httpPost.setEntity(entity);//发送请求CloseableHttpResponse response = httpClient.execute(httpPost);//解析返回结果int statusCode = response.getStatusLine().getStatusCode();System.out.println("响应码为:"+ statusCode);HttpEntity entity1 = response.getEntity();String body =EntityUtils.toString(entity1);System.out.println("响应数据为:"+ body);//关闭资源
response.close();
httpClient.close();}
测试结果:
1.2 微信登录流程
微信登录:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
流程图:
步骤分析:
- 小程序端,调用wx.login()获取code,就是授权码。
- 小程序端,调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
- 开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
- 开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识。
- 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
- 小程序端,收到自定义登录态,存储storage。
- 小程序端,后绪通过wx.request()发起业务请求时,携带token。
- 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
- 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
接下来,使用Postman进行测试。
说明:
- 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
- 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
实现步骤:
1). 获取授权码
点击确定按钮,获取授权码,每个授权码只能使用一次,每次测试,需重新获取。
2). 明确请求接口
请求方式、请求路径、请求参数
3). 发送请求
获取session_key和openid
若出现code been used错误提示,说明授权码已被使用过,请重新获取
1.3 需求分析和设计
1.3.1 产品原型
用户进入到小程序的时候,微信授权登录之后才能点餐。需要获取当前微信用户的相关信息,比如昵称、头像等,这样才能够进入到小程序进行下单操作。是基于微信登录来实现小程序的登录功能,没有采用传统账户密码登录的方式。若第一次使用小程序来点餐,就是一个新用户,需要把这个新的用户保存到数据库当中完成自动注册。
登录功能原型图:
业务规则:
- 基于微信登录实现小程序的登录功能
- 如果是新用户需要自动完成注册
1.3.2 接口设计
通过微信登录的流程,如果要完成微信登录的话,最终就要获得微信用户的openid。在小程序端获取授权码后,向后端服务发送请求,并携带授权码,这样后端服务在收到授权码后,就可以去请求微信接口服务。最终,后端向小程序返回openid和token等数据。
基于上述的登录流程,就可以设计出该接口的请求参数和返回数据。
说明:请求路径/user/user/login,第一个user代表用户端,第二个user代表用户模块。
1.3.3 表设计
当用户第一次使用小程序时,会完成自动注册,把用户信息存储到user表中。
字段名数据类型说明****备注idbigint主键自增openidvarchar(45)微信用户的唯一标识namevarchar(32)用户姓名phonevarchar(11)手机号sexvarchar(2)性别id_numbervarchar(18)身份证号avatarvarchar(500)微信用户头像路径create_timedatetime注册时间
1.4 代码开发
1.4.1 定义相关配置
配置微信登录所需配置项:
application-dev.yml
sky:wechat:appid:***********secret:***********************
application.yml
sky:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}
配置为微信用户生成jwt令牌时使用的配置项:
application.yml
sky:jwt:# 设置jwt签名加密时使用的秘钥admin-secret-key: itcast
# 设置jwt过期时间admin-ttl:7200000# 设置前端传递过来的令牌名称admin-token-name: token
user-secret-key: test
user-ttl:7200000user-token-name: authentication
1.4.2 DTO设计
根据传入参数设计DTO类:
在sky-pojo模块,UserLoginDTO.java已定义
packagecom.sky.dto;/**
* C端用户登录
*/@DatapublicclassUserLoginDTOimplementsSerializable{privateString code;}
1.4.3 VO设计
根据返回数据设计VO类:
在sky-pojo模块,UserLoginVO.java已定义
packagecom.sky.vo;@Data@Builder@NoArgsConstructor@AllArgsConstructorpublicclassUserLoginVOimplementsSerializable{privateLong id;privateString openid;privateString token;}
1.4.4 Controller层
根据接口定义创建UserController的login方法:
packagecom.sky.controller.user;@RestController@RequestMapping("/user/user")@Api(tags ="C端用户相关接口")@Slf4jpublicclassUserController{@AutowiredprivateUserService userService;@AutowiredprivateJwtProperties jwtProperties;/**
* 微信登录
* @param userLoginDTO
* @return
*/@PostMapping("/login")@ApiOperation("微信登录")publicResult<UserLoginVO>login(@RequestBodyUserLoginDTO userLoginDTO){
log.info("微信用户登录:{}",userLoginDTO.getCode());//微信登录User user = userService.wxLogin(userLoginDTO);//后绪步骤实现//为微信用户生成jwt令牌Map<String,Object> claims =newHashMap<>();
claims.put(JwtClaimsConstant.USER_ID,user.getId());String token =JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);UserLoginVO userLoginVO =UserLoginVO.builder().id(user.getId()).openid(user.getOpenid()).token(token).build();returnResult.success(userLoginVO);}}
其中,JwtClaimsConstant.USER_ID常量已定义。
1.4.5 Service层接口
创建UserService接口:
packagecom.sky.service;importcom.sky.dto.UserLoginDTO;importcom.sky.entity.User;publicinterfaceUserService{/**
* 微信登录
* @param userLoginDTO
* @return
*/UserwxLogin(UserLoginDTO userLoginDTO);}
1.4.6 Service层实现类
创建UserServiceImpl实现类:实现获取微信用户的openid和微信登录功能
packagecom.sky.service.impl;@Service@Slf4jpublicclassUserServiceImplimplementsUserService{//微信服务接口地址publicstaticfinalStringWX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";@AutowiredprivateWeChatProperties weChatProperties;@AutowiredprivateUserMapper userMapper;/**
* 微信登录
* @param userLoginDTO
* @return
*/publicUserwxLogin(UserLoginDTO userLoginDTO){String openid =getOpenid(userLoginDTO.getCode());//判断openid是否为空,如果为空表示登录失败,抛出业务异常if(openid ==null){thrownewLoginFailedException(MessageConstant.LOGIN_FAILED);}//判断当前用户是否为新用户User user = userMapper.getByOpenid(openid);//如果是新用户,自动完成注册if(user ==null){
user =User.builder().openid(openid).createTime(LocalDateTime.now()).build();
userMapper.insert(user);//后绪步骤实现}//返回这个用户对象return user;}/**
* 调用微信接口服务,获取微信用户的openid
* @param code
* @return
*/privateStringgetOpenid(String code){//调用微信接口服务,获得当前微信用户的openidMap<String,String> map =newHashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");String json =HttpClientUtil.doGet(WX_LOGIN, map);JSONObject jsonObject =JSON.parseObject(json);String openid = jsonObject.getString("openid");return openid;}}
1.4.7 Mapper层
创建UserMapper接口:
packagecom.sky.mapper;@MapperpublicinterfaceUserMapper{/**
* 根据openid查询用户
* @param openid
* @return
*/@Select("select * from user where openid = #{openid}")UsergetByOpenid(String openid);/**
* 插入数据
* @param user
*/voidinsert(User user);}
创建UserMapper.xml映射文件:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.sky.mapper.UserMapper"><insertid="insert"useGeneratedKeys="true"keyProperty="id">
insert into user (openid, name, phone, sex, id_number, avatar, create_time)
values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime})
</insert></mapper>
1.4.8 编写拦截器
编写拦截器JwtTokenUserInterceptor:统一拦截用户端发送的请求并进行jwt校验
packagecom.sky.interceptor;/**
* jwt令牌校验的拦截器
*/@Component@Slf4jpublicclassJwtTokenUserInterceptorimplementsHandlerInterceptor{@AutowiredprivateJwtProperties jwtProperties;/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/publicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{//判断当前拦截到的是Controller的方法还是其他资源if(!(handler instanceofHandlerMethod)){//当前拦截到的不是动态方法,直接放行returntrue;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getUserTokenName());//2、校验令牌try{
log.info("jwt校验:{}", token);Claims claims =JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);Long userId =Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户的id:", userId);BaseContext.setCurrentId(userId);//3、通过,放行returntrue;}catch(Exception ex){//4、不通过,响应401状态码
response.setStatus(401);returnfalse;}}}
在WebMvcConfiguration配置类中注册拦截器:
@AutowiredprivateJwtTokenUserInterceptor jwtTokenUserInterceptor;/**
* 注册自定义拦截器
* @param registry
*/protectedvoidaddInterceptors(InterceptorRegistry registry){
log.info("开始注册自定义拦截器...");//.........
registry.addInterceptor(jwtTokenUserInterceptor).addPathPatterns("/user/**").excludePathPatterns("/user/user/login").excludePathPatterns("/user/shop/status");}
1.5 功能测试
重新编译小程序,进行登录,获取到openid和token数据
查看后台日志
查看数据库user表,第一次登录,会自动注册
后记
👉👉💕💕美好的一天,到此结束,下次继续努力!欲知后续,请看下回分解,写作不易,感谢大家的支持!! 🌹🌹🌹
版权归原作者 失重外太空. 所有, 如有侵权,请联系我们删除。