一个注解实现SpringBoot接口请求数据和返回数据加密,提高系统安全性!

注解实现接口加密

  • 1、前言
    • 1.1、前端必看
    • 1.2、后端必看
  • 2、后端注解实现
    • 2.1、实现流程
    • 2.2、开始实现
      • 2.2.1、 pom
      • 2.2.2、 注解
      • 2.2.3、 加密工具类
      • 2.2.3、 定义切面(注意切点包名)
      • 2.2.4、 定义加密基类与各种入参VO
      • 2.2.5、写两个Controller
  • 3、参考文章

1、前言

  起因是公司给人开发的内网系统登录接口采用明文传输,本来用着没啥问题(其实不太好!),毕竟只是内网使用。后面需要外网访问,需要提高安全等级,禁止密码明文传输。于是就有了这篇文章。本文采用:对称加密
有问题联系Qq:1101165230

1.1、前端必看

  需要说明的是,加密传输是需要前端配合的,毕竟请求接口的是前端。所以前端也需要知道加密模式、填充方式与偏移量等基础知识。同时需要商定是对称加密还是非对称加密。

  前端加密快速上手

1.2、后端必看

点开链接:按照目录浏览

  后端加密快速上手

2、后端注解实现

  好的,你已经了解了上面的基础知识,接下来你准备好为你项目编写一个牛x的功能了吗?

2.1、实现流程

实现流程描述

2.2、开始实现

开始前我们现在application.yml 中增加一个配置

# 配置是否开启AOP参数加密解密,不配置默认为true
MySecret:
  isSecret: true 

2.2.1、 pom

	<!-- AOP切面依赖 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	
	<!-- lombok工具 -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	
	<!-- json操作类 -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.52.sec06</version>
	</dependency>

	 <!-- String工具包 -->
	<dependency>
		<groupId>org.apache.commons</groupId>
		 <artifactId>commons-lang3</artifactId>
	</dependency>

2.2.2、 注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secret {

    Class value();

    // 参数类中传递加密数据的属性名,默认encryptStr
    String encryptStrName() default "encryptStr";
}

2.2.3、 加密工具类

  注意AES_KEY需要更改,切记和前端商量

import com.alibaba.fastjson.JSON;
import com.example.undertwo.entity.vo.UserVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author by Guoshun
 * @version 1.0.0
 * @description 支持AES、DES、RSA加密、数字签名以及生成对称密钥和非对称密钥对
 * @date 2024/5/8 15:04
 */
public class CryptoUtils {

    public static final String AES_KEY = "IQcoqkg==";
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final Encoder BASE64_ENCODER = Base64.getEncoder();
    private static final Decoder BASE64_DECODER = Base64.getDecoder();
    private static final Map<Algorithm, KeyFactory> KEY_FACTORY_CACHE = new ConcurrentHashMap<>();
    private static final Map<Algorithm, Cipher> CIPHER_CACHE = new HashMap<>();

    /**
     * 对称加密
     *
     * @param secretKey 密钥
     * @param iv        加密向量,只有CBC模式才支持,如果是CBC则必传
     * @param plainText 明文
     * @param algorithm 对称加密算法,如AES、DES
     * @return
     * @throws Exception
     */
    public static String encryptSymmetrically(String secretKey, String iv, String plainText, Algorithm algorithm) throws Exception {
        SecretKey key = decodeSymmetricKey(secretKey, algorithm);
        IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
        byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
        byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes);

