STM32F4按键状态机--单击、双击、长按

STM32F4按键状态机--单击、双击、长按

  • 一、状态机的三要素
  • 二、使用状态机原因
    • 2.1资源占用方面
    • 2.2 执行效率方面:
    • 2.3 按键抖动方面:
  • 三、状态机实现
    • 3.1 状态机分析
    • 3.1 程序实现

百度解析的状态机概念如下

状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。有限状态机简写为FSM(Finite State Machine),主要分为2大类:

第一类,若输出只和状态有关而与输入无关,则称为Moore状态机
第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
在这里插入图片描述

简而言之,状态机是使不同状态之间的改变以及状态时产生的相应动作的一种机制。

一、状态机的三要素

状态机,或称有限状态机FSM(Finite State Machine),是一种重要的编程思想。

状态机有3要素:状态、事件与响应

状态:系统处在什么状态?

事件:发生了什么事?

响应:此状态下发生了这样的事,系统要如何处理?

状态机编程前,首先要根据需要实现的功能,整理出一个对应的状态转换图(状态机图),然后就可以根据这个状态转换图,套用状态机编程模板,实现对应是状态机代码了。

状态机编程主要有 3 种方法:switch-case 法、表格驱动法、函数指针法,本篇先介绍最简单也最易理解的switch-case 法。

二、使用状态机原因

举一个简单的例子,在实现按键扫描常常有三种方式

(1)轮询方式:main函数大循环中加入按键扫描函数key_scan(),相信大家最开始接触的也是这个
(2)中断方式:在单片机中大多数都支持外部中断,但每一个(或几个)IO口占用一个中断向量,使用非常方便。
(3)状态机方式:在我看来这种方式优于前两者任意一个,为什么呢?我们来来看看。

2.1资源占用方面

​ 轮询方式:在主循环中一直占用CPU的执行。

​ 中断方式:仅仅在时间产生后跳转执行后回调,相对于轮询占用的资源少很多,但很多按键时需要多个中断向量。

​ 状态机方式:在使用状态机实现按键扫描时,我们仅仅需要一个定时器即可实现任意个按键的扫描,效率不低于中断方式。

2.2 执行效率方面:

​ 轮询方式:效率极低,反应且不灵敏,有时候要按很多次才有反应。

​ 中断方式:效率较高,反应灵敏,单独的中断方式不支持长按等状态操作。

​ 状态机方式:效率较高,反应灵敏,支持长按的状态操作。

2.3 按键抖动方面:

​ 轮询方式:需要延时消抖,消抖的同时无法进行其他操作。

​ 中断方式:需要延时消抖,消抖的同时无法进行其他操作。

​ 状态机方式:间接的产生了消抖,为什么这么说呢?这里我们采用一个定时时间为50ms的定时器,每50ms进行一次状态的处理,在上一次处理和下一次处理的50ms中可以跳出定时器中断进行其他操作,也就是消抖的同时在进行其他的操作,大大的提高了运行的效率。

三、状态机实现

3.1 状态机分析

以下相关技术参考:B站-码农爱学习博主相关,如果不妥,请联系博主删文。

先看按实现单击、双击、长按状态图
在这里插入图片描述
状态机分析需要注意事项:
(1)“确认按下”不是短按触发的条件,需要等松开后,经消抖进入到“等待再次按下”一段时间后(200ms),没有再次被按下,才触发短按事件。

(2)“确认按下”不是短按触发的条件,另一个用途是,当此状态继续保持按下状态一段时间后(1s),则会单独触发长按事件,同时进入到“确认长按”状态,这样就解决了本篇开头提到的第2个问题

(3)对于双击事件的检测,首先按下按键进入“确认按下”状态,然后在1s内松开进入“等待再次按下”状态,接着在200ms内再次按下进入“确认第2次按下”状态,然后在1s内松开,即可触发双击事件,并同时进入“稳定松开”状态

注意,在“确认第2次按下”状态下,如果在1s内没有松开,也会进入到“确认长按”状态。

3.1 程序实现

硬件连接
在这里插入图片描述
key_state.h

#ifndef __KEY_STATE_H
#define __KEY_STATE_H

#include "stm32f4xx.h"
#include "sys.h"


#define KEY0  PAin(0)




void key_state_init(void);
u8 key_status_check(void);




#endif

key_state.c

#include "key_state.h"


typedef enum
{
	KE_SHORT_PRESS,
	KE_DOUBLE_PRESS,
	KE_LONG_PRESS,
	KE_OTHER,
}KEY_EVENT;


