0


微信小程序登录流程(包含前端、后端代码)


theme: channing-cyan

cover (2).png

一、前言

在微信小程序的开发过程中,如果想要保留

用户

数据

(比如:

操作记录

购物车信息

等等)就必须要

用户

登陆。为什么呢?比如说,数据库中有一条

数据

你如何知道这条数据属于谁?属于那个用户呢?这就需要用户登录来获取

用户

唯一标识

从而确定这条数据是属于哪个用户的,那么如何做微信小程序的登陆功能呢?让我们使用

Springboot

框架+

AOP

一起来学习吧!


二、流程

微信小程序登录

流程:

此图来自微信小程序开发文档

开发者服务器

处理流程:

在这里插入图片描述

1.1 获取用户

Code

通过

wx.login

来获取临时登录

code

javascript wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: 'https://example.com/onLogin', data: { code: res.code } }) } else { console.log('登录失败!' + res.errMsg) } } }) 

1.2 获取

appid

在注册

微信开发者账

后,可以在

微信小程序管理后台

获取

appid

在这里插入图片描述

1.3 获取

appsecret

小程序密钥同样是在注册微信开发者平台账号后,在管理后台获取的: 在这里插入图片描述 由于微信小程序密钥不以明文的方式展示,如果忘记了,

重置

下就可以了。

1.4 开发者服务向微信接口服务发起请求

拿着

微信code

appid

appsecret

开发者服务器

去请求

微信接口服务

换取

openId

secretKey

(这里我们使用ApiPost工具来进行请求,当然PostMan工具也行): 在这里插入图片描述

调用

微信接口服务

接口(注意是

Get

请求):

javascript https://api.weixin.qq.com/sns/jscode2session? 

1.5 返回值

java { "session_key": "xxxxx", "openid": "xxxxx" } 

拿到返回值后,应该

入库

,保存一下。 数据库结构如下: 在这里插入图片描述 等下次该用户登录时,走完

1.4

流程后,可以根据返回值中的

openid

在我们库中找到该用户,然后进行后续的操作。

1.6 自定义

token

所谓

token

就是用来确认用户的身份证,拿到下面的返回值后,我们有下面两种方式生成

自定义token

(1)使用

业务ID

生成

token

(推荐使用,后续的内容都是以用户ID作为例子的):

在这里插入图片描述

(2)使用

session_key

生成

token

java { "session_key": "xxxxx" } 

(3)生成

token

的工具:

使用

md5

加密工具来生成

token

,工具类如下:


import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets;

public class AESUtil {

/**

  • 加密密钥

/
private static final String ENCODE_KEY = "test_key_secret_";
/
*

  • 偏移量

*/
private static final String IV_KEY = "0000000000000000";

public static String encryptFromString(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
return aes.encryptBase64(data, StandardCharsets.UTF_8);
}

public static String decryptFromString(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
byte[] decryptDataBase64 = aes.decrypt(data);
return new String(decryptDataBase64, StandardCharsets.UTF_8);
}


} ```

***注意:

ENCODE_KEY

加密密钥不是固定的可以自己设置,但是!!!

ENCODE_KEY

IV_KEY

偏移量的字符

数量

一定要保持一致!!!否者解密失败!!!***

**测试:**

java String encryptData = AESUtil.encryptFromString("test123456..", Mode.CBC, Padding.ZeroPadding); System.out.println("加密:" + encryptData); String decryptData = AESUtil.decryptFromString(encryptData, Mode.CBC, Padding.ZeroPadding); System.out.println("解密:" + decryptData);


**结果:**

java 加密:UYKwmVTh39qvwHsQ+tkFow== 解密:test123456..


#### (5)将生成好的

token

放入到

Redis

(不重要,可以省略)

之所以放入

Redis

是因为它可以设置过期时间,可以实现

token

过期重新登录的功能。比如:如果接收到

微信小程序

请求所携带的

token

后先去

Redis

查询是否

存在

,如果

不存

在则判定过期,直接返回让再次用户登录。

```java @Autowired private RedisTemplate redisTemplate; .... //微信用户的唯一标识 private String userId= 'xxxxx' //将token放入redis并设置3天过期 redisTemplate.opsForValue().set(userId,JSONObject.toJSONString(userInfo),3, TimeUnit.DAYS);

(6)返回

token

给微信小程序

token

放到返回体中返回给微信端。

java ... return returnSuccess(token); 

1.7 将

token

放到本地