        return BASE64_ENCODER.encodeToString(ciphertextInBytes);
    }

    /**
     * 对称解密
     *
     * @param secretKey  密钥
     * @param iv         加密向量,只有CBC模式才支持,如果是CBC则必传
     * @param ciphertext 密文
     * @param algorithm  对称加密算法,如AES、DES
     * @return
     * @throws Exception
     */
    public static String decryptSymmetrically(String secretKey, String iv, String ciphertext, Algorithm algorithm) throws Exception {
        SecretKey key = decodeSymmetricKey(secretKey, algorithm);
        IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
        byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
        byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, key, ivParameterSpec, ciphertextInBytes);
        return new String(plainTextInBytes, DEFAULT_CHARSET);
    }

    /**
     * 将密钥进行Base64位解码,重新生成SecretKey实例
     *
     * @param secretKey 密钥
     * @param algorithm 算法
     * @return
     */
    private static SecretKey decodeSymmetricKey(String secretKey, Algorithm algorithm) {
        byte[] key = BASE64_DECODER.decode(secretKey);
        return new SecretKeySpec(key, algorithm.getName());
    }

    private static IvParameterSpec decodeIv(String iv) {
        byte[] ivInBytes = BASE64_DECODER.decode(iv);
        return new IvParameterSpec(ivInBytes);
    }

    private static byte[] transform(Algorithm algorithm, int mode, Key key, IvParameterSpec iv, byte[] msg) throws Exception {
        Cipher cipher = CIPHER_CACHE.get(algorithm);
        // double check,减少上下文切换
        if (cipher == null) {
            synchronized (CryptoUtils.class) {
                if ((cipher = CIPHER_CACHE.get(algorithm)) == null) {
                    cipher = determineWhichCipherToUse(algorithm);
                    CIPHER_CACHE.put(algorithm, cipher);
                }
                cipher.init(mode, key, iv);
                return cipher.doFinal(msg);
            }
        }

        synchronized (CryptoUtils.class) {
            cipher.init(mode, key, iv);
            return cipher.doFinal(msg);
        }
    }

    private static Cipher determineWhichCipherToUse(Algorithm algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
        Cipher cipher;
        String transformation = algorithm.getTransformation();
        // 官方推荐的transformation使用algorithm/mode/padding组合,SunJCE使用ECB作为默认模式,使用PKCS5Padding作为默认填充
        if (StringUtils.isNotEmpty(transformation)) {
            cipher = Cipher.getInstance(transformation);
        } else {
            cipher = Cipher.getInstance(algorithm.getName());
        }

        return cipher;
    }

    /**
     * 算法分为加密算法和签名算法,更多算法实现见:<br/>
     * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl">jdk8中的标准算法</a>
     */
    public static class Algorithm {

        @Getter
        private String name;
        @Getter
        private String transformation;
        @Getter
        private int keySize;

        public Algorithm(String name, int keySize) {
            this(name, null, keySize);
        }

        public Algorithm(String name, String transformation, int keySize) {
            this.name = name;
            this.transformation = transformation;
            this.keySize = keySize;
        }

        public interface Encryption {
            Algorithm AES_ECB_PKCS5 = new Algorithm("AES", "AES/ECB/PKCS5Padding", 128);
            Algorithm AES_CBC_PKCS5 = new Algorithm("AES", "AES/CBC/PKCS5Padding", 128);
            Algorithm DES_ECB_PKCS5 = new Algorithm("DES", "DES/ECB/PKCS5Padding", 56);
            Algorithm DES_CBC_PKCS5 = new Algorithm("DES", "DES/CBC/PKCS5Padding", 56);
            Algorithm RSA_ECB_PKCS1 = new Algorithm("RSA", "RSA/ECB/PKCS1Padding", 1024);
            Algorithm DSA = new Algorithm("DSA", 1024);
        }

        public interface Signing {
            Algorithm SHA1WithDSA = new Algorithm("SHA1withDSA", 1024);
            Algorithm SHA1WithRSA = new Algorithm("SHA1WithRSA", 2048);
            Algorithm SHA256WithRSA = new Algorithm("SHA256WithRSA", 2048);
        }

    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class AsymmetricKeyPair {

        private String publicKey;
        private String privateKey;
    }

    /**
     * 加密
     * @param jsonStr
     * @return
     * @throws Exception
     */
    public static String encrypt(String jsonStr) throws Exception {
       return CryptoUtils.encryptSymmetrically(AES_KEY, null, jsonStr, Algorithm.Encryption.AES_ECB_PKCS5);
    }

    /**
     * 解密
     * @param jsonStr
     * @return
     * @throws Exception
     */
    public static String decrypt(String jsonStr) throws Exception {
       return CryptoUtils.decryptSymmetrically(AES_KEY, null, jsonStr, Algorithm.Encryption.AES_ECB_PKCS5);
    }

    public static void main(String[] args) throws Exception {
        UserVO userVO = new UserVO();
        userVO.setId(123);
        userVO.setName("阿萨");
        String encrypt = encrypt(JSON.toJSONString(userVO));
        System.out.println("加密后:" + encrypt);
        String decrypt = decrypt(encrypt);
        UserVO userVO1 = JSON.parseObject(decrypt, UserVO.class);
        System.out.println("解密后" + userVO1.toString());

    }

2.2.3、 定义切面(注意切点包名)

import com.alibaba.fastjson.JSON;
import com.example.undertwo.config.Secret;
import com.example.undertwo.entity.result.ResultVO;
import com.example.undertwo.utils.CryptoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * @author by Guoshun
 * @version 1.0.0
 * @description 切面加密解密
 * @date 2024/5/9 14:14
 */
@Aspect
@Component
@Slf4j
public class SecretAOP {

    // 是否进行加密解密,通过配置文件注入(不配置默认为true)
    @Value("${MySecret.isSecret:true}")
    boolean isSecret;

    // 定义切点,使用了@Secret注解的类 或 使用了@Secret注解的方法
    @Pointcut("@within(com.example.undertwo.config.Secret) || @annotation(com.example.undertwo.config.Secret)")
    public void pointcut(){}

    // 环绕切面
    @Around("pointcut()")
    public ResultVO around(ProceedingJoinPoint point){
        ResultVO result = null;
        // 获取被代理方法参数
        Object[] args = point.getArgs();
        // 获取被代理对象
        Object target = point.getTarget();
        // 获取通知签名
        MethodSignature signature = (MethodSignature )point.getSignature();

        try {
            // 获取被代理方法
            Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            // 获取被代理方法上面的注解@Secret
            Secret secret = pointMethod.getAnnotation(Secret.class);
            // 被代理方法上没有,则说明@Secret注解在被代理类上
            if(secret==null){
                secret = target.getClass().getAnnotation(Secret.class);
            }

            if(secret!=null){
                // 获取注解上声明的加解密类
                Class clazz = secret.value();
                // 获取注解上声明的加密参数名
                String encryptStrName = secret.encryptStrName();

                for (int i = 0; i < args.length; i++) {
                    // 如果是clazz类型则说明使用了加密字符串encryptStr传递的加密参数
                    if(clazz.isInstance(args[i])){
                        Object cast = clazz.cast(args[i]);      //将args[i]转换为clazz表示的类对象
                        // 通过反射,执行getEncryptStr()方法,获取加密数据
                        Method method = clazz.getMethod(getMethedName(encryptStrName));
                        // 执行方法,获取加密数据
                        String encryptStr = (String) method.invoke(cast);
                        // 加密字符串是否为空
                        if(StringUtils.isNotBlank(encryptStr)){
                            // 解密
                            String json = CryptoUtils.decrypt(encryptStr);
                            // 转换vo
                            args[i] = JSON.parseObject(json, (Type) args[i].getClass());
                        }
                    }
                    // 其他类型,比如基本数据类型、包装类型就不使用加密解密了
                }
            }

            // 执行请求
            result = (ResultVO) point.proceed(args);

            // 判断配置是否需要返回加密
            if(isSecret){
                // 获取返回值json字符串
                String jsonString = JSON.toJSONString(result.getData());
                // 加密
                String s = CryptoUtils.encrypt(jsonString);
                result.setData(s);
            }

        } catch (NoSuchMethodException e) {
            log.error("@Secret注解指定的类没有字段:encryptStr,或encryptStrName参数字段不存在");
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

    // 转化方法名
    private String getMethedName(String name){
        String first = name.substring(0,1);
        String last = name.substring(1);
        first = StringUtils.upperCase(first);
        return "get" + first + last;
    }
}

2.2.4、 定义加密基类与各种入参VO

import lombok.Data;

/**
 * @author by Guoshun
 * @version 1.0.0
 * @description 基础的实体类
 * @date 2024/5/9 14:00
 */
@Data
public class BaseVO {
    private String encryptStr;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author by Guoshun
 * @version 1.0.0
 * @description 部门类
 * @date 2024/5/9 14:16
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptVO{

    private Integer id;

    private String deptName;

    // 自己实现的一个参数,用来给前端传递加密字符串
    private String encryptJson;

}
import com.example.undertwo.entity.BaseVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author by Guoshun
 * @version 1.0.0
 * @description TODO
 * @date 2024/5/9 14:05
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVO extends BaseVO {

    private Integer id;

    private String name;

}

2.2.5、写两个Controller


import com.example.undertwo.config.Secret;
import com.example.undertwo.entity.result.ResultVO;
import com.example.undertwo.entity.vo.DeptVO;
import org.springframework.web.bind.annotation.*;


/**
 * @author by Guoshun
 * @version 1.0.0
 * @description 部门类
 * @date 2024/5/9 14:17
 */
@RestController
@RequestMapping("dept")
public class DeptController {

    @GetMapping("getDeptName/{id}")
    public ResultVO getDeptName(@PathVariable("id") String id){
        return new ResultVO(0,"查询成功","财务部" + id);
    }

    // 注解在方法上,并传递了encryptStrName自己定义的加密字符串名称encryptJson
    @Secret(value = DeptVO.class,encryptStrName = "encryptJson")
    @PostMapping("addDept")
    public ResultVO addDept(@RequestBody DeptVO dept){
        return new ResultVO(0,"新增成功",dept);
    }

}

import com.example.undertwo.config.Secret;
import com.example.undertwo.entity.BaseVO;
import com.example.undertwo.entity.result.ResultVO;
import com.example.undertwo.entity.vo.UserVO;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * @author by Guoshun
 * @version 1.0.0
 * @description  用户控制类
 * @date 2024/5/9 14:10
 */
@Secret(BaseVO.class)                             //接口参数和返回要进行加解密
@RestController
@RequestMapping("user")
public class UserController {

    //采用内部类的实例代码块方式初始化map
    HashMap<Integer, UserVO> userMap = new HashMap<Integer, UserVO>(){
        {
            put(1,new UserVO(1,"张三"));
            put(2,new UserVO(2,"李四"));
            put(3,new UserVO(3,"王五"));
        }
    };

    // 通过id查询用户
    @GetMapping("getUserName/{id}")
    public ResultVO getUserName(@PathVariable("id")  Integer id){
        return new ResultVO(0,"查询成功",userMap.get(id));
    }

    // 通过name查询用户id
    @GetMapping("getUserId")
    public ResultVO getUserId(@RequestParam  String name){
        Iterator<Map.Entry<Integer, UserVO>> iterator = userMap.entrySet().iterator();
        UserVO u = null;
        while (iterator.hasNext()){
            Map.Entry<Integer, UserVO> entry = iterator.next();
            if(entry.getValue().getName().equals(name)){
                u = entry.getValue();
                break;
            }
        }
        return new ResultVO(0,"查询成功",u);
    }

    // 新增用户
    @PostMapping("addUser")
    public ResultVO addUser(@RequestBody UserVO user){
        return new ResultVO(0,"新增成功",user);
    }

    // 更改用户
    @PostMapping("updateUser")
    public ResultVO updateUser(@RequestBody UserVO user) throws Throwable {
        if(user==null||user.getId()==null){
            throw new NullPointerException();
        }else{
            return new ResultVO(0,"修改成功",user);
        }
    }
}

3、参考文章

[1]:springboot 自定义注解使用AOP实现请求参数解密以及响应数据加密
[2]:前端CryptoJS和Java后端数据互相加解密(AES)

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

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

相关文章

Python | Leetcode Python题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution:def exist(self, board: List[List[str]], word: str) -> bool:def dfs(i, j, k):if not 0 < i < len(board) or not 0 < j < len(board[0]) or board[i][j] ! word[k]: return Falseif k len(word) - 1: r…

linux性能监控之lsof

lsof&#xff1a;list open files&#xff0c;显示所有打开的文件以及进程信息&#xff0c;我们通常用来检查特定的文件被哪些进程打开 [rootk8s-master ~]# lsof --help lsof: illegal option character: - lsof: -e not followed by a file system path: "lp" lso…

《软件方法(下)》8.3.3 泛化的一些重点讨论(202405更新)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.3 建模步骤C-2 识别类的关系 8.3.3 泛化的一些重点讨论 8.3.3.1 子集的不相交和完整 泛化是集合关系&#xff0c;在建模泛化关系时&#xff0c;我们对泛化关系中的子类&#xff0…

【随笔】Git 高级篇 -- 远程跟踪分支 git checkout -b | branch -u(三十五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

Postman基础功能-常见类型的接口请求

天空灰暗到一定程度&#xff0c;星辰就会熠熠生辉。大家好&#xff0c;之前给大家分享了关于 Postman 工具的介绍以及安装&#xff0c;在当今数字化的时代&#xff0c;接口请求在软件开发和系统集成中扮演着至关重要的角色。而 Postman 作为一款强大且广受认可的接口测试工具&a…

【系统架构师】-案例篇(一)UML用例图

1、概述 用于表示系统功能需求&#xff0c;以及应用程序与用户或者与其他应用程序之间的交互关系。 2、组成 参与者&#xff08;Actors&#xff09;&#xff1a;与系统交互的用户或其他系统。用一个人形图标表示。用例&#xff08;Use Cases&#xff09;&#xff1a;系统需要…

OpenAI 今日(北京时间 5 月 14 日凌晨两点)将发布的大更新,不是 GPT-5,也不是搜索引擎

&#x1f989; AI新闻 &#x1f680; OpenAI 今日&#xff08;5月13日&#xff09;将发布的大更新&#xff0c;不是 GPT-5&#xff0c;也不是搜索引擎 摘要&#xff1a;OpenAI 预计即将推出一款新的 AI 语音助手&#xff0c;该助手不仅可以进行语音和文字交流&#xff0c;还能…

如何利用AI提高内容生产效率与AIGC典型案例分析

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

Spring Boot:让微服务开发像搭积木一样简单!

带你一探 Spring Boot 的自动配置和 Starter POMs 的神奇之处&#xff0c;展示如何通过几个简单的步骤就能让你的微服务应用在云端翱翔&#xff01; 文章目录 1. 引言1.1 简述Spring框架的起源与重要性1.2 阐述文章目的&#xff1a;深入解析Spring核心功能与应用实践2. 背景介绍…

Attention Sink

论文发现自回归LLM存在的一个有趣现象&#xff1a;对于输入文本最靠前的少量几个token&#xff0c;无论它们在语义上与语言建模任务的相关性如何&#xff0c;大量的注意力分数都会分配给他们&#xff0c;如下图所示&#xff1a; 模型的前两层还能保持attention score更多分配给…

Angular入门

Angular版本&#xff1a;Angular 版本演进史概述-天翼云开发者社区 - 天翼云 安装nodejs&#xff1a;Node.js安装与配置环境 v20.13.1(LTS)-CSDN博客 Angular CLI是啥 Angular CLI 是一个命令行接口(Angular Command Line Interface)&#xff0c;是开发 Angular 应用的最快、最…

C++/Qt 小知识记录6

工作中遇到的一些小问题&#xff0c;总结的小知识记录&#xff1a;C/Qt 小知识6 dumpbin工具查看库导出符号OSGEarth使用编出的protobuf库&#xff0c;报错问题解决VS2022使用cpl模板后&#xff0c;提示会乱码的修改设置QProcess调用cmd.exe执行脚本QPainterPath对线段描边处理…

python实现幸运大转盘 python实现抽奖

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.代码 三.使用 四.总结 一.前言 幸运大转盘是一种活动形式,广泛应用于各种场合,如商业促销、展览活动、企业庆典以及体育课堂等,旨在增加活动的趣味性和参与度。以下是对幸运大转盘的详细介

STM32实现1.8寸液晶屏 LCD SPI串口显示屏模块 TFT彩屏(标准库和HAL库实现)

目录 一、所选模块 液晶模块选择&#xff08;淘宝上均有售卖&#xff09; 模块引脚 二、嵌入式单片机型号 三、接线表设计 四、开发环境版本说明 五、标准库实现 六、HAL库实现 七、完整工程&#xff08;内含标准库和HAL库源码&#xff09; 代码链接 一、所选模块 液…

什么是卷积神经网络

在机器视觉和其他很多问题上&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;取得了当前最好的效果&#xff0c;被广泛用于各个领域&#xff0c;在很多问题上都取得了当前最好的性能。 卷积神经网络原理 卷积神经网络发展历史中…

开源免费的定时任务管理系统:Gocron

Gocron&#xff1a;精准调度未来&#xff0c;你的全能定时任务管理工具&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Gocron是github上一个开源免费的定时任务管理系统。它使用Go语言开发&#xff0c;是一个轻量级定时任务集中调度和管理系统&#xff0c;用于替代L…

14.CAS原理

文章目录 CAS原理1.什么是CAS2.Unsafe类中的CAS方法2.1.获取UnSafe实例2.2.调用UnSafe提供的CAS方法2.3.调用Unsafe提供的偏移量相关2.4.CAS无锁编程2.4.1.使用cas进行无锁安全自增案例 CAS原理 由于JVM的synchronized重量级锁设计操作系统内核态下的互斥锁的使用&#xff0c;其…

多剖面土壤墒情监测仪

TH-GTS04在农业生产中&#xff0c;土壤墒情是影响作物生长的关键因素之一。为了更好地了解土壤的水分状况&#xff0c;为农业生产提供科学依据&#xff0c;多剖面土壤墒情监测仪应运而生。这种先进的监测设备具有多项功能优势&#xff0c;为土壤水分的精准监测提供了有力支持。…

火绒安全原理、用法、案例和注意事项

火绒安全是一款功能强大的安全软件&#xff0c;它采用了先进的安全技术和算法&#xff0c;通过实时监测、恶意代码识别、防火墙功能、沙箱技术和网络保护等多种手段&#xff0c;为用户提供全面的计算机安全防护。 1.为什么选用火绒安全&#xff1f; 火绒安全是一款优秀的安全软…

通过内网穿透实现远程访问个人电脑资源详细过程(免费)(NatApp + Tomcat)

目录 1. 什么是内网穿透 2. 内网穿透软件 3. NatApp配置 4. 启动NatApp 5. 通过内网穿透免费部署我们的springboot项目 通过内网穿透可以实现远程通过网络访问电脑的资源&#xff0c;本文主要讲述通过内网穿透实现远程访问个人电脑静态资源的访问&#xff0c;下一章节将讲…