SpringBoot 实现数据加密脱敏(注解 + 反射 + AOP)

news2024/11/19 9:40:39

SpringBoot 实现数据加密脱敏(注解 + 反射 + AOP)

场景:响应政府要求,商业软件应保证用户基本信息不被泄露,不能直接展示用户手机号,身份证,地址等敏感信息。

根据上面场景描述,我们可以分析出两个点。

  • 不被泄露说明用户信息应被加密储存;
  • 不能直接展示说明用户信息应脱敏展示;

解决方案

  • 傻瓜式编程:将项目中关于用户信息实体类的字段,比如姓名,手机号,身份证,地址等,在新增进数据库之前,对数据进行加密处理;在列表中展示用户信息时,对数据库中的数据进行解密脱敏,然后返回给前端;

  • 切入式编程:将项目中关于用户信息实体类的字段用注解给标记,新增用户信息实体类(这里我们用UserBO来表示,给UserBO里面的name,phone字段添加@EncryptField),返回用户信息实体类(这里我们用UserDO来表示,给UserDO里面的name,phone字段添加@DecryptField);然后利用@EncryptField,@DecryptField做为切入点,以切面的形式实现加密,解密脱敏;

傻瓜式编程不是说傻,而是相当于切入式编程,傻瓜式编程需要对用户信息相关的所有接口进行加密,解密脱敏的逻辑处理,这里改动的地方就比较多,风险高,重复操作相同的逻辑,工作量大,后期不好维护;切入式编程只需要对用户信息字段添加注解,对有注解的字段统一进行加密,解密脱敏逻辑处理,操作方便,高聚合,易维护;

方案实现

傻瓜式编程没什么难度,这里我给大家有切入式编程来实现;在实现之前,跟大家预热一下注解,反射,AOP的知识;

注解实战

创建注解

创建一个只能标记在方法上的注解:

package com.weige.javaskillpoint.annotation;

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

@Target(ElementType.METHOD)         //METHOD 说明该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface Encryption {

}

创建一个只能标记在字段上的注解:

package com.weige.javaskillpoint.annotation;

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

@Target(ElementType.FIELD)           //FIELD 说明该注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME)  //RUNTIME 说明该注解在运行时生效
public @interface EncryptField {

}

创建一个标记在字段上,且有值的注解:

package com.weige.javaskillpoint.annotation;

import com.weige.javaskillpoint.enums.DesensitizationEnum;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
	// 注解是可以有值的,这里可以为数组,String,枚举等类型
	// DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); 这里的field是指当前标记的字段
    DesensitizationEnum value(); 
}
注解使用

创建枚举

package com.weige.javaskillpoint.enums;

public enum DesensitizationEnum {
    name,     // 用户信息姓名脱敏
    address,  // 用户信息地址脱敏
    phone;    // 用户信息手机号脱敏
}

创建UserDO类

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.DecryptField;
import com.weige.javaskillpoint.enums.DesensitizationEnum;
import com.weige.javaskillpoint.utils.AesUtil;

import java.lang.reflect.Field;

// 用户信息返回实体类
public class UserDO {

    @DecryptField(DesensitizationEnum.name)
    private String name;

    @DecryptField(DesensitizationEnum.address)
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public UserDO(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public static void main(String[] args) throws IllegalAccessException {
        // 生成并初始化对象
        UserDO userDO = new UserDO("梦想是什么","湖北省武汉市");
        // 反射获取当前对象的所有字段
        Field[] fields = userDO.getClass().getDeclaredFields();
        // 遍历字段
        for (Field field : fields) {
            // 判断字段上是否存在@DecryptField注解
            boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
            // 存在
            if (hasSecureField) {
                // 暴力破解 不然操作不了权限为private的字段
                field.setAccessible(true);
                // 如果当前字段在userDo中不为空 即name,address字段有值
                if (field.get(userDO) != null) {
                    // 获取字段上注解的value值
                    DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
                    // 控制台输出
                    System.out.println(desensitizationEnum);
                    // 根据不同的value值 我们可以对字段进行不同逻辑的脱敏 比如姓名脱敏-魏*,手机号脱敏-187****2275 
                }
            }
        }
    }
}

反射实战

创建UserBO类

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.EncryptField;

import java.lang.reflect.Field;

// 用户信息新增实体类
public class UserBO {
    @EncryptField
    private String name;