开发者服务器

返回给微信小程序结果后,将

token

放入到本地存储。

javascript ... //将token放到本地 wx.setStorageSync('token', result.sessionKey) ... 

1.8 请求带上

token

开发者服务器

发起请求时,在

header

中带上

token
javascript ... wx.request({ url: 'https://xxxx.com/api/method', header:{"token":wx.getStorageSync('token')}, success:function(res){}, fail:function(res){} }) ... 

1.9 开发者服务器验证

token
开发者服务器

在接收到微信端发起的业务请求时,通过

AOP

进行拦截获取

header

中的

token

(1)

AOP

统一拦截:

使用

Spring

AOP

来拦截请求获取

token

java //获取token String token = request.getHeader("token"); log.info("token:{}",token); 

(2)解密

token
java ... String token = 'xxxx'; log.info("解密前:{}",decryptData); String decryptData = AESUtil.decryptFromString(token, Mode.CBC, Padding.ZeroPadding); log.info("解密结果:{}",decryptData); //拿到用户ID String userId = decryptData; ... 

(3)验证是否过期(不重要,可以省略的步骤)

java @Autowired private RedisTemplate redisTemplate; ... //用户ID String userId = decryptData ValueOperations valueOperations = redisTemplate.opsForValue(); String userInfoRedis = (String)valueOperations.get(userId); ... 

三、前后端完整代码

2.1 前端代码

(1)登陆

