微信小程序第五节——登录那些事儿(超详细的前后端完整流程)

news2024/11/25 22:29:44

📌 微信小程序第一节 ——自定义顶部、底部导航栏以及获取胶囊体位置信息。
📌 微信小程序第二节 —— 自定义组件
📌 微信小程序第三节 —— 页面跳转的那些事儿
📌 微信小程序第四节—— 网络请求那些事儿

  • 😜           :是江迪呀
  • ✒️本文关键词微信小程序登陆token前端后端验证加密
  • ☀️每日   一言:趁青春尚存,别为生活沉沦。

前言

在微信小程序的开发过程中,如果想要保留用户数据(比如:操作记录购物车信息等等)就必须要用户登陆。为什么呢?比如说,数据库中有一条数据你如何知道这条数据属于谁?属于那个用户呢?这就需要用户登录来获取用户唯一标识从而确定这条数据是属于哪个用户的,这就需要用到用户登陆,那么如何做微信小程序的登陆功能呢?让我们使用Springboot框架+AOP一起来学习吧!


一、登陆的流程

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

1.1 获取用户Code

通过wx.login来获取临时登录code

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 开发者服务向微信接口服务发起请求

拿着微信codeappidappsecret开发者服务器去请求微信接口服务 换取 openIdsecretKey(这里我们使用ApiPost工具来进行请求,当然PostMan工具也行):
在这里插入图片描述

调用微信接口服务接口(注意是Get请求):

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

1.5 返回值

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

拿到返回值后,应该入库,保存一下。
数据库结构如下:
在这里插入图片描述
等下次该用户登录时,走完1.4流程后,可以根据返回值中的openid在我们库中找到该用户,然后进行后续的操作。

1.6 自定义token

拿到下面的返回值后,我们有下面两种方式生成自定义token

(1)使用业务ID生成token(推荐使用,后续的内容都是以用户ID作为例子的)在这里插入图片描述

(2)使用session_key生成token

{
	"session_key": "xxxxx"
}

(3)生成token的工具:

使用md5加密工具来生成token,工具类如下:

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

注意:ENCODE_KEY加密密钥不是固定的可以自己设置,但是!!!ENCODE_KEYIV_KEY 偏移量的字符数量一定要保持一致!!!否者解密失败!!!

测试:

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

结果:

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

(5)将生成好的token放入到Redis(不重要,可以省略)

之所以放入Redis是因为它可以设置过期时间,可以实现token过期重新登录的功能。比如:如果接收到微信小程序请求所携带的token后先去Redis查询是否存在,如果不存在则判定过期,直接返回让再次用户登录。

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

(6)返回token给微信小程序

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

...
return returnSuccess(token);

1.7 将token放到本地

开发者服务器返回给微信小程序结果后,将token放入到本地存储。

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

1.8 请求带上token

开发者服务器发起请求时,在header中带上token

...
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统一拦截:

使用SpringAOP来拦截请求获取token

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

(2)解密token

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

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

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

二、前后端完整代码

2.1 前端代码

(1)登陆

 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)发起业务请求

 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)依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

<dependency>
 	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.7.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
   <version>1.16.16</version>
</dependency>

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.30</version>
</dependency>

<dependency>
  <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.6.3</version>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
   <version>3.0.4</version>
</dependency>

(2)切面相关代码

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.trueland.config.AopException;
import cn.trueland.model.Base;
import cn.trueland.model.User;
import cn.trueland.model.UserContent;
import cn.trueland.utils.AESUtil;
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(* cn.trueland.controller.*.*(..))")
    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.*(..))")
public class AopException extends Exception {
    public AopException() {
        super("登录超时,请重新登录");
    }
}

(3)控制层代码

登陆Controller代码:

import com.hjd.task.common.AbstractController;
import com.hjd.task.dto.wx.WxLoginRequestDto;
import com.hjd.task.dto.wx.WxLoginResponseDto;
import com.hjd.task.entity.exception.Response;
import com.hjd.task.service.IWxLoginService;
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 extends AbstractController {

    @Autowired
    private IWxLoginService iWxLoginService;

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

业务逻辑Controller代码:

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层代码,业务的没有必要。

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)实体类相关代码

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

@Data
public class Base {
    private String userId;
}

