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

news/2024/4/23 18:20:11/文章来源:https://blog.csdn.net/qq_42785250/article/details/130173916

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

  • 😜           :是江迪呀
  • ✒️本文关键词微信小程序登陆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

 //获取tokenString 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.resultwx.setStorageSync('token', result.token)//页面跳转...},fail:function(res){},})}}})

(2)发起业务请求

 wx.request({url: "https://xxxx.com/test/test",method: "GET",dataType:"json",data:{},//在heard中戴上tokenheader:{"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 {@Autowiredprivate HttpServletRequest request;@Pointcut("execution(* cn.trueland.controller.*.*(..))")public void pointCut(){}@Around(value = "pointCut()")public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {//获取tokenString token = request.getHeader("token");log.info("token:{}",token);//不存在token直接抛出异常if(StringUtils.isEmpty(token)){throw new AopException();}//解析tokenString 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 {@Autowiredprivate 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());//请求微信服务接口获取 openIdString result = HttpClientUtil.doGet(authCode2Session);String openId = JSONObject.parseObject(result).getString("openid");String sessionKey = JSONObject.parseObject(result).getString("session_key");//入库 并返回  userId (逻辑省略)String userId = ...;//将用户信息存入redisredisTemplate.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: xxxxapp-secret: xxxxauth-code-session-url: https://api.weixin.qq.com/sns/jscode2session?grant-type: authorization_code

测试结果

在这里插入图片描述

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

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

三、总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MFC之CRect详解

2023年4月25日&#xff0c;周二晚上。 今天查了不少关于CRect类及其相关内容的资料&#xff0c;学到了不少东西&#xff0c;所以我决定写一篇详细的关于CRect类及其相关内容的文章&#xff0c;以记录今天所学。 CRect类 在 MFC 中&#xff0c;CRect 类表示一个矩形区域。它是…

linux 命令之 tar -czvf和 tar -xzvf

文章目录 一、概述&#xff1a;二、基础知识 一、概述&#xff1a; tar 用于linux 系统中压缩和解压 二、基础知识 tar常用命令参数说明 tar命令的czvf/xzvf参数分别代表的意义如下&#xff1a; -c 或–create 建立新的备份文件。 -x或–extract或–get 从备份文件中还原文件…

SparkStreaming学习之——无状态与有状态转化、遍历kafka的topic消息、WindowOperations

目录 一、状态转化 二、kafka topic A→SparkStreaming→kafka topic B (一)rdd.foreach与rdd.foreachPartition (二)案例实操1 1.需求&#xff1a; 2.代码实现&#xff1a; 3.运行结果 (三)案例实操2 1.需求&#xff1a; 2.代码实现&#xff1a; 3.运行结果 三、W…

Eclipse代码提示突然失灵的解决方案

不知道改动了啥&#xff0c;突然间Eclipse的代码提示就失效了&#xff0c;发现缺少后极不方便。 使用快捷键&#xff1a;Alt/ 提示 No Default Proposals 为什么使用快捷键&#xff1a;Alt/ 会提示“No Default Proposals。”呢&#xff1f; 网上提示可能是热键冲突 但是一套…

数据可视化大屏电商数据展示平台开发实录(Echarts柱图曲线图、mysql筛选统计语句、时间计算、大数据量统计)

数据可视化大屏电商数据展示平台 一、前言二、项目介绍三、项目展示四、项目经验分享4.1 翻牌器4.1.1 翻牌器-今日实时交易4.1.2.翻牌器后端统计SUM函数的使用 4.2 不同时间指标的数据MySql内部的时间计算 4.3 实时交易播报MySql联表查询和内部遍历循环 4.4 每日交易量4.4.1.近…

5.5 高斯型求积公式简历

学习目标&#xff1a; 我会按照以下步骤学习高斯求积公式简介&#xff1a; 理解积分的概念&#xff1a;学习什么是积分以及积分的几何和物理意义&#xff0c;如面积、质量、电荷等概念。 掌握基本的积分技巧&#xff1a;掌握基本的积分公式和技巧&#xff0c;如换元法、分部积…

流辰信息微服务平台:数字化转型的优良工具!

在互联网迅猛发展的今天&#xff0c;越来越多的企业倾向于新兴领域带来的便利性和灵活性了&#xff0c;其中&#xff0c;微服务平台就是其中之一了。流辰信息微服务平台是专注于研发系统开发、数据治理、数据分析的平台&#xff0c;致力于为各中大小型企业提供优质的微服务解决…

Java——字符串的排列

题目链接 牛客网在线oj题——字符串的排列 题目描述 输入一个长度为 n 字符串&#xff0c;打印出该字符串中字符的所有排列&#xff0c;你可以以任意顺序返回这个字符串数组。 例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。 数…

打造卓越游戏 | 2023 Google 游戏开发者峰会

一款游戏从初始构想的开发到辉煌赛季的策划&#xff0c;开发者们每时每刻都在倾注心血潜心钻研&#xff0c;Google 也致力于在整个开发和发布生命周期中为您提供帮助。我们很高兴能在今年如约而至的 Google 游戏开发者峰会中与您分享诸多更新&#xff0c;展示我们为助力您打造精…

如何有效的开展接口自动化测试,一篇就行

一、简介 接口自动化测试是指使用自动化测试工具和脚本对软件系统中的接口进行测试的过程。其目的是在软件开发过程中&#xff0c;通过对接口的自动化测试来提高测试效率和测试质量&#xff0c;减少人工测试的工作量和测试成本&#xff0c;并且能够快速发现和修复接口错误&…

PCL点云库(2) — IO模块

目录 2.1 IO模块接口 2.2 PCD数据读写 &#xff08;1&#xff09; PCD数据解析 &#xff08;2&#xff09;PCD文件读写示例 2.3 PLY数据读写 &#xff08;1&#xff09;PLY数据解析 &#xff08;2&#xff09;PLY文件读写示例 2.4 OBJ数据读写 &#xff08;1&#xff…

C语言指针2大问题:指针类型有什么用?指针如何运算?

如题&#xff0c;本篇博客主要解决2个疑点&#xff1a;指针类型的用处&#xff0c;指针如何运算。 1.指针类型 C语言中的指针类型&#xff0c;在X86环境下大小是4个字节&#xff0c;在X64环境下大小是8个字节。既然指针的大小和指针类型无关&#xff0c;那么指针类型究竟有什么…

银行系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 ​​​​​​​ 本系统源码地址&#xff1a;​​​​​​​https://download.csdn.net/download/qq_50…

从零开始写ChatGLM大模型的微调代码

cursor 的下载及安装&#xff08;免费版每月100次&#xff0c;升级pro 20刀/月&#xff09; cursor是一款与openai合作的&#xff0c;使用gpt-4的一款编程工具&#xff0c;它可以让你通过gpt-4进行辅助编程&#xff0c;以此提高效率。 下载地址&#xff1a;https://www.curso…

USART串口协议和USART串口外设(USART串口发送串口发送和接收)

1、通信接口 • 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 • 通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 异步&#xff1a;需要双方约定一个频率 2、 硬件电路 • 简单双向串口通信有两根通信…

【Unity-ML】Unity机器学习(一)

安装环境&#xff1a;Windows10 Anaconda3(64-bit)&#xff0c;网上很多教程&#xff0c;例如这个anaconda下载及安装(保姆级教程) - 知乎anaconda包管理器和环境管理器&#xff0c;强烈建议食用 1.下载官网下载太慢可选用镜像下载 官网下载&#xff1a; Anaconda | Individua…

〖ChatGPT实践指南 - 零基础扫盲篇④〗- OpenAI API 相关介绍、提示-Prompt 与 完成-Completion

文章目录 ⭐ OpenAI API介绍⭐ 提示-Prompt 与 完成-Completion 介绍 这一章节将为各位小伙伴介绍一下 OpenAI 的 API 相关内容&#xff0c;以及在 ChatGPT 中两个经常被用来比较的名词&#xff1a;“提示-prompt” 与 “完成-completion”。 ⭐ OpenAI API介绍 OpenAI API 概…

JavaScript常用方法整理

文章目录 前言1.栈方法&#xff1a;push()、pop()2.队列方法&#xff1a;unshift()、shift()3.indexof()、lastIndexOf()、includes()4.操作方法&#xff1a;concat()、slice()、splice()5.Array.isArray()6.排序方法:sort()、reverse()7.转换方法&#xff1a;toString()、join…

【Winform学习笔记(二)】TextBox文本框实现按回车键触发Button事件

TextBox文本框实现按回车键触发Button事件 前言正文1、实现方法2、具体代码3、实现效果 前言 在本文中主要介绍 如何基于 Winform 框架实现 TextBox 文本框实现按回车键触发 Button 事件&#xff0c;该功能可实现在文本框中输入密码后不需要按登录或确定按钮&#xff0c;直接回…

vue页面内嵌iframe使用postMessage进行数据交互(postMessage跨域通信)

什么是postMessage postMessage是html5引入的API,它允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案. vue父页面&#xff08;嵌入iframe的页面&#xff09; 在vue中…