javascript wx.login({ success(res){ if(res.code){ wx.request({ url:'https://xxxx.com/login/wxLogin', method:"POST", data:{"code":res.code} , dataType:"json", success:function(res){ result = res.data.result wx.setStorageSync('token', result.token) //页面跳转 ... }, fail:function(res){}, }) } } }) 

(2)发起业务请求

javascript wx.request({ url: "https://xxxx.com/test/test", method: "GET", dataType:"json", data:{}, //在heard中戴上token header:{"token":wx.getStorageSync('token')}, success:function(res){ ... }, fail:function(res){} }); 

2.2 后端代码

后端使用的

Java

语言,框架是

Springboot

+

AOP

实现。 目录结构如下: 在这里插入图片描述

yml

配置文件: 在这里插入图片描述

(1)依赖

   
   
     org.springframework.boot 
    
   
     spring-boot-starter-web 
    
   
     2.1.2.RELEASE 
    
  

 
   
   
     org.springframework.boot 
    
   
     spring-boot-starter 
    
   
     2.3.7.RELEASE 
    
  

 
   
   
     org.projectlombok 
    
   
     lombok 
    
   
     1.16.16 
    
  

 
   
   
     org.slf4j 
    
   
     slf4j-api 
    
   
     1.7.30 
    
  

 
   
   
     cn.hutool 
    
   
     hutool-all 
    
   
     5.6.3 
    
  

 
   
   
     org.springframework.boot 
    
   
     spring-boot-starter-aop 
    
   
     3.0.4 
    

(2)切面相关代码


@Aspect @Component @Slf4j public class TestAspect { @Autowired private HttpServletRequest request;

@Pointcut("execution(* xx.xxx.controller..(..))"
+"&& !execution(* xx.xxx.controller.WxLogin.*(..)" )
public void pointCut(){}
@Around(value = "pointCut()")
public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取token
String token = request.getHeader("token");
log.info("token:{}",token);
//不存在token直接抛出异常
if(StringUtils.isEmpty(token)){
throw new AopException();
}
//解析token
String userId = AESUtil.decryptFromString(token, Mode.CBC, Padding.ZeroPadding);
log.info("解析token:{}",userId);
//将token 放入到 Base基础类
Base base = new Base();
base.setUserId(userId);
//放到Base中
final Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if(arg instanceof Base){
BeanUtils.copyProperties(base, arg);
}
}
//放到ThreadLocal中
User user = new User();
user.setUserId(userId);
UserContent.setUserContext(user);
return joinPoint.proceed();
}

@After(value = "pointCut()")
public void controllerAfter() throws Throwable {
log.info("后置通知");
log.info("移除ThreadLocal中的用户信息:{}",UserContent.getUserContext());
UserContent.removeUserContext();
}


}
知识点:

从上面代码中我们可以看到。我们通过解密可以拿到

UserId

,这个值我们是频繁使用的,那么如何做到

随用随取

呢?

  • 第一种方式:使用Base基础类,然后让Controller需要传递参数的DTO都继承Base然后就可以随时使用UserId了。
  • 第二种方式:使用ThreadLocal,这种是比上一种优雅一些,也可以完全做到随用随取。但是需要注意在会话结束后一定要移除ThreadLocal中的用户信息,否则会导致内存溢出(这很重要),一般使用切面的后置通知来做这件事情。
execution(* xx.xx.controller.*.*(..))

解释:在方法执行时,xx.xx.controller包下的所有

下面的所有带有任何参数的

方法

都需要走这个切面。

@PointCut

注解值的规则:

  • execution:方法执行时触发。
  • 第一个 *:返回任意类型。
  • xx.xx.controller:具体的报路径。
  • 第二个*:任意类。
  • 第三个*:任意方法。
  • (..):任意参数。

如果想要排除

xxController

类可以这样写: @Pointcut("execution(* xx.xxx.xxxx.controller..(..)) " + "&& !execution(* xx.xxx.xxxx.controller.xxController.*(..))") 比如 登陆的时候就需要

放行

登陆的接口。


(3)控制层代码

登陆

Controller

代码:


@Autowired
private IWxLoginService iWxLoginService;

@PostMapping("/wxLogin")
public Response wxLogin(@RequestBody WxLoginRequestDto requestDto){
WxLoginResponseDto wxLoginResponseDto = iWxLoginService.wxLogin(requestDto);
return returnSuccess(wxLoginResponseDto);
}


}

业务逻辑

Controller

代码:


@RestController @RequestMapping("/test") public class TestController { @GetMapping("/test") public String test(Base base){ return base.getUserId(); } @GetMapping("/test2") public String test2(){ return UserContent.getUserContext().getUserId(); }

}

(4)

Service

层代码:

这里我只帖登陆的

Service

层代码,业务的没有必要。

java public String wxLogin(WxLoginRequestDto requestDto) { if(StringUtils.isBlank(requestDto.getCode())){ throw new BusinessException("code为空!"); } //获取微信服务接口地址 String authCode2Session = wxConfig.getAuthCode2Session(requestDto.getCode()); //请求微信服务接口获取 openId String result = HttpClientUtil.doGet(authCode2Session); String openId = JSONObject.parseObject(result).getString("openid"); String sessionKey = JSONObject.parseObject(result).getString("session_key"); //入库 并返回 userId (逻辑省略) String userId = ...; //将用户信息存入redis redisTemplate.opsForValue().set(userId,userId ,3, TimeUnit.DAYS); String token = AESUtil.encryptFromString(userId, Mode.CBC, Padding.ZeroPadding); return token; } 

(4)实体类相关代码

登录请求

DTO

:

java import lombok.Data; @Data public class WxLoginRequestDto { /** * code */ private String code; } 

基础类

Base

:


@Data public class Base { private String userId; }

``

用户实体类

User`:

```java import lombok.Data;

@Data public class User { private String userId; }

``

用户信息实体

UserContent`:

```java public class UserContent { private static final ThreadLocal 
  
    userInfo = new ThreadLocal(); 
  

public static User getUserContext(){
return userInfo.get();
}

public static void setUserContext(User userContext){
userInfo.set(userContext);
}

public static void removeUserContext(){
userInfo.remove();
}


}

(5)配置类


@Data @Component @ConfigurationProperties(prefix = "wx") public class WxConfig { /** * 小程序AppId */ private String appId; /** * 小程序密钥 */ private String appSecret; /** * 授权类型 */ private String grantType; /** * auth.code2Session 的 url */ private String authCodeSessionUrl; } ```

#### (6)

yml

配置信息

xml wx: app-id: xxxx app-secret: xxxx auth-code-session-url: https://api.weixin.qq.com/sns/jscode2session? grant-type: authorization_code


#### 测试结果

![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/bc476925ecdfed8b3fdfab741d36a4d7.png?x-oss-process=image/watermark,image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfMTIy,g_se,x_0,y_0,t_100)

![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/cc2a3a50191b8baa938af6d788cf1b75.png?x-oss-process=image/watermark,image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfMTIy,g_se,x_0,y_0,t_100) 都可以拿到

UserId

```
并返回。

下面就可以开心的处理业务逻辑啦!!!


本文转载自: https://blog.csdn.net/qq_42785250/article/details/132980733
版权归原作者 是江迪呀 所有, 如有侵权,请联系我们删除。

“微信小程序登录流程(包含前端、后端代码)”的评论:

还没有评论