import lombok.Data;

@Data
public class User {
    private String userId;
}

public class UserContent {
    private static final ThreadLocal<User> 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)配置类

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

@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配置信息

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

测试结果

在这里插入图片描述

在这里插入图片描述
都可以拿到UserId并返回。

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

三、总结

再多言语都没一张图来的贴切,整个业务流程如下图:
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工智能时代背景下,如何发展与应用自动化测试?

人工智能时代为自动化测试提供了机会和挑战。在发展自动化测试方面&#xff0c;是人工智能领域下的一个应用方向&#xff0c;和无人驾驶、机器人等一样&#xff0c;都是AI技术的应用场景。从技术的发展角度看&#xff0c;自动化测试一共经历了四代发展变化。从最早提出自动化测…

关于 变量

关于局部变量和静态变量&#xff08;基于有一定指针基础&#xff09; #include<stdio.h> void aaa() {int n10;} int main() {printf("%d",n);return 0; } 在这个代码里&#xff0c;很明显会报错&#xff0c;未定义该n标识符&#xff0c;因为这个n是局部变量…

在程序里面执行system(“cd /某个目录“),为什么路径切换不成功?

粉丝提问&#xff1a; 彭老师&#xff0c;问下&#xff0c;在程序里面执行system(“cd /某个目录”)&#xff0c;这样会切换不成功&#xff0c;为啥呢 实例代码&#xff1a; 粉丝的疑惑是明明第10行执行了cd /media操作&#xff0c; 为什么12行执行的pwd > test2.txt 结…

Unity InputField滑动条

InputField增加滑动条效果 类似图中效果 添加一个InputField组件 2 .添加一个Scrollbar放在InputField内 调整属性 调整InputFiled组件属性 需要将Scrollbal添加到InputField的scrollbar上 然后根据美术需求将位置进行调整&#xff0c;记得InputFiled下的Text不要被Scr…

【浓缩概率】浓缩概率思想帮我蒙选择题的概率大大提升!

今天在学习的时候遇到一个很有趣的思想叫作浓缩概率&#xff0c;可以帮我们快速解决一下概率悖论问题&#xff01; 什么是概率 计算概率有下面两个最简单的原则&#xff1a; 原则一、计算概率一定要有一个参照系&#xff0c;称作「样本空间」&#xff0c;即随机事件可能出现…

Docker容器--Consul部署

Docker容器--Consul部署 一、简介1、概述2、Consul两种模式 二、Consul特性1、特性2、应用场景 三、部署Consul集群&#xff08;Server端&#xff09;1、建立Consul服务2、设置代理&#xff0c;在后台启动consul服务端3、查看集群信息 四、Consul部署&#xff08;Client端&…

第4章:运算符

1.算术运算符 ① SELECT 10010,100-35.5,100*2,100/2,100%30 FROM DUAL;②在sql中“”没有连接作用&#xff0c;表示加法运算&#xff0c;字符串转换为数值&#xff08;隐式转换&#xff09;。非数值看作0处理 SELECT 1001 1 FROM DUAL;SELECT 100 a FROM DUAL;③加法运算…

Trie Tree(字典树)例题

字典树. 又称单词查找树&#xff0c; Trie树 &#xff0c;是一种 树形结构 &#xff0c;是一种哈希树的变种。经常被搜索引擎系统用于文本词频统计。. 它的优点是&#xff1a;利用字符串的公共前缀来减少查询时间&#xff0c;最大限度地减少无谓的字符串比较&#xff0c;查询效…

Python 使用chatGPT帮忙写一个有序集类 OrderedSet

需求:需要实现一个有序的集合&#xff0c;像python普通集合一样&#xff0c;除了 它是有序的 我这边穿插着使用了gpt3.5和gpt4,发现确实还是gpt4好用&#xff0c;一分钱一分货啊 问&#xff1a;我的要求是这样&#xff0c;data是一个集合&#xff0c;往里面放了2&#xff0c;…

【大厂面试问题】:飞机绕行地球问题

你的阅读是我最大的动力 目录 你的阅读是我最大的动力 问题描述&#xff1a; 引出思路&#xff1a; 一台加油飞机 两台加油飞机 返航方案一&#xff1a;加油机I、II同时起飞。 返航方案二&#xff1a;加油机I先起飞加油机II再起飞 答案 不直接说答案&#xff0c;一步一…