typedef enum
{
	KS_RELEASE,				//0-稳定松开状态
	KS_SHAKE,				//1-抖动
	KS_AFFIRM_SHORT_PRESS,	//2-确认按下
	KS_WAIT_PRESS_AGAIN,	//3-等待再次按下
	KS_AFFIRM_PRESS_AGAIN,	//4-确认第2次按下
	KS_AFFIRM_LONG_PRESS,   //5-确认长按
	
}KEY_STATUS;

KEY_STATUS g_keyStatus 		= KS_RELEASE; //当前循环结束的(状态机的)状态
KEY_STATUS g_nowKeyStatus 	= KS_RELEASE; //当前状态(每次循环后与g_keyStatus保持一致)
KEY_STATUS g_lastKeyStatus 	= KS_RELEASE; //上次状态(用于记录前一状态以区分状态的来源)

u16 g_PressTimeCnt = 0;  		//第一次按下时间计数
u16 g_Press2TimeCnt = 0;		//第二次按下时间计数
u16 g_WaitPressAgainCnt = 0;	//再次按下时间计数
u16 g_LongPressTimeCnt = 0;		//长按时间计数

u8 g_value = 5;



 
 
 
 
/******************************************
定时器说明

TIM3 -- APB1(16位定时器)

TIM3定时器频率:84MHZ
*******************************************/
void Tim3_state_Init(void)
{
	
	TIM_TimeBaseInitTypeDef		TIM_TimeBaseInitStruct;
	NVIC_InitTypeDef   			NVIC_InitStructure;
	
	//1、能定时器时钟。
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	TIM_TimeBaseInitStruct.TIM_Prescaler	= (84-1);  //84分频 84MHZ/84 = 1MHZ
	TIM_TimeBaseInitStruct.TIM_Period		= (50000-1);//计1000个数产生中断, 在1MHZ,1ms产生中断
	TIM_TimeBaseInitStruct.TIM_CounterMode  = TIM_CounterMode_Up; //向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision= TIM_CKD_DIV1;//分频因子
	
	//2、初始化定时器,配置ARR,PSC。
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	//配置NVIC
	NVIC_InitStructure.NVIC_IRQChannel 					 = TIM3_IRQn; //中断通道,中断的通道只能在stm32f4xx.h查找
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority 		 = 0x01; //响应优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd 				 = ENABLE; //通道使能
	NVIC_Init(&NVIC_InitStructure);
	
	//4、设置 TIM3_DIER  允许更新中断
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	
	//5、使能定时器。
	TIM_Cmd(TIM3, ENABLE);

}

/***********************************
按PA0做为状态机按键

TIM3每隔10ms检测一次按键状态

***********************************/

void key_state_init(void)
{
	//5ms中断一次
	Tim3_state_Init();
	
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	//使能GPIOA组时钟
	 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
	
	
	GPIO_InitStruct.GPIO_Pin	= GPIO_Pin_0; 		//引脚
	GPIO_InitStruct.GPIO_Mode	= GPIO_Mode_IN;		//输出
	GPIO_InitStruct.GPIO_PuPd	= GPIO_PuPd_UP;		//上拉

	GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.GPIO_Pin	= GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; 		//引脚
	GPIO_InitStruct.GPIO_Mode	= GPIO_Mode_IN;		//输出
	GPIO_InitStruct.GPIO_PuPd	= GPIO_PuPd_UP;		//上拉

	GPIO_Init(GPIOE, &GPIO_InitStruct);
	
}



