Springboot数据加密篇

一、密码加密

1.1Hash算法(MD5/SHA-512等)

哈希算法,又称摘要算法(Digest),是一种将任意长度的输入通过散列函数变换成固定长度的输出的单向密码体制。这种映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。

哈希算法最重要的特点就是:相同的输入一定得到相同的输出,不同的输入可能得到相同的输出,但不可能通过输出反推出原始输入。这意味着哈希算法可以用于快速比较两个数据是否相同,常用于密码存储、数字签名、数据完整性校验等领域。

Hash算法特性 

  • 唯一性。数据通过hash算法计算的hash值是唯一的
  • 压缩性。例如,任意长度的数据,算出的MD5值的长度是固定的(128位二进制数,32位十六进制数)
  • 不可逆。无法从结果复原源数据信息
  • 抗修改。对原数据的任何改动,hash值完全不同
  • 强抗碰撞。伪造数据非常困难
  • 容易计算。从原数据计算出值很容易

Hash算法无法转换回源数据,因此是签名算法,不是加密/解密算法(无解密)

即,仅判断是不是源数据,不知道源数据是什么。因此适合,验证敏感源数据的正确性。例如,验证密码(如何判断密码正确?) 

 Hash算法缺点

 1.2加Salt算法

Salt(盐)是在密码学中常用的一种安全措施,其本质是一段随机的字符串。在密码加密过程中,Salt会被添加到原始密码中,再通过散列函数进行散列,最终生成一个唯一的散列值。

Salt的主要作用是增加破解密码的难度,因为即使两个用户使用了相同的密码,由于Salt的存在,他们的密码散列值也会不同。这就意味着,攻击者即使获取了数据库中的密码散列值,也无法通过简单的对比找到匹配的原始密码。

 1.3Spring-Security

Spring提供了一套安全框架,处理加密/解密数据信息 提供了包括对称/非对称加密,不同Hash算法等一系列实现

 相关配置

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

相关接口

PasswordEncoder(org.springframework.security.crypto.password)接口