    @EncryptField
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public UserBO(String name, String address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public String toString() {
        return "UserBO{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public static void main(String[] args) throws IllegalAccessException {
        UserBO userBO = new UserBO("周传雄","湖北省武汉市");
        Field[] fields = userBO.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean annotationPresent = field.isAnnotationPresent(EncryptField.class);
            if(annotationPresent){
                // 当前字段内容不为空
                if(field.get(userBO) != null){
                    // 这里对字段内容进行加密
                    Object obj = encrypt(field.get(userBO));
                    // 字段内容加密过后 通过反射重新赋给该字段
                    field.set(userBO, obj);
                }
            }
        }
        System.out.println(userBO);
    }

    public static Object encrypt(Object obj){
        return "加密: " + obj;
    }
}

AOP实战

切入点:

package com.weige.javaskillpoint.controller;

import com.weige.javaskillpoint.annotation.Encryption;
import com.weige.javaskillpoint.entity.UserBO;
import lombok.extern.slf4j.Slf4j;
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("/encrypt")
@Slf4j
public class EncryptController {

    @PostMapping("/v1")
    @Encryption  // 切入点
    public UserBO insert(@RequestBody UserBO user) {
        log.info("加密后对象:{}", user);
        return user;
    }
}

切面:

package com.weige.javaskillpoint.aop;

import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class EncryptAspect {

    //拦截需加密注解 切入点
    @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
    public void point() {

    }

    @Around("point()") //环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //加密逻辑处理
        encrypt(joinPoint);
        return joinPoint.proceed();
    }

}

为什么这里要使用AOP:无论是注解,反射,都需要一个启动方法,我上面演示的是通过main函数来启动。使用AOP,项目启动后,只要调用切入点对应的方法,就会根据切入点来形成一个切面,进行统一的逻辑增强;如果大家熟悉SpringMVC,SpringMVC提供了 ResponseBodyAdvice 和 RequestBodyAdvice两个接口,这两个接口可以对请求和响应进行预处理,就可以不需要使用AOP;

加密解密脱敏实战

项目目录:

在这里插入图片描述

pom.xml文件:

<dependencies>
        <!--Springboot项目自带 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Springboot Web项目 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

        <!-- hutool  -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>

		<!-- 切面 aop  -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
    </dependencies>
实体类

用户信息新增实体类 :UserBO

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.EncryptField;

// 实体类
public class UserBO {
    @EncryptField
    private String name;

    @EncryptField
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public UserBO(String name, String address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public String toString() {
        return "UserBO{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

用户信息返回实体类 :UserDO

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.DecryptField;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

// 实体类
public class UserDO {

    @DecryptField(DesensitizationEnum.name)
    private String name;

    @DecryptField(DesensitizationEnum.address)
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public UserDO(String name, String address) {
        this.name = name;
        this.address = address;
    }
}
脱敏枚举
package com.weige.javaskillpoint.enums;

public enum DesensitizationEnum {
    name,
    address,
    phone;
}

注解

解密字段注解(字段):

package com.weige.javaskillpoint.annotation;

import com.weige.javaskillpoint.enums.DesensitizationEnum;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
    DesensitizationEnum value();
}

解密方法注解(方法 作切入点):

package com.weige.javaskillpoint.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Decryption {

}

加密字段注解(字段):

package com.weige.javaskillpoint.annotation;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {

}

加密方法注解(方法 作切入点):

package com.weige.javaskillpoint.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {

}
控制层

解密 Controller:

package com.weige.javaskillpoint.controller;

import com.weige.javaskillpoint.annotation.Decryption;
import com.weige.javaskillpoint.entity.UserDO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/decrypt")
public class DecryptController {

    @GetMapping("/v1")
    @Decryption
    public UserDO decrypt() {
        return new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae");
    }

}

加密 Controller:

package com.weige.javaskillpoint.controller;

import com.weige.javaskillpoint.annotation.Encryption;
import com.weige.javaskillpoint.entity.UserBO;
import lombok.extern.slf4j.Slf4j;
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("/encrypt")
@Slf4j
public class EncryptController {

    @PostMapping("/v1")
    @Encryption
    public UserBO insert(@RequestBody UserBO user) {
        log.info("加密后对象:{}", user);
        return user;
    }
}
切面

解密脱敏切面:

package com.weige.javaskillpoint.aop;

import com.weige.javaskillpoint.annotation.DecryptField;
import com.weige.javaskillpoint.enums.DesensitizationEnum;
import com.weige.javaskillpoint.utils.AesUtil;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class DecryptAspect {
    //拦截需解密注解
    @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Decryption)")
    public void point() {

    }

    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //解密
        return decrypt(joinPoint);
    }

