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.rhkb.cn/news/222467.html

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

相关文章

【爬虫软件】孔夫子二手书采集

项目演示 孔网爬取图书信息 目录结构 [ |-- api-ms-win-core-synch-l1-2-0.dll, |-- api-ms-win-core-sysinfo-l1-1-0.dll, |-- api-ms-win-core-timezone-l1-1-0.dll, |-- api-ms-win-core-util-l1-1-0.dll, |-- api-ms-win-crt-conio-l1-1-0.dll, |-- api…

初级数据结构(七)——二叉树

文中代码源文件已上传&#xff1a;数据结构源码 <-上一篇 初级数据结构&#xff08;六&#xff09;——堆 | NULL 下一篇-> 1、写在前面 二叉树的基本概念在《初级数据结构&#xff08;五&#xff09;——树和二叉树的概念》中已经介绍得足够详细了。上一…

java开发需要掌握的TypeScript相关的知识点,细致简洁版。

Typescript&#xff1a; 介绍&#xff1a; TypeScript&#xff08;简称 TS&#xff09;是JavaScript的超集&#xff08;继承了JS全部语法&#xff09;&#xff0c;TypeScript Type JavaScript。 简单说&#xff0c;就是在JS的基础上&#xff0c;为JS添加了类型支持。是微软开…

分享一套国内功能齐全的开源MES/免费MES/MES源代码

一、系统概述&#xff1a; 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、适合二开的开源MES、好看的数字大屏。 1.万界星空开源MES制造执行系统的Java开源版本。 开源mes系统包括系统管理&#xff0c;车间基础数据管理&#xff0c;计划管理…

大模型之二十一-小语言模型塞道开启

当前提到大语言模型&#xff0c;大家想到的都是动辄百亿规模以上参数量的模型&#xff0c;13B、70B都是稀疏平常入门级的&#xff0c;但是目前从模型层面来看&#xff0c;模型参数量的规模两极分化已经来临&#xff0c;早期各大公司为了效果怼上去&#xff0c;采取了简单粗暴的…

Jmeter 性能测试 —— 评估一个系统TPS与并发数!

问题&#xff1a;性能压测&#xff0c;如何评估一个系统的TPS和并发数&#xff1f; 1、对于新系统 由业务部门或开发人员预估交易量和TPS指标 可以参考公式&#xff1a;并发用户 在线用户数 * 10%。 当一个系统还没有上线时&#xff0c;我们可以预判的是这个系统准备要给多…

【数字图像处理】实验一 图像基本运算

图像基本运算 一、实验内容&#xff1a; 1&#xff0e; 熟悉和掌握利用Matlab工具进行数字图像的读、写、显示等数字图像处理基本步骤。 2&#xff0e; 熟练掌握各种图像点运算的基本原理及方法。 3&#xff0e; 能够从深刻理解点运算&#xff0c;并能够思考拓展到一定的应用领…

MatGPT - 访问 OpenAI™ ChatGPT API 的 MATLAB® 应用程序

系列文章目录 前言 MatGPT 是一款 MATLAB 应用程序&#xff0c;可让您轻松访问 OpenAI 的 ChatGPT API。使用该应用程序&#xff0c;您可以加载特定用例的提示列表&#xff0c;并轻松参与对话。如果您是 ChatGPT 和提示工程方面的新手&#xff0c;MatGPT 不失为一个学习的好方…

【Linux】编辑、查看和搜索文件

大多数 Linux 发行版不包含真正的 vi;而是自带一款高级替代版本&#xff0c;叫做 vim(它是“vi improved”的简写)由 Bram Moolenaar 开发的&#xff0c;vim 相对于传统的 Unix vi 来说&#xff0c;取得了实质性进步。 启动和退出 vim 使用vim可以启动&#xff0c;如命令行输…

MySQL概括与SQL分类

文章目录 一、计算机语言二、SQL语言三、数据库系统四、MySQL简介 一、计算机语言 二、SQL语言 三、数据库系统 四、MySQL简介

竞赛保研 基于CNN实现谣言检测 - python 深度学习 机器学习

文章目录 1 前言1.1 背景 2 数据集3 实现过程4 CNN网络实现5 模型训练部分6 模型评估7 预测结果8 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于CNN实现谣言检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&am…

搭建知识付费平台?明理信息科技为你提供全程解决方案

明理信息科技saas知识付费平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和维护…

搭载紫光展锐芯的移远通信RedCap模组顺利通过中国联通OPENLAB实验室认证

近日&#xff0c;移远通信联合紫光展锐在中国联通5G物联网OPENLAB开放实验室&#xff0c;完成了RedCap模组RG207U-CN端到端测试验收&#xff0c;并获颁认证证书。移远通信RG207U-CN成为业内率先通过联通OPENLAB认证的紫光展锐RedCap芯片平台的模组。 本次测试基于联通OPENLAB实…

Golang 的内存管理

文章目录 1.内存管理角色1.常见的内存分配方法线性分配器空闲链表分配器TCMalloc 2.Go 内存管理组件mspanmcache初始化替换微分配器 mcentralmheap 3.内存分配4.内存管理思想参考文献 1.内存管理角色 内存管理一般包含三个不同的组件&#xff0c;分别是用户程序&#xff08;Mu…

【C语言】指针详解(二)

目录 1.指针变量类型的意义 1.1指针的解引用 1.2指针 - 整数 1.3void*指针 2.const修饰指针 2.1const修饰变量 2.2const修饰指针变量 1.指针变量类型的意义 1.1指针的解引用 指针变量的大小和类型无关&#xff0c;只要是指针变量&#xff0c;在同一个平台下&#xff0…

制作成电子版的五金产品册,打开线上消费市场

五金产品作为家庭装修和维修的必备之物&#xff0c;一直深受广大用户的喜爱。然而&#xff0c;传统的五金市场存在着诸多问题&#xff0c;如产品信息不透明、价格混乱、购买不便等。这些问题不仅影响了消费者的购物体验&#xff0c;也制约了五金行业的进一步发展。 现在很多人都…

《Python Advanced Programming + Design Patterns + Clean Code》

清洁代码 — 学习如何编写可读、可理解且可维护的代码 高级Python编程知识 Python之常用设计模式 Advanced Programming装饰器 decorators生成器 & 迭代器with 上下文管理器面向对象Mixin 模式反射机制并发编程 Design Patterns设计模式分类简单工厂模式工厂模式 √抽象工厂…

读取spring boot项目resource目录下的文件

背景 项目开发过程中&#xff0c;有一些情况下将配置文件放在resource下能简化代码实现和部署时的打包步骤。例如&#xff1a; 项目中使用的数据库升级脚本、初始化脚本。将文件放到resource下&#xff0c;打包在jar包中&#xff0c;不能直接通过File路径读取。下面介绍两种读…

法线贴图实现衣服上皱褶特效

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 法线贴图在3D建模中扮演着重要的角色&#xff0c;它通过模拟表面的微…

AngularJS

理解实现代码的逻辑为主要&#xff0c;代码怎么写为次要。 参考资料&#xff1a; 《AngularJS入门与进阶》&#xff0c;江荣波著 前端开发常用框架 React&#xff1a;由Facebook开发&#xff0c;用于构建用户界面的JavaScript库&#xff0c;以组件化和虚拟DOM著称。 Angular&…