//50ms检测一次
u8 key_status_check(void)
{
	
	switch(g_keyStatus)
	{
		//按键释放(初始状态)
		case KS_RELEASE:  
		{
			//检测到低电平,先进行消抖
			if (KEY0 == 0)
			{
				g_keyStatus = KS_SHAKE;
			//	g_lastKeyStatus = KS_SHAKE;
			}
		}
		break;

		//抖动
		case KS_SHAKE:
		{
			if (KEY0 == 1) //松开判断
			{
				//从松开状态来的抖动
				if (KS_RELEASE == g_lastKeyStatus)
				{
					g_keyStatus = KS_RELEASE;
				}
				//从等待再次按下状态来的抖动
				else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_keyStatus = KS_WAIT_PRESS_AGAIN;
				}
				//从确认按下状态来
				else if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
				{
					g_WaitPressAgainCnt = 0;
					g_keyStatus = KS_WAIT_PRESS_AGAIN;
				}
				//从确认再次按下状态来
				else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
				{
					
					printf("=====> key double press\r\n");
					g_value = 1;
					g_keyStatus = KS_RELEASE;
					return g_value;
				}
				//从确认长按状态来
				else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
				{
					
					g_keyStatus = KS_RELEASE;
				}
				else
				{
					printf("err!\r\n");
				}
			}
			else
			{
				//从确认按下状态来的抖动
				if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_SHORT_PRESS;
				}
		        //从第2次按下状态来的抖动
				else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
				}
				//从确认长按状态来的抖动
				else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_LONG_PRESS;
				}
				//从松开状态而来
				else if (KS_RELEASE == g_lastKeyStatus)
				{
					g_PressTimeCnt = 0;
					g_keyStatus = KS_AFFIRM_SHORT_PRESS;
					//printf("=====> key short press\r\n");
					//return KE_SHORT_PRESS;
				}
				//从等待再次看下(的松开)状态而来
				else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_Press2TimeCnt = 0;
					g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
				}
				else
				{
					printf("err!\r\n");
				}
			}
		}
		break;

		//确认按下
		case KS_AFFIRM_SHORT_PRESS:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
			}
			else
			{
				if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
				{
					g_value = 2;
					printf("=====> vt key long press:%d\r\n", g_LongPressTimeCnt/20);
					g_keyStatus = KS_AFFIRM_LONG_PRESS;
					return g_value;
				}
				g_LongPressTimeCnt++;
				
			}
		}
		break;

		//等待再次按下
		case KS_WAIT_PRESS_AGAIN:
		{
			//检测到低电平,先进行消抖
			if (KEY0 == 0)
			{
				g_keyStatus = KS_SHAKE;
			}

			g_WaitPressAgainCnt++;
			if (g_WaitPressAgainCnt == 4) //200ms没有再次按下
			{
			//	g_WaitPressAgainCnt = 0;
				printf("=====> key single press\r\n");
				g_value = 0;
				g_keyStatus = KS_RELEASE;
				return g_value;
			}
		}
		break;

		//确认第2次按下
		case KS_AFFIRM_PRESS_AGAIN:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
			}

			g_Press2TimeCnt++;
			if (g_Press2TimeCnt == 20) //1000ms
			{
				g_LongPressTimeCnt = 0;
				g_keyStatus = KS_AFFIRM_LONG_PRESS;
			}
		}
		break;

	    //确认长按
		case KS_AFFIRM_LONG_PRESS:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
				break;
			}

			g_LongPressTimeCnt++;
			if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
			{
				g_WaitPressAgainCnt = 0;
				g_value = 2;
				printf("=====> key long press:%d\r\n", g_LongPressTimeCnt/20);
				
				return g_value;
			}
		}
		break;

		default:break;
	}

	if (g_keyStatus != g_nowKeyStatus)
	{
		g_lastKeyStatus = g_nowKeyStatus;
		g_nowKeyStatus = g_keyStatus;
		
		g_value = 3;
	//	printf("new key status:%d(%s)\r\n", g_keyStatus, key_status_name[g_keyStatus]);
	}
	
	return 8; //返回其它的值,也就是按键未有动作的值
}



//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志

		g_value = key_status_check();
		switch (g_value)
		{
			case 0:  printf("检测到单击\r\n"); break;
			case 1: printf("检测到双击\r\n"); break;
			case 2:   printf("检测到长按\r\n"); break;
			default:break;
		}
		

	}
}



main.c

#include "stm32f4xx.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "delay.h"
#include "tim.h"
#include "pwm.h"
#include "usart.h"
#include "string.h"
#include "sr04.h"
#include "dht11.h"
#include "key_state.h"

u8 g_data;
u8 g_flag = 0, g_count = 0;
u8 g_buffer[32] = {0};
u8 g_rxbuffer[32] = {0};

void USART1_IRQHandler(void)
{
	//判断串口接收标志位是否置1
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);	
		//从串口1接收数据
		g_buffer[g_count++] = USART_ReceiveData(USART1); 
		//判断接受的字符是否为':'
		if(g_buffer[g_count-1] == ':')
		{
			//数据重新存放在g_rxbuffer,并过滤结束符':'
			for(int i = 0; i < g_count-1; i++)
			{
				g_rxbuffer[i] = g_buffer[i];
			}
			
			memset(g_buffer, 0, sizeof(g_buffer));
		
			g_flag = 1;  //表示一帧数据(HCL11:或者 HCL10:)接受完毕
			
			g_count = 0; //一帧数据结束后,g_count置为0,下一帧数据从g_buffer[0]开始接受数据
		}
	

	}
	
}