    public Object decrypt(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            Object obj = joinPoint.proceed();
            if (obj != null) {
                //抛砖引玉 ,可自行扩展其他类型字段的判断
                if (obj instanceof String) {
                    decryptValue();
                } else {
                    result = decryptData(obj);
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }

    private Object decryptData(Object obj) throws IllegalAccessException {

        if (Objects.isNull(obj)) {
            return null;
        }
        if (obj instanceof ArrayList) {
            decryptList(obj);
        } else {
            decryptObj(obj);
        }
        return obj;
    }

    private void decryptObj(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
            if (hasSecureField) {
                field.setAccessible(true);
                if (field.get(obj) != null) {
                    String realValue = (String) field.get(obj);
                    DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
                    String value = (String) AesUtil.decrypt(realValue,desensitizationEnum);
                    field.set(obj, value);
                }
            }
        }
    }

    private void decryptList(Object obj) throws IllegalAccessException {
        List<Object> result = new ArrayList<>();
        if (obj instanceof ArrayList) {
            result.addAll((Collection<?>) obj);
        }
        for (Object object : result) {
            decryptObj(object);
        }
    }

    private void decryptValue() {
        log.info("根据对象进行解密脱敏,单个字段不做处理!");
    }



}

加密切面:

package com.weige.javaskillpoint.aop;

import com.weige.javaskillpoint.annotation.EncryptField;
import com.weige.javaskillpoint.entity.UserBO;
import com.weige.javaskillpoint.utils.AesUtil;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;

import java.lang.reflect.Field;

@Slf4j
@Aspect
@Component
public class EncryptAspect {

    //拦截需加密注解
    @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
    public void point() {

    }

    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //加密
        encrypt(joinPoint);
        return joinPoint.proceed();
    }