PasswordEncoder接口是Spring Security中用于对密码进行加密的接口。它提供了一种通用的方法来将明文密码转换为加密后的密码,以便在存储和验证过程中使用。

  • String encode(CharSequence rawPassword),编码密码
  • boolean matches(CharSequence rawPassword, String encodedPassword),验证原始密码与编码密码
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class PasswordEncoderExample {public static void main(String[] args) {// 创建PasswordEncoder实例BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 原始密码String plainPassword = "myPassword";// 加密密码String encodedPassword = passwordEncoder.encode(plainPassword);System.out.println("原始密码: " + plainPassword);System.out.println("加密后的密码: " + encodedPassword);}
}

 Pbkdf2PasswordEncoder类,Pbkdf2算法

 Pbkdf2PasswordEncoder类是Spring Security中的一个密码编码器,它使用PBKDF2算法对密码进行加密。PBKDF2是一种密钥导出函数,它可以从用户输入的密码生成一个足够复杂的密钥,以保护存储在数据库中的密码。

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class PasswordEncoderExample {public static void main(String[] args) {// 创建Pbkdf2PasswordEncoder对象BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 原始密码String plainPassword = "myPassword";// 使用Pbkdf2算法加密密码String encodedPassword = passwordEncoder.encode(plainPassword);System.out.println("原始密码: " + plainPassword);System.out.println("加密后的密码: " + encodedPassword);}
}

 BCryptPasswordEncoder类,Bcrypt算法

BCryptPasswordEncoder类是Spring Security中的一个密码编码器,它使用Bcrypt算法对密码进行加密。Bcrypt是一种加密算法,它可以生成一个足够复杂的哈希值来保护存储在数据库中的密码。自动生成随机盐值,并附在结果,避免盐值的单独保存

  • 128bits随机二进制数,16bytes,base64,24chars,特殊算法转为22chars
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class PasswordEncoderExample {public static void main(String[] args) {// 创建BCryptPasswordEncoder对象BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 原始密码String plainPassword = "myPassword";// 使用Bcrypt算法加密密码String encodedPassword = passwordEncoder.encode(plainPassword);System.out.println("原始密码: " + plainPassword);System.out.println("加密后的密码: " + encodedPassword);}
}

1.4实现

配置类

package com.passwordencoder;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
public class SecurityConfiguration {// 定义一个名为getPasswordEncoder的方法,返回类型为PasswordEncoder@Beanpublic PasswordEncoder getPasswordEncoder() {// 创建一个新的BCryptPasswordEncoder对象并返回return new BCryptPasswordEncoder();}
}

状态类

package com.passwordencoder.vo;import lombok.Builder;
import lombok.Data;import java.util.Map;@Data
@Builder
public class ResultVO {private int code;private String message;private Map<String, Object> data;public static ResultVO success(Map<String, Object> data) {return ResultVO.builder().code(200).data(data).build();}public static ResultVO error(int code, String msg) {return ResultVO.builder().code(code).message(msg).build();}
}

实体类

package com.passwordencoder.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User04 {private String userName;private String password;
}

服务类

package com.example.springmvcexamples.example04.passwordencoder.service;import com.example.springmvcexamples.example04.passwordencoder.entity.User04;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Slf4j // 使用lombok的注解,简化代码并自动生成getter、setter等方法
@Service // 标记这是一个Spring服务类
public class UserService04 {// 根据用户名获取用户信息的方法public User04 getUser(String userName) {// 如果用户名为"BO",则返回一个包含用户名和加密密码的用户对象return "BO".equals(userName)? User04.builder() // 使用User04的构建器模式创建一个新的用户对象.userName("BO") // 设置用户名为"BO".password("$2a$10$A7OcKw5xxRMh9c4ghWySr.Rjh22gpWyiWExZO5i2B32eJLQrFXcr6") // 设置加密后的密码.build() // 构建并返回用户对象: null; // 如果用户名不为"BO",则返回null}
}

处理类

package com.passwordencoder.controller;import com.passwordencoder.entity.User04;
import com.passwordencoder.service.UserService04;
import com.passwordencoder.vo.ResultVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
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;import java.util.Map;@Slf4j
@RestController
@RequestMapping("/api/example04/")
@RequiredArgsConstructor
public class ExampleController04 {private final UserService04 userService;private final PasswordEncoder passwordEncoder;@PostMapping("login")public ResultVO login(@RequestBody User04 user) {// 先查询用户是否存在User04 u = userService.getUser(user.getUserName());if (u == null || !passwordEncoder.matches(user.getPassword(), u.getPassword())) {log.debug("登录失败");return ResultVO.error(401, "用户名密码错误");}// 登录成功,添加token等操作log.debug("登录成功");return ResultVO.success(Map.of("user", u));}
}

测试

POST http://localhost:8081/api/example04/login
Content-Type: application/json{"userName": "BO","password": "12345"
}

二、序列化与反序列化

  • SpringMVC默认基于Jackson实现序列化/反序列化
  • SpringMVC自动注入Jackson ObjectMapper映射对象到容器
    • String writeValueAsString(T payload),将对象序列化为json字符串
    • T readValue(String content, Class c),将json字符串反序列化为指定类型的Java对象

TypeReference<T>抽象类。创建子类,具体化泛型。可通过创建类似接口的匿名内部类实现

三、token令牌

 3.1概述

Token令牌是一种用于身份验证和授权的凭证,通常由服务器生成并发送给用户。它包含有关用户的信息,例如用户名、角色等,以及一些会话信息,例如过期时间等。当用户尝试访问受保护的资源时,他们需要提供有效的Token令牌以证明其身份和权限。服务器将验证Token令牌的有效性,并根据其中包含的信息授予或拒绝用户的请求。

Restful设计思想,服务器端不再保存用户状态(无HttpSession)

  • 用户登录后,将用户身份/权限信息封装在Token(令牌)
  • ·将token信息加密(Authorization)通过http header返给客户端
  • ·客户端每次需要身份/权限的请求,均需在http header携带Authorization
  • ·服务器端拦截权限请求,从Authorization中解密出Token权鉴

实现

  • ·JWT。流行的认证标准,信息由header/payload/,signature组成,多种实现
  • ·自定义Token。更灵活,数据量小

3.2适合敏感数据的加密传输 

加密/解密算法,适合敏感数据的加密传输

  • 对称加密算法(AES等),通过相同密钥加密/解密
  • 非对称加密算法(RSA等),公钥加密的数据,必须通过私钥才能解密 

密钥生成器

my:secretkey: R28K42ZEJ8LWRHU5salt: 636eac2534bcfcb0

实体类

package com.example.springmvcexamples.example05;import lombok.Data;@Data
public class MyToken {public enum Role{USER, ADMIN}private Integer uid;private Role role;
}

组件类

package com.example.springmvcexamples.example05.textencryptor;import com.example.springmvcexamples.example02.handlingexception.exception.MyException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.Map;@Component
public class EncryptComponent05 {private final ObjectMapper objectMapper;@Value("${my.secretkey}")private String secretKey;@Value("${my.salt}")private String salt;private TextEncryptor encryptor;public EncryptComponent05(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}/*** 直接基于密钥/盐值创建单例TextEncryptor对象。避免反复创建*/@PostConstructpublic void getTextEncryptor() {encryptor = Encryptors.text(secretKey, salt);}public String encrypt(Map<String, Object> payload) {try {String json = objectMapper.writeValueAsString(payload);return encryptor.encrypt(json);} catch (JsonProcessingException e) {throw new MyException(500, "服务器端错误");}}/*** 无法验证/解密/反序列化,说明数据被篡改,判定无权限* @param auth* @return*/public Map<String, Object> decrypt(String auth) {try {String json = encryptor.decrypt(auth);return objectMapper.readValue(json, Map.class);} catch (Exception e) {throw new MyException(403, "无权限");}}
}

@Component注解

`@Component`注解是Spring框架中的一个注解,用于标记一个类作为Spring容器中的组件。当Spring容器启动时,会自动扫描带有`@Component`注解的类,并将这些类实例化为对象,然后将这些对象存储在Spring容器中,以便在其他组件中通过依赖注入的方式使用。

测试一

package com.example.springmvcexamples.example05;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;import java.util.HashMap;
import java.util.Map;@SpringBootTest
@Slf4j
public class EncryptorTest {// 自动注入ObjectMapper对象,用于将对象转换为JSON字符串和将JSON字符串转换为对象@Autowiredprivate ObjectMapper objectMapper;// 从配置文件中获取加密密钥@Value("${my.secretkey}")private String secretKey;// 从配置文件中获取盐值@Value("${my.salt}")private String salt;// 测试方法@Testpublic void test_encrypt() {// 创建加密器,使用密钥和盐值进行加密TextEncryptor encryptor = Encryptors.text(secretKey, salt);try {// 创建一个包含uid和role的Map对象Map<String, Object> map = Map.of("uid", 1384896304762638307L, "role", 9);// 将Map对象转换为JSON字符串String json = objectMapper.writeValueAsString(map);// 使用加密器对JSON字符串进行加密String r = encryptor.encrypt(json);// 输出加密后的字符串log.debug(r);// 输出加密后的字符串长度log.debug("{}", r.length());// 再次使用加密器对JSON字符串进行加密,验证加密结果是否一致log.debug(encryptor.encrypt(json));// 使用加密器对加密后的字符串进行解密String reJson = encryptor.decrypt(r);// 将解密后的JSON字符串转换为Map对象Map<String, Object> reToken = objectMapper.readValue(reJson, Map.class);// 输出解密后的Map对象中的role值log.debug(reToken.get("role").toString());} catch (JsonProcessingException e) {e.printStackTrace();}}
}

测试二

package com.example.springmvcexamples.example05;import com.example.springmvcexamples.example05.textencryptor.EncryptComponent05;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.Map;@SpringBootTest
@Slf4j
public class TextEncryptorTest {@Autowiredprivate EncryptComponent05 encrypt;@Testpublic void test_encrypt() {
//        MyToken Token = new MyToken();
//        Token.setUid(1);
//        Token.setRole(MyToken.Role.ADMIN);Map<String, Object> map = Map.of("uid", 1384896304762638307L, "role", 9);String r = encrypt.encrypt(map);log.debug(r);log.debug("{}", r.length());log.debug(encrypt.encrypt(map));}@Testpublic void test_decrypt() {String auth = "b3a60e67dfcd220874e36569f623829ea97d556d646b4eb208c2f43" +"b452bbf61a3e5982e0a52810517bcc734a5561e2dc53a9e3854d5fd4afebf0b15b7c1ad5c";Map<String, Object> token = encrypt.decrypt(auth);log.debug("{}", token.get("uid"));log.debug("{}", token.get("role"));}
}

四、拦截器

HandlerInterceptor (org.springframework.web.servlet.HandlerInterceptor)接口

  • Boolean preHandle()方法:controller方法执行前回调,返回false,则不会继续执行。登录验证等
  • Void postHandle()方法:perHandle()返回true后,controller方法执行后
  • afterCompletion方法:postHandle()执行后,回调
  • Object handle,封装被拦截方法对象

拦截器类

InterceptorRegistry

  • addInterceptor(),添加拦截器组件
  • addPathPatterns(),添加拦截路径
  • excludePathPatterns(),添加排除路径
  • 可声明多个拦截器,按顺序拦截
package com.example.springmvcexamples.example06.interceptor.interceptor;import com.example.springmvcexamples.example02.handlingexception.exception.MyException;
import com.example.springmvcexamples.example05.textencryptor.EncryptComponent05;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;@Component // 将LoginInterceptor06类标记为Spring容器中的组件
@Slf4j // 使用log4j进行日志记录
@RequiredArgsConstructor // 通过构造函数注入依赖,避免在实例化时需要手动注入依赖
public class LoginInterceptor06 implements HandlerInterceptor {private final EncryptComponent05 encryptComponent; // 注入加密组件@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token"); // 从请求头中获取tokenif (token == null) {throw new MyException(401, "未登录"); // 如果token为空,抛出自定义异常}Map<String, Object> result = encryptComponent.decrypt(token); // 解密token,获取用户信息request.setAttribute("role", result.get("role")); // 将用户角色设置到请求属性中,以便后续处理中使用return true; // 返回true表示继续执行后续的拦截器和处理器}
}

 配置类

package com.example.springmvcexamples;import com.example.springmvcexamples.example06.interceptor.interceptor.LoginInterceptor06;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {private final LoginInterceptor06 adminInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(adminInterceptor).addPathPatterns("/api/example06/admin/**");}}

五、定时服务(Timer Service)

定时服务(Timer Service):用以调度安排所有基于定时通知的工作流程

支持指定时间、某一时间后、指定时间间隔内的定时通知

Spring Task Execution and Scheduling

  • 基于TaskExecutor,TaskScheduler接口
  • 基于注解
  • 基于Quartz Scheduler第三方库

@Scheduled

@Scheduled注解是Spring框架中的一个注解,用于标记一个方法为定时任务。它可以与@Scheduled注解一起使用,或者与TaskScheduler接口一起使用。

使用@Scheduled注解的方法会在指定的时间间隔内自动执行

定时任务方法声明在组件内,方法必须无返回值

  • fixedRate,每次执行间隔时间,即使上一任务未执行完依然执行任务(毫秒)
  • fixedDelay,每次执行完成到下一次开始执行间隔时间,即上一任务执行结束后,过指定时间执行任务(毫秒)
  • initialDelay,第一次执行前的延迟时间(毫秒)
  • Cron,指定执行时间表达式

Cron表达式

顺序:秒、分、时、日、月、星期(数字或单词缩写)、年(可省略,即每年)。值为数字或符号

默认不支持从后计算(不支持L) 

启动类

package com.example.springmvcexamples;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;@SpringBootApplication
@EnableScheduling
public class SpringmvcExamplesApplication {public static void main(String[] args) {SpringApplication.run(SpringmvcExamplesApplication.class, args);}
}

 @EnableScheduling注解

@EnableScheduling注解是Spring框架中的一个注解,用于开启定时任务功能。在Spring Boot应用中,可以通过在主类上添加该注解来启用定时任务。

组件类

package com.example.springmvcexamples.example07.timer;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class MyTimer {@Scheduled(cron = "0 0 8 10 * ?")public void paySalary() {log.debug("Your salary has been paid!");}
}

 

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

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

相关文章

STM32——串口通信应用篇

一、引言 STM32微控制器是一款功能强大的嵌入式系统芯片&#xff0c;广泛应用于各种领域。其中&#xff0c;串口通信是其重要功能之一&#xff0c;可用于与外部设备进行数据交换和控制。本文将介绍STM32串口通信的基本原理、应用场景以及实现方法。 二、STM32串口通信基本原理 …

三维模型轻量化工具

老子云三维模型服务平台&#xff1a;常规模型轻量化通过底层算法快速有效的对常规模型进行轻量化处理&#xff0c;目前包含两种处理模式&#xff1a;减面模式、合并模式。 减面模式&#xff1a;保留原始模型信息&#xff0c;仅使模型网格更轻量。合并模式&#xff1a;合并模型材…

探秘 AJAX:让网页变得更智能的异步技术(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Linux笔记---网络操作命令详细介绍

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 前言&#xff1a; 网络操作是Linux系统中常见的任务之一&#xff0c;它涵盖了测试网络连接、配置网络接口、显示网络统计信息以及远程登录和文件传…

178. 第K短路(A*启发式算法)

178. 第K短路 - AcWing题库 给定一张 N 个点&#xff08;编号 1,2…N&#xff09;&#xff0c;M 条边的有向图&#xff0c;求从起点 S 到终点 T 的第 K 短路的长度&#xff0c;路径允许重复经过点或边。 注意&#xff1a; 每条最短路中至少要包含一条边。 输入格式 第一行包…

测试工具Jmeter:界面介绍、核心选项说明、核心选项用途

本文章主要介绍Jmeter的界面布局&#xff0c;以及各个选项的功能和它们的用途。 JMeter基本原理是建立一个线程池&#xff0c;多线程运行取样器产生大量负载&#xff0c;在运行过程中通过断言来验证结果的正确性&#xff0c;通过监听器来记录测试结果。 1. Jmeter主界面 当我…

多维时序 | MATLAB实现SSA-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测

多维时序 | MATLAB实现SSA-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现SSA-CNN-LSTM-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现SSA-CNN-LST…

路由器设置代理IP教程,http代理怎么固定IP地址?

路由器设置代理IP教程 一、确定代理IP地址 首先&#xff0c;你需要确定你要使用的代理IP地址。你可以从代理服务提供商处获取代理IP地址和端口号。 二、登录路由器管理界面 在浏览器中输入路由器的IP地址&#xff0c;输入账号和密码&#xff0c;进入路由器的管理界面。 三、设置…

【MATLAB第83期】基于MATLAB的LSTM代理模型的SOBOL全局敏感性运用

【MATLAB第83期】基于MATLAB的LSTM代理模型的SOBOL全局敏感性运用 引言 在前面几期&#xff0c;介绍了敏感性分析法&#xff0c;本期来介绍lstm作为代理模型的sobol全局敏感性分析模型。 【MATLAB第31期】基于MATLAB的降维/全局敏感性分析/特征排序/数据处理回归问题MATLAB代…

基于单片机的火灾报警器 (论文+源码)

1.系统设计 本系统由火灾检测模块、A/D转换模块、信号处理模块、声光报警模块和灭火装置模块组成。火灾检测模块由温度检测和烟雾检测构成&#xff0c;其温度传感器选用DS18B20&#xff0c;烟雾传感器选用MQ-2烟雾传感器。A/D转换模块选用常用的模数转换芯片ADC0832。声光报警模…

比PPT还好用的翻页电子书

翻页电子书是一种将纸质书籍转化为电子设备的书籍形式。它具有与纸质书相同的外观和手感&#xff0c;但可以通过手指翻页&#xff0c;实现更加便捷的阅读体验。同时&#xff0c;翻页电子书还具有一些独特的功能&#xff0c;如插入图片、音频、视频等多媒体素材&#xff0c;让阅…

Nginx快速入门:Nginx应用场景、安装与部署(一)

1. Nginx简介 Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;也是一个非常流行的开源 Web 服务器软件。它是由俄罗斯程序员 Igor Sysoev 开发的&#xff0c;最初是为了解决在高并发场景下的C10k 问题&#xff08;即一个服务器进程只能处理 10,000 个并发连接&#x…

嵌入式中串口输入

学习目标 掌握串口初始化流程掌握串口接收逻辑了解中断接收逻辑熟练掌握串口开发流程学习内容 需求 串口接收PC机发送的数据。 串口数据接收 串口初始化 static void USART_config() {uint32_t usartx_tx_rcu = RCU_GPIOA;uint32_t usartx_tx_port = GPIOA;uint32_t usartx…

Leetcode 376 摆动序列

题意理解&#xff1a; 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 如果是摆动序列&#xff0c;前后差值呈正负交替出现 为保证摆动序列尽可能的长&#xff0c;我们可以尽可能的保留峰值&#xff0c;&#xff0c;删除上下坡的中间值&…

Mongodb复制集架构

目录 复制集架构 复制集优点 复制集模式 复制集搭建 复制集常用命令 复制集增删节点 复制集选举 复制集同步 oplog分析 什么是oplog 查看oplog oplog大小 复制集架构 复制集优点 数据复制: 数据在Primary节点上进行写入&#xff0c;然后异步地复制到Secondary节点&a…

14 v-model绑定输入框

概述 v-model用于实现双向数据绑定&#xff0c;使用v-model绑定输入框是Vue3中最常见的用法之一。 比如&#xff0c;在制作登录界面的时候&#xff0c;我们会使用v-model绑定用户名和密码&#xff0c;这里的用户名和密码都是输入框。 基本用法 我们创建src/components/Demo…

cleanmymacX安全吗?有用吗

苹果电脑以其流畅的操作系统和卓越的性能而备受用户喜爱。然而&#xff0c;随着时间的推移&#xff0c;系统可能会积累大量垃圾文件&#xff0c;影响性能。本文将介绍苹果电脑怎么清理垃圾文件的各种方法&#xff0c;以提升系统运行效率。 CleanMyMac X是一款专业的Mac清理软件…

Android Studio问题解决:Gradle Download 下载超时 Connect reset

文章目录 一、遇到问题二、解决办法 一、遇到问题 Gradle Download下载超时Sync了很多次&#xff0c;一直失败 二、解决办法 手动通过gradle网站下载 https://gradle.org/releases/可能也会出现超时&#xff0c;最好开个VPN软件会比较快。 下载好的软件&#xff0c;放到本机的…

工业缺陷检测新时代!OpenCV4六种方法助你轻松应对生产难题!

OpenCV4工业缺陷检测的六种方法 机器视觉缺陷检测好书推荐工业上常见缺陷检测方法方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;方法四&#xff1a;方法五&#xff1a;方法六&#xff1a; 写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 送书系列…

hive企业级调优策略之分组聚合优化

测试用表准备 hive企业级调优策略测试数据 (阿里网盘下载链接)&#xff1a;https://www.alipan.com/s/xsqK6971Mrs 订单表(2000w条数据) 表结构 建表语句 drop table if exists order_detail; create table order_detail(id string comment 订单id,user_id …