//粗延时
void delay(int n)
{
	int i, j;
	
	for(i=0; i<n; i++)
	{
		for(j=0; j<30000; j++)
		{
		
		}
	}
			
}

int main(void)
{
	int ret;
	//设置NVIC分组(一个项目只能配置一次)
	//第2分组,抢占优先级范围:0~3  响应优先级范围:0~3
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	Delay_Init();

	Led_Init();
	
	Usart1_Init(115200);

	key_state_init();
	


	while(1)
	{

		delay_s(2);
	}
	
    return 0;
}


实验效果
在这里插入图片描述

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

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

相关文章

SQL【2】稍稍进阶

目录 首先&#xff0c;怎么安装&#xff0c;环境怎么调。 alter——改变&#xff08;此段Al&#xff09; 创建于删除数据库、表格 上一节进阶 预设值default​编辑 关于插入顺序 有条件的删除DELETE FROM 表 WHERE 条件 多种语句组合查看 查看排序​编辑 LIMIT 2只取前…

Leetcode面试经典150题-221.最大正方形

解法都在代码里&#xff0c;不懂就留言或者私信 class Solution {/**本题一看就是典型的动态规划&#xff0c;要找以每个点为右下角的正方形的面积&#xff0c;然后取最大的这个题要注意找规律&#xff0c;我找到的规律如下&#xff1a;1.以第一行为右下角的&#xff0c;因为正…

ADC——模数转换器

一、转换流程 在处理器中主要进行ADC 1、AD转换流程 &#xff1a;采样、保持、量化、编码 通过比较器获得的电信号转换数字信号&#xff0c;根据自己需求&#xff0c;如果要求速率就可以使用较多的比较器&#xff0c;不要求速率考虑成本就可以使用较少的比较器&#xff0c;将最…

Vue学习笔记 二

4、Vue基础扩展 4.1 插槽 组件的最大特性就是复用性&#xff0c;而用好插槽能大大提高组件的可复用能力在Vue中插槽是很重要的存在&#xff0c;通过插槽&#xff0c;我们可以把父组件中指定的DOM作用到子组件的任意位置&#xff0c;后面我们坐项目用到的组件库比如element-ui…

5、Django Admin后台移除“删除所选”操作

默认情况下&#xff0c;Django Admin后台的listview模型列表页&#xff0c;会有一个Delete Selected删除所选操作。假设你需要再从Hero管理模型中移除该删除操作。 ModelAdmin.get_actions方法可以返回所有的操作方法。通过覆盖此方法&#xff0c;移除其中delete_selected方法…

Python 优雅编程:会报恩的代码(五)

文章目录 引言从文本搜索指定单词&#xff0c;不区分单词的大小写使用 str.lower()使用 re 模块 从文本搜索多个单词&#xff0c;依旧不区分单词的大小写使用 str.lower() 和循环使用 re 模块 反复执行 re.compile&#xff0c;re 是否会缓存编译结果&#xff1f;结语 引言 在 …

苹果mac数据恢复概率大吗 mac数据恢复专业软件哪个好用

一般情况下&#xff0c;当我们把电脑中的数据删掉后&#xff0c;都会保存在回收站里面&#xff0c;但如果回收站被清空了或者数据在回收站中没有找到的话&#xff0c;那么&#xff0c;之前被删掉的数据还能恢复吗&#xff1f;恢复的概率有多大呢&#xff1f; 答案是可以的&…

链式栈、队列

1、链式栈&#xff1a; 声明&#xff1a; #ifndef _STACK_H #define _STACK_H #include<stdlib.h>typedef int DataType;typedef struct snode //节点 {DataType data;struct snode *pnext; }SNode_t;typedef struct stack //链表 {SNode_t *ptop;int clen; }St…

【python】—— Python爬虫实战:爬取珠海市2011-2023年天气数据并保存为CSV文件

目录 目标 准备工作 爬取数据的开始时间和结束时间 爬取数据并解析 将数据转换为DataFrame并保存为CSV文件 本文将介绍如何使用Python编写一个简单的爬虫程序,以爬取珠海市2011年至2023年的天气数据,并将这些数据保存为CSV文件。我们将涉及到以下知识点: 使用r…

金九银十,自动化测试面试题精选【美团二面】

面试一般分为技术面和hr面&#xff0c;形式的话很少有群面&#xff0c;少部分企业可能会有一个交叉面&#xff0c;不过总的来说&#xff0c;技术面基本就是考察你的专业技术水平的&#xff0c;hr面的话主要是看这个人的综合素质以及家庭情况符不符合公司要求&#xff0c;一般来…

Kubernetes 上安装 Jenkins

安装 Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash添加 Jenkins Helm 仓库 首先添加 Jenkins Helm 仓库 helm repo add jenkins https://charts.jenkins.io helm repo update安装 Jenkins 使用 Helm 安装 Jenkins 的最新版本&…

uni-app应用更新(Android端)

关于app更新&#xff0c;uni-app官方推荐的是 uni-upgrade-center&#xff0c;看了下比较繁琐&#xff0c;因此这里自己实现检查更新并下载安装的逻辑。 1.界面效果 界面中的弹框和 进度条采用了uView 提供的组件 2.检查更新并下载安装 一、版本信息配置在服务端&#xff0c…

时装爱好者的网页购物天堂:Spring Boot技术探索

第2章相关技术 2.1 B/S架构 B/S结构的特点也非常多&#xff0c;例如在很多浏览器中都可以做出信号请求。并且可以适当的减轻用户的工作量&#xff0c;通过对客户端安装或者是配置少量的运行软件就能够逐步减少用户的工作量&#xff0c;这些功能的操作主要是由服务器来进行控制的…

详解TensorRT的C++高性能部署以及C++部署Yolo实践

详解TensorRT的C高性能部署 一. ONNX1. ONNX的定位2. ONNX模型格式3. ONNX代码使用实例 二、TensorRT1 引言 三、C部署Yolo模型实例 一. ONNX 1. ONNX的定位 ONNX是一种中间文件格式&#xff0c;用于解决部署的硬件与不同的训练框架特定的模型格式的兼容性问题。 ONNX本身其…

JAVA数据导出为Excel

目录 一、导入依赖 二、使用的相关类 1、XSSFWorkbook 构造方法 创建表 操作表 保存表 样式和格式 日期处理 密码保护 其他 2、XSSFSheet 获取属性和信息 行操作 列操作 表的属性 合并单元格 保护表 页眉和页脚 注释 其它 3、XSSFRow 获取属性和信息 单…

单点登录OAuth2.0

OAuth 2.0&#xff08;Open Authorization 2.0&#xff09;是OAuth协议的第二个版本&#xff0c;于2012年正式成为RFC 6749标准。在OAuth 2.0之前&#xff0c;OAuth 1.0版本已经为Web应用提供了一定程度的授权功能&#xff0c;但随着时间的推移&#xff0c;这些版本逐渐显露出一…

Nginx: TCP建立连接的优化和启用Fast Open功能

TCP 建立连接优化 在三次握手中&#xff0c;相关TCP的内核参数可优化这一过程 net.ipv4.tcp_syn_retries 6net.ipv4.tcp_synack_retries 5net.ipv4.tcp_syncookies 0net.ipv4.tcp_max_syn_backlognet.core.somaxconnnet.core.netdev_max_backlog 1 &#xff09; net.ipv4…

C语言之猜数字小游戏

哈喽&#xff0c;大家好&#xff01;我是冰淇淋加点糖。今天我们来用前面所学的知识来开发一个猜数字的小游戏&#xff0c;锻炼我们的编程能力和编程思维。 猜数字小游戏功能简介 1.随机生成一个1-100的数字。 2.玩家用户开始猜数字。 > 猜大了&#xff0c;提醒猜大了…

【知识库系列】MPR/多模态方向观察:图像视频与3D生成

多模态背后的backbone会长成什么样&#xff1f; 各种模态到梯度下降到最后会不会都差不多&#xff1f; Sora 是不是已经被追上了? 我们真的把视频数据都用好了吗&#xff1f; 知识库完整文档&#xff1a; MPR/多模态方向观察&#xff1a;图像视频与3D生成&#xff1a;https…

SpringBoot实现前后端传输加密设计

在Web应用中&#xff0c;确保前后端之间的数据传输安全是非常重要的。这通常涉及到使用HTTPS协议、数据加密、令牌验证等安全措施。本文通过将前后端之间的传输数据进行加密&#xff0c;用于在Spring Boot应用中实现前后端传输加密设计。 一、数据加密方案 即使使用了HTTPS&…