    public void encrypt(ProceedingJoinPoint joinPoint) {
        Object[] objects;
        try {
            objects = joinPoint.getArgs();
            if (objects.length != 0) {
                for (Object object : objects) {
                    if (object instanceof UserBO) {
                        Field[] fields = object.getClass().getDeclaredFields();
                        for (Field field : fields) {
                            if (field.isAnnotationPresent(EncryptField.class)) {
                                field.setAccessible(true);
                                if (field.get(object) != null) {
                                    // 进行加密
                                    Object o = field.get(object);
                                    Object encrypt = AesUtil.encrypt(field.get(object));
                                    field.set(object, encrypt);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}
工具类

加密工具类:AesUtil

package com.weige.javaskillpoint.utils;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

public class AesUtil {

    // 默认16位 或 128 256位
    public static String AES_KEY = "Wk#qerdfdshbd910";

    public static AES aes = SecureUtil.aes(AES_KEY.getBytes());

    public static Object encrypt(Object obj) {
        return aes.encryptHex((String) obj);
    }

    public static Object decrypt(Object obj, DesensitizationEnum desensitizationEnum) {
        // 解密
        Object decrypt = decrypt(obj);
        // 脱敏
        return DesensitizationUtil.desensitization(decrypt, desensitizationEnum);
    }

    public static Object decrypt(Object obj) {
        return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8);
    }

}

脱敏工具类:DesensitizationUtil

package com.weige.javaskillpoint.utils;

import cn.hutool.core.util.StrUtil;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

public class DesensitizationUtil {


    public static Object desensitization(Object obj, DesensitizationEnum desensitizationEnum) {
        Object result;
        switch (desensitizationEnum) {
            case name:
                result = strUtilHide(obj, 1);
                break;
            case address:
                result = strUtilHide(obj, 3);
                break;
            default:
                result = "";
        }
        return result;
    }

    /**
     * start从0开始
     */
    public static Object strUtilHide(String obj, int start, int end) {
        return StrUtil.hide(obj, start, end);
    }

    public static Object strUtilHide(Object obj, int start) {
        return strUtilHide(((String) obj), start, ((String) obj).length());
    }

}

完结

以上代码不难,大伙复制到本地跑一遍,基本就能理解;愿每一位程序员少走弯路!

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

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

相关文章

功率放大器的种类有哪三种类型

功率放大器是一种能将输入信号转换为更高功率输出的电子设备。在电子工程和音频领域中&#xff0c;功率放大器通常被分为三种类型&#xff1a;A类、B类和AB类。下面安泰电子将详细介绍这三种类型的功率放大器及其特点。 A类功率放大器 A类功率放大器是一种基本的线性功率放大器…

# windows安装gcc、g++、pthread

目录 windows安装gcc、g、pthreadSTEP 1 &#xff1a;下载mingw文件STEP 2&#xff1a;安装.exeSTEP 3&#xff1a;环境变量配置STEP 4&#xff1a;确认MinGw的安装成功STEP 5&#xff1a;安装gccSTEP 6&#xff1a;安装gSTEP 7&#xff1a;查看gccSTEP 8&#xff1a;安装pthre…

城市内涝 | 复杂城市排水管网系统快速建模、管网水力性能专题图制作、城市内涝一维二维耦合模拟、海绵城市关键控制指标计算

随着计算机的广泛应用和各类模型软件的发展&#xff0c;将排水系统模型作为城市洪灾评价与防治的技术手段已经成为防洪防灾的重要技术途径。本次培训将聚焦于综合利用GIS及CAD等工具高效地进行大规模城市排水系统水力模型的建立&#xff0c;利用SWMM实现排水系统水力模拟。讲解…

怎么学习CSS相关技术知识? - 易智编译EaseEditing

学习CSS技术是前端开发中的重要一环&#xff0c;它用于控制网页的样式和布局&#xff0c;使网页更加美观和易于使用。以下是学习CSS技术的几个方面&#xff1a; 基本语法和选择器&#xff1a; 了解CSS的基本语法&#xff0c;学习如何使用选择器来选择HTML元素并应用样式。 样…

elementUi el-radio神奇的:label与label不能设置默认值

问题:最近项目遇到一个奇葩的问题:红框中列表的单选按钮无法根据需求设置默认选中,但是同样是设置开启状态的单选框可以设置默认状态 原因:开始同样是和开启/关闭状态一样也把红框中列表的默认值设置为数字模式,但是由于后台返回值、与label绑定值的类型不同导致设置红框…

问道管理:补仓什么意思?怎么补仓可以降低成本?

补仓这个术语我们在理财出资中经常听到&#xff0c;例如基金补仓&#xff0c;股票补仓。那么&#xff0c;补仓什么意思&#xff1f;怎样补仓能够降低成本&#xff1f;问道管理为我们预备了相关内容&#xff0c;以供参阅。 补仓什么意思&#xff1f; 股票补仓是指出资者在某一只…

机房断网断电监测网关支持远程告警

RTU5028E网络故障监测终端是一款功能强大且方便实用的设备&#xff0c;集合了断网、断电、网线故障报警功能。它支持同时监测多达7台网络设备&#xff0c;可以帮助用户快速定位远程网络设备离线的原因。此外&#xff0c;它还具备自动重启和远程重启网络设备的功能&#xff0c;为…

ODOO16运输费用摊入采购成本,总账和收发表一致?

产品的采购成本准确的计算是怎样的呢&#xff1f;在《会计准则》中有规定&#xff1a; “第三章 计量 ... 第六条 存货的采购成本&#xff0c;包括购买价款、相关税费、运输费、装卸费、保险费以及其他可归属于存货采购成本的费用。” 可见存货的采购成本包含的信息是很多&am…

【java】对象和类

java对象和类 面向对象对象和类属性方法静态包构建对象 面向对象 所谓的面向对象&#xff0c;其实就是分析问题时&#xff0c;以问题所涉及到的事或物为中心的分析方式。 比如在写小学作文里《美好的一天》&#xff0c;一种写法是记录几点起床&#xff0c;几点吃饭&#xff0…

GPTCache 悬赏令!寻找最佳捉虫猎手,豪华赏格等你来拿!

号外号外&#xff01;GPTCache 全宇宙寻找最佳捉虫猎手&#xff01;捉虫数量越多&#xff0c;奖品越丰厚&#xff01; GPTCache 是为 AIGC 应用搭建的全新缓存&#xff0c;典型的应用场景是大模型&#xff0c;它采用语义缓存技术&#xff0c;能够存储 LLM 响应&#xff0c;从而…

将Visio和Excel导出成没有白边的PDF文件

1、VISIO如何无白边导出pdf格式 在使用Latex时&#xff0c;要导入矢量图eps格式。但是VISIO无法输出eps格式&#xff0c;这就需要将其导出为pdf。但是导出pdf时&#xff0c;往往会有大量的白边。VISIO无白边导出pdf格式的方法如下&#xff1a; 1.文件——开发工具——显示sha…

物联网||不一样的点灯实验(2)|通过使用CMSIS库函数实现点灯实验-学习笔记(12)

文章目录 通过使用CMSIS库函数实现点灯实验1 如何使用CMIS库2 如何利用CMSIS库操作IO 两种实现方法的比较课后作业:完整代码&#xff1a;LED.C:test.c:led.h: 通过使用CMSIS库函数实现点灯实验 1 如何使用CMIS库 #####如何使用此驱动#####[. .](#)启用GPIO AHB时钟使用以下函数…

使用条件访问自动执行访问决策

由于远程用户更容易受到网络攻击&#xff0c;因此需要实施严格的安全措施&#xff0c;例如多因素身份验证 &#xff08;MFA&#xff09; 以防止数据泄露。但是&#xff0c;应用严格的组织范围的访问策略&#xff08;如 MFA&#xff09;可能会对用户体验产生不利影响。ADSelfSer…

ICCV 2023 | Prior真的重要吗?IST-Net:更强更快的category-level物体位姿估计模型

​ 论文链接&#xff1a; https://arxiv.org/abs/2303.13479 代码链接&#xff1a; https://github.com/CVMI-Lab/IST-Net 01.背景介绍 Category-level 的物体姿态估计旨在让模型学习到类别独有的特征&#xff0c;从而能够在面对未见过的同类别物体时展现出良好的泛化性。为了…

私人网盘搭建(利用阿里云oss搭建)

1、个人网盘场景说明 个人网盘架构 使用ECS安装Cloudreve提供网盘服务&#xff0c;OSS提供存储服务。当用户使用个人网盘时&#xff0c;访问部署Cloudreve ECS的公网IP地址即可完成文件上传、下载、删除、分享等服务。 什么是Cloudreve Cloudreve可帮助您即刻构建出兼备自用…

下载vue-router的环境变量与创建路由包

目录 一、查看路由是否存在 二、vue路由介绍 三、操作步骤 &#xff08;一&#xff09;查看vue路由的版本号并记住 &#xff08;二&#xff09;vscode里面下载 一、查看路由是否存在 1.点击package.json文件查看里面是否有路由router的依赖&#xff0c;这里面没有发现&a…

Spring框架——AOP配置文件方式

目录 Spring框架的核心功能之AOP技术 AOP的概述 Spring的AOP的简单介绍 AOP概述 什么是AOP? Spring底层AOP实现 Spring的AOP的简介 AOP开发的相关术语 Spring框架的AOP的底层实现 JDK的动态代理&#xff08;代码了解&#xff0c;理解原理&#xff09; CGLIB的代理技…

Unity-数据持久化-PlayerPrefs

一、数据持久化概念 数据持久化就是将内存中的数据模型转换为存储模型&#xff0c;以及将存储模型转换为内存中的数据模型的统称。 简单来说&#xff1a;就是将游戏数据存储到硬盘&#xff0c;将硬盘中数据读取到游戏中&#xff0c;也就是传统意义上的存盘。 是Unity提供的可以…

软考A计划-系统集成项目管理工程师-项目合同管理-上

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

软件测试报告的作用在哪?

软件测试报告是测试工作的重要成果之一&#xff0c;它是测试人员向项目团队和相关利益相关者传递测试结果和评估软件质量的文档。软件测试报告具有以下几个重要作用&#xff1a; 1、向项目团队和管理层提供测试结果软件测试报告会详细汇报测试的执行情况、测试用例覆盖的范围、…