14个WooCommerce商城网站必备插件

开始建立 WooCommerce 网站&#xff1f;您需要一个具有许多有助于吸引和留住客户的有用功能的网站。虽然基本的 WooCommerce 设置非常方便&#xff0c;但您可以通过使用有用的插件扩展 WooCommerce 来做更多的事情。 有数百个插件需要考虑&#xff0c;我们已经完成了研究&…

[C++初阶]栈和队列_优先级队列的模拟实现 deque类 的理解

为了更好的理解优先级队列priority_queue&#xff0c;这里会同时进行栈和队列的提及 文章目录 简要概念&#xff08;栈和队列&#xff09;栈和队列的模拟实现与使用stack&#xff08;栈&#xff09;deque的理解和操作queue priority_queue&#xff08;优先级队列&#xff09;框…

悲观锁、乐观锁、自旋锁和读写锁

悲观锁和乐观锁 悲观锁&#xff1a;在每次取数据时&#xff0c;总是担心数据会被其他线程修改&#xff0c;所以会在取数据前先加锁&#xff08;读锁&#xff0c;写锁&#xff0c;行 锁等&#xff09;&#xff0c;当其他线程想要访问数据时&#xff0c;被阻塞挂起。&#xff08…

金融贷款行业如何高效获客,积累意向客户群体——运营商大数据

现如今贷款行业面对的运营压力日益扩大&#xff0c;顾客贮备是生存的关键&#xff0c;传统式的陌生拜访&#xff0c;一切随缘销售市场已不能满足其要求。互联网消费行为的融合与转变是在销售市场端反映&#xff0c;直接影响着广告推广广告策略的确立与运用。 可是&#xff0c;…

移除元素【数组】

⭐前言⭐ ※※※大家好&#xff01;我是同学〖森〗&#xff0c;一名计算机爱好者&#xff0c;今天让我们进入练习模式。若有错误&#xff0c;请多多指教。更多有趣的代码请移步Gitee &#x1f44d; 点赞 ⭐ 收藏 &#x1f4dd;留言 都是我创作的最大的动力&#xff01; 题目 27…

IPEmotion 2023 R1支持在线能量分析

新发布的IPEmotion 2023 R1提供了许多新功能&#xff0c;其中最重要的是新的“在线功率计算&#xff08;Online Power Calculation&#xff09;”功能。该功能允许使用预定义的功率计算来进行测量任务和数据分析。此外&#xff0c;IPEmotion 2023 R1现在支持一种新的存储模式&a…

Maya英文界面怎么改为中文界面

Maya是一款3D动画和视觉效果软件&#xff0c;用于创建逼真的角色和大片般的效果&#xff0c;也是受到电影、电视和游戏行业的 3D 建模师、动画师、照明艺术家和 VFX 艺术家等多数人喜爱的一款3D软件。我们在使用Maya的过程中&#xff0c;常常会遇到一些小阻碍&#xff0c;比如M…

【Python爬虫实战】你不必到处找数据,你完全可以自己爬之Python批量采集图虫网摄影师高清美照,听说~你喜欢御姐...(爬图神器)

前言 怎么批量保存网页图片&#xff1f; 有时候在网页中看到很多美图其中有很多自己喜欢的图片素材或壁纸&#xff0c;一张纸一张下载保存未 免太低效了。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众ha…

C++哈希应用——位图布隆过滤器

C布隆过滤器 文章目录 C布隆过滤器概念实质用途控制误判率实现插入和查找布隆过滤器的删除 布隆过滤器优点布隆过滤器缺陷相关大数据题目 用哈希表存储用户记录&#xff0c;缺点是需要消耗较大的内存&#xff1b;用位图存储用户记录&#xff0c;缺点是位图一般处理整形&#xf…

P1039 [NOIP2003 提高组] 侦探推理

题目描述 明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中&#xff0c;于是他召集了一群同学玩推理游戏。游戏的内容是这样的&#xff0c;明明的同学们先商量好由其中的一个人充当罪犯&#xff08;在明明不知情的情况下&#xff09;&#xff0c;明明的任务就是找出这…