WeChat applet login process (including front-end and back-end code)


theme: channing-cyan

cover (2).png

I. Introduction

During the development process of WeChat applet, if you want to retain 用户( 数据such as: 操作记录, 购物车信息etc.), you must 用户log in. why? For example, if there is a piece of data in the database, 数据how do you know who this piece of data belongs to? Which user does it belong to? This requires the user to log in to obtain it 用户to 唯一标识determine which user this data belongs to. So how to implement the login function of the WeChat applet? Let’s use Springbootthe framework + AOPlearn together!


2. Process

微信小程序登录process:

This picture comes from WeChat applet development documentation

开发者服务器Processing flow:

Insert image description here

1.1 Obtain usersCode

wx.loginGet a temporary login via 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 Getappid

After registering 微信开发者账, you can 微信小程序管理后台obtain appid:Insert image description here

1.3 Getappsecret

The mini program key is also obtained in the management backend after registering a WeChat developer platform account: Insert image description hereSince the WeChat mini program key is not displayed in clear text, if you forget it, 重置just download it.

1.4 The developer service initiates a request to the WeChat interface service

Take 微信code, appid, and request appsecretin exchange for 开发者服务器sum (here we use the ApiPost tool to make the request, of course the PostMan tool will also work):微信接口服务openIdsecretKeyInsert image description here

Call 微信接口服务the interface (note that it is Geta request):

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

1.5 Return value

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

After getting the return value, you should 入库save it. The database structure is as follows: Insert image description herethe next time the user logs in, 1.4after completing the process, the user can be found in our library based on the return value openid, and then subsequent operations can be performed.

1.6 Customizationtoken

The so-called tokenis used to confirm the user's ID card. After getting the following return value, we have the following two ways to generate it 自定义token:

(1) Use 业务IDgeneration token(recommended, the following content uses user ID as an example):
Insert image description here

(2)Use session_keyto generate token:

java { "session_key": "xxxxx" }

(3) Generated tokentools:

Use md5encryption tools to generate token, the tool classes are as follows:

```java import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.AES;

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);
}

} ```

Note: ENCODE_KEYThe encryption key is not fixed and can be set by yourself, however! ! ! The characters of the offset ENCODE_KEYmust be consistent! ! ! Otherwise, decryption fails! ! !IV_KEY数量

test:

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);

result:

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

(5) Put the generated ones tokeninto Redis(not important, can be omitted)

The reason why it is put in Redisis that it can set the expiration time and realize tokenthe function of re-login after expiration. For example: after receiving 微信小程序the request, tokenfirst Redischeck whether it is there 存在. If 不存it is, it will be determined to be expired and return directly to allow the user to log in again.

```java @Autowired private RedisTemplate redisTemplate; .... //The unique identifier of the WeChat user private String userId= 'xxxxx' //Put the token into redis and set it to expire in 3 days redisTemplate.opsForValue().set(userId, JSONObject.toJSONString(userInfo),3, TimeUnit.DAYS);

```

(6) Return tokento WeChat applet

Will tokenbe placed in the return body and returned to WeChat.

java ... return returnSuccess(token);

1.7 will tokenbe placed locally

After 开发者服务器the results are returned to the WeChat applet, they will tokenbe placed in local storage.

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

1.8 Request to bringtoken

开发者服务器When making a request to headerthetoken

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

1.9 Developer Server Verificationtoken

开发者服务器When receiving a business request initiated by WeChat, AOPintercept and headerobtain token:

(1) AOPUnified interception:

Used Springto AOPintercept request acquisition token.

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

(2) Decryptiontoken

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

(3) Verify whether it has expired (not important, a step that can be omitted)

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

3. Complete front-end and back-end codes

2.1 Front-end code

(1)Login

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) Initiate a business request

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 Backend code

JavaThe language and framework used by the backend are Springboot+ AOPimplemented. The directory structure is as follows: Insert image description here ymlConfiguration file:Insert image description here

(1)Dependence

```xml 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 related code

```java import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest;

@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.*(..))") 比如 登陆的时候就需要放行登陆的接口。

```java public class AopException extends Exception { public AopException() { super("登录超时,请重新登录"); } }

```

(3)控制层代码

登陆Controller代码:

```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/login") public class WxLogin {

@Autowired
private IWxLoginService iWxLoginService;

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

}

```

业务逻辑Controller代码:

```java import cn.trueland.model.Base; import cn.trueland.model.UserContent; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

@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:

```java import lombok.Data;

@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)配置类

```java import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;

@Data @Component @ConfigurationProperties(prefix = "wx") public class WxConfig { / * * Mini program AppId */ private String appId; / * * Mini program key / private String appSecret; / * * Authorization type / private String grantType ; / * * url of auth.code2Session */ private String authCodeSessionUrl; } ```

(6) ymlConfiguration information

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

Test Results

Insert image description here

Insert image description hereAll can be obtained UserIdand returned.

Now you can happily handle the business logic! ! !

Guess you like

Origin blog.csdn.net/qq_42785250/article/details/132980733
Recommended