当前位置: 首页 > news >正文

SpringBoot:一个注解就能帮你下载任意对象

介绍

下载功能应该是比较常见的功能了,虽然一个项目里面可能出现的不多,但是基本上每个项目都会有,而且有些下载功能其实还是比较繁杂的,倒不是难,而是麻烦。

所以结合之前的下载需求,我写了一个库来简化下载功能的实现

传送门:https://github.com/Linyuzai/concept/wiki/Concept-Download

如果我说现在只需要一个注解就能帮你下载任意的对象,是不是觉得非常的方便

@Download(source = "classpath:/download/README.txt")
@GetMapping("/classpath")
public void classpath() {

}

@Download
@GetMapping("/file")
public File file() {
    return new File("/Users/Shared/README.txt");
}

@Download
@GetMapping("/http")
public String http() {
    return "http://127.0.0.1:8080/concept-download/image.jpg";
}

感觉差别不大?那就听听我遇到的一个下载需求

我们有一个平台是管理设备的,然后每个设备都会有一个二维码图片,用一个字段存储的 http 地址

现在需要导出所有设备二维码图片的压缩包,图片名称需要用设备名称加 .png 后缀,需求上来说并不难,但是着实有点麻烦

  • 首先我需要将设备列表查出来

  • 然后使用二维码地址下载图片并写到本地缓存文件

  • 在下载之前需要先判断是否已经存在缓存

  • 下载时需要并发下载提升性能

  • 等所有图片下载结束后

  • 再生成一个压缩文件

  • 然后再操作输入输出流写到响应中

看着我实现了将近 200 行的代码,真是又臭又长,一个下载功能咋能那么麻烦呢,于是我就想有没有更简单的方式

我当时的需求很简单,我想着我只要提供需要下载的数据,比如一个文件路径,一个文件对象,一段字符串文本,一个http地址,或者混搭了前面所有类型的一个集合,甚至是我们自定义的某个类的实例,后面的事情我就不用管了

文件路径是一个文件还是一个目录?字符串文本需要先写入一个文本文件中?http资源如何下载到本地?多个文件怎么压缩?最后怎么写到响应中?我才不想花时间管这些

比如就像我现在这个需求,我只要返回设备列表就行了,其他的事情我都不用管

@Download(filename = "二维码.zip")
@GetMapping("/download")
public List download() {
    return deviceService.all();
}

public class Device {

    //设备名称
    private String name;

    //设备二维码
    //注解表示该http地址是需要下载的数据
    @SourceObject
    private String qrCodeUrl;

    //注解表示文件名称
    @SourceName
    public String getQrCodeName() {
        return name + ".png";
    }
    //省略其他属性方法
}

通过在 Device 的字段上标注某些注解(或是实现某个接口)来指定文件名称和文件地址

如果能这样实现,省时省心省力,又多了写 199 行代码的摸鱼时间难道不香么

思路

下面来讲讲这个库的主要设计思路,以及中间遇到的坑,大家有兴趣可以继续往下看

其实基于一开始的设想,我觉得功能并没有多复杂,于是就决定开肝

只是万万没想到实现起来比我想象的更复杂(这是后话了)

基础

首先整个库基于响应式编程,但却并不是完全意义上的响应式,只能说是Mono<inputstream< code="">>这样的。。。奇怪组合?</inputstream<>

为什么会这样呢,很大的一个原因是由于需要兼容webmvc和webflux,导致我仅仅是将之前实现的InputStream方式重构成了响应式,所以就出现了这样的组合

这也是我遇到的最大的一个坑,我先前已经基本调通了基于Servlet的整个下载流程,然后就想着支持一下webflux

大家都知道webmvc中,我们可以通过RequestContextHolder来获得请求和响应对象,但是在webflux中就不行了,当然我们可以在方法参数中注入

@Download(source = "classpath:/download/README.txt")
@GetMapping("/classpath")
public void classpath(ServerHttpResponse response) {

}

结合Spring自带的注入功能,我们就可以通过AOP拿到响应的入参了,但是总觉得这样写有点多余,强迫症表示不能忍

有什么办法既能把用不到的入参干掉,又能拿到响应对象呢,在网上找到了一种实现方式

/**
 * 用于设置当前的请求和响应。
 *
 * @see ReactiveDownloadHolder
 */
public class ReactiveDownloadFilter implements WebFilter {

    @Override
    public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        return chain.filter(exchange)
                //低版本使用subscriberContext
                .contextWrite(ctx -> ctx.put(ServerHttpRequest.class, request))
                .contextWrite(ctx -> ctx.put(ServerHttpResponse.class, response));
    }
}

/**
 * 用于获得当前的请求和响应。
 *
 * @see ReactiveDownloadFilter
 */

public class ReactiveDownloadHolder {

    public static Mono getRequest() {
        //低版本使用subscriberContext
        return Mono.deferContextual(contextView -> Mono.just(contextView.get(ServerHttpRequest.class)));
    }

    public static Mono getResponse() {
        //低版本使用subscriberContext
        return Mono.deferContextual(contextView -> Mono.just(contextView.get(ServerHttpResponse.class)));
    }
}

通过添加WebFilter就可以获得响应对象了,但是返回值是Mono

那么可不可以通过Mono.block()阻塞得到对应的对象呢,答案是不行,由于webflux基于Netty的非阻塞线程,如果调用该方法会直接抛出异常

所以就没有任何办法了,只能将之前代码基于响应式重构

架构

接下来说说整体架构

图片

图片

对于一个下载请求,我们可以分成几个步骤,以下载多个文件的压缩包为例

  • 首先我们一般是得到多个文件的路径或对应的File对象

  • 然后将这些文件压缩生成一个压缩文件

  • 最后将压缩文件写入到响应中

但是对于我上面描述的需求,一开始就不是文件路径或对象了,而是一个http地址,然后在压缩之前还需要多一个步骤,需要先将图片下载下来

那么对于各种各样的需求我们可能需要在当前步骤中的任意位置添加额外的步骤,所以我参考了Spring Cloud Gateway 拦截链的实现方式

/**
 * 下载处理器。
 */
public interface DownloadHandler extends OrderProvider {

    /**
     * 执行处理。
     *
     * @param context {@link DownloadContext}
     * @param chain   {@link DownloadHandlerChain}
     */
    Mono handle(DownloadContext context, DownloadHandlerChain chain);
}

/**
 * 下载处理链。
 */
public interface DownloadHandlerChain {

    /**
     * 调度下一个下载处理器。
     *
     * @param context {@link DownloadContext}
     */
    Mono next(DownloadContext context);
}

这样每个步骤就可以单独实现一个DownloadHandler,步骤与步骤之间可以任意的组合添加

下载上下文

在此基础上使用一个贯穿整个流程的上下文DownloadContext,方便共享和传递步骤之间的中间结果

对于上下文DownloadContext也提供了DownloadContextFactory可以用于自定义上下文

同时提供了DownloadContextInitializer和DownloadContextDestroyer用于在上下文初始化和销毁时扩展自己的逻辑

下载类型支持

我们需要下载的数据的类型是不固定的,比如有文件,有http地址,也会有之前我希望的自定义的类的实例

所以我将所有的下载对象抽象成了Source,表示一个下载源,这样文件可以实现为FileSource,http地址可以实现为HttpSource,然后通过对应的SourceFactory来匹配创建

比如FileSourceFactory可以匹配File并且创建FileSource,HttpSourceFactory可以匹配http://前缀并且创建HttpSource

/**
 * {@link Source} 工厂。
 */
public interface SourceFactory extends OrderProvider {

    /**
     * 是否支持需要下载的原始数据对象。
     *
     * @param source  需要下载的原始数据对象
     * @param context {@link DownloadContext}
     * @return 如果支持则返回 true
     */
    boolean support(Object source, DownloadContext context);

    /**
     * 创建。
     *
     * @param source  需要下载的原始数据对象
     * @param context {@link DownloadContext}
     * @return 创建的 {@link Source}
     */
    Source create(Object source, DownloadContext context);
}

那么对于我们自定义的类要怎么支持呢,之前提到可以在类上标注注解或是实现特定的接口,那么就用我实现的注解的方式来大概讲一讲吧

其实逻辑很简单,只要能熟练的运用反射就完全没问题,我们再来看一看用法

@Download(filename = "二维码.zip")
@GetMapping("/download")
public List download() {
    return deviceService.all();
}

public class Device {

    //设备名称
    private String name;

    //设备二维码
    //注解表示该http地址是需要下载的数据
    @SourceObject
    private String qrCodeUrl;

    //注解表示文件名称
    @SourceName
    public String getQrCodeName() {
        return name + ".png";
    }
    //省略其他属性方法
}

首先我定义了一个注解@SourceModel标注在类上表示需要被解析,然后定义了一个@SourceObject注解标注在需要下载的字段(或方法)上,这样我们就可以通过反射拿到这个字段(或方法)的值

基于当前支持的SourceFactory就能创建出对应的Source,接下来使用@SourceName指定名称,也同样可以通过反射获得这个方法(或字段)的值并依旧通过反射设置到创建出来的Source上

这样就能非常灵活的支持任意的对象类型了

并发加载

对于像http这种网络资源,我们需要先并发加载(多个文件时)到本地的内存中或是缓存文件中来提升我们的处理效率

当然我可以直接定死一个线程池来执行,但是每个机器每个项目甚至每个需求对于并发的要求和资源的分配都不一样

所以我提供了SourceLoader来支持自定义的加载逻辑,你甚至可以一部分用线程池,一部分用协程,剩下一部分不加载

/**
 * {@link Source} 加载器。
 *
 * @see DefaultSourceLoader
 * @see SchedulerSourceLoader
 */
public interface SourceLoader {

    /**
     * 执行加载。
     *
     * @param source  {@link Source}
     * @param context {@link DownloadContext}
     * @return 加载后的 {@link Source}
     */
    Mono load(Source source, DownloadContext context);
}

压缩

当我们加载完之后就可以执行压缩了,同样的我定义了一个类Compression作为压缩对象的抽象

一般来说,我们会先在本地创建一个缓存文件,然后将压缩后的数据写入到缓存文件中

不过我每次都很讨厌在配置文件中配置各种各样的路径,所以在压缩时支持内存压缩,当然如果文件比较大还是老老实实生成一个缓存文件

对于压缩格式也提供了可以完全自定义的SourceCompressor接口,你想自己实现一个压缩协议都没有问题

/**
 * {@link Source} 压缩器。
 *
 * @see ZipSourceCompressor
 */
public interface SourceCompressor extends OrderProvider {

    /**
     * 获得压缩格式。
     *
     * @return 压缩格式
     */
    String getFormat();

    /**
     * 判断是否支持对应的压缩格式。
     *
     * @param format  压缩格式
     * @param context {@link DownloadContext}
     * @return 如果支持则返回 true
     */
    default boolean support(String format, DownloadContext context) {
        return format.equalsIgnoreCase(getFormat());
    }

    /**
     * 如果支持对应的格式就会调用该方法执行压缩。
     *
     * @param source  {@link Source}
     * @param writer  {@link DownloadWriter}
     * @param context {@link DownloadContext}
     * @return {@link Compression}
     */
    Compression compress(Source source, DownloadWriter writer, DownloadContext context);
}

响应写入

我将响应抽象成了DownloadResponse,主要用于兼容HttpServletResponse和ServerHttpResponse

但是问题又出现了,下面是webmvc和webflux写入响应的方式

//HttpServletResponse
response.getOutputStream().write(byte b[], int off, int len);

//ServerHttpResponse
response.writeWith(Publisher body);

这兼容的我脑壳疼,不过最后还是搞定了

/**
 * 持有 {@link ServerHttpResponse} 的 {@link DownloadResponse},用于 webflux。
 */
@Getter
public class ReactiveDownloadResponse implements DownloadResponse {

    private final ServerHttpResponse response;

    private OutputStream os;

    private Mono mono;

    public ReactiveDownloadResponse(ServerHttpResponse response) {
        this.response = response;
    }

    @Override
    public Mono write(Consumer consumer) {
        if (os == null) {
            mono = response.writeWith(Flux.create(fluxSink -> {
                try {
                    os = new FluxSinkOutputStream(fluxSink, response);
                    consumer.accept(os);
                } catch (Throwable e) {
                    fluxSink.error(e);
                }
            }));
        } else {
            consumer.accept(os);
        }
        return mono;
    }

    @SneakyThrows
    @Override
    public void flush() {
        if (os != null) {
            os.flush();
        }
    }

    @AllArgsConstructor
    public static class FluxSinkOutputStream extends OutputStream {

        private FluxSink fluxSink;

        private ServerHttpResponse response;

        @Override
        public void write(byte[] b) throws IOException {
            writeSink(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            byte[] bytes = new byte[len];
            System.arraycopy(b, off, bytes, 0, len);
            writeSink(bytes);
        }

        @Override
        public void write(int b) throws IOException {
            writeSink((byte) b);
        }

        @Override
        public void flush() {
            fluxSink.complete();
        }

        public void writeSink(byte... bytes) {
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            fluxSink.next(buffer);
            //在这里可能有问题,但是目前没有没有需要释放的数据
            DataBufferUtils.release(buffer);
        }
    }
}

只要最后都是写byte[]就可以相互转化,只不过可能麻烦一点,需要用接口回调

将FluxSink伪装成一个OutputStream,写入时把byte[]转成DataBuffer 并调用next方法,最后在flush的时候调用complete方法就行了,完美

响应写入其实就是对输入输出流的处理了,正常情况下,我们会定义一个byte[]用来缓存读到的数据,所以我也不会固定这个缓存的大小而是提供了DownloadWriter可以自定义处理输入输出流,包括存在指定编码或是Range头的情况

/**
 * 具体操作 {@link InputStream} 和 {@link OutputStream} 的写入器。
 */
public interface DownloadWriter extends OrderProvider {

    /**
     * 该写入器是否支持写入。
     *
     * @param resource {@link Resource}
     * @param range    {@link Range}
     * @param context  {@link DownloadContext}
     * @return 如果支持则返回 true
     */
    boolean support(Resource resource, Range range, DownloadContext context);

    /**
     * 执行写入。
     *
     * @param is      {@link InputStream}
     * @param os      {@link OutputStream}
     * @param range   {@link Range}
     * @param charset {@link Charset}
     * @param length  总大小,可能为 null
     */
    default void write(InputStream is, OutputStream os, Range range, Charset charset, Long length) {
        write(is, os, range, charset, length, null);
    }

    /**
     * 执行写入。
     *
     * @param is       {@link InputStream}
     * @param os       {@link OutputStream}
     * @param range    {@link Range}
     * @param charset  {@link Charset}
     * @param length   总大小,可能为 null
     * @param callback 回调当前进度和增长的大小
     */
    void write(InputStream is, OutputStream os, Range range, Charset charset, Long length, Callback callback);

    /**
     * 进度回调。
     */
    interface Callback {

        /**
         * 回调进度。
         *
         * @param current  当前值
         * @param increase 增长值
         */
        void onWrite(long current, long increase);
    }
}

事件

当我把整个下载流程实现之后发现其实整个逻辑还是有点复杂的,所有得想个办法能监控整个下载流程

最开始我定义了几个监听器用来回调,但是并不好用,首先我们整个架构设计的是十分灵活可扩展的,而定义的监听器类型少而且不好扩展

当我们后续添加了其他的流程和步骤后,不得不新加几类监听器或是在原来的监听器类上添加方法,十分麻烦

所以我想到使用事件的方式能更加灵活的扩展,并定义了DownloadEventPublisher用于发布事件和DownloadEventListener用于监听事件,而且支持了Spring的事件监听方式

日志

基于上述的事件方式,我在此基础上实现了几种下载日志

  • 每个流程对应的日志

  • 加载进度更新,压缩进度更新,响应写入进度更新的日志

  • 时间花费的日志

这些日志由于比较详细的打印了整个下载流程的信息,还帮我发现了好多Bug

其他坑

最开始上下文的初始化和销毁各自对应了一个步骤分别位于最开始和最末尾,但是当我在webflux中写完响应后,发现上下文的销毁不会执行

于是我跟了下Spring的源码发现写入方法返回的是Mono.empty(),也就是说,当响应写入后就不会往下调用next方法了,所以在响应写入之后的步骤永远都不会被调用

最后就把上下文初始化和销毁单独出来了,并且在doAfterTerminate时调用销毁方法

结束

基本上的内容就是这样了,不过对于响应式这块的内容还是莫得不是很透,以及有部分操作符也不是很会用,但还是有了解到很多高级的用法

相关文章:

SpringBoot:一个注解就能帮你下载任意对象

介绍 下载功能应该是比较常见的功能了&#xff0c;虽然一个项目里面可能出现的不多&#xff0c;但是基本上每个项目都会有&#xff0c;而且有些下载功能其实还是比较繁杂的&#xff0c;倒不是难&#xff0c;而是麻烦。 所以结合之前的下载需求&#xff0c;我写了一个库来简化…...

oracle全量、增量备份

采用0221222增量备份策略,7天一个轮回 也就是周日0级备份&#xff0c;周1 2 4 5 6 采用2级增量备份&#xff0c;周3采用1级增量备份 打开控制文件自动备份 CONFIGURE CONTROLFILE AUTOBACKUP ON; 配置控制文件备份路径 CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVI…...

React Router 5 vs 6:使用上的主要差异与升级指南

React Router 5 的一些API 在 React Router 6 上有时可能找不到&#xff0c;可能会看到如下画面&#xff1a;export ‘useHistory’ was not found in ‘react-router-dom’ … React Router目前有两个大的版本&#xff0c;即React Router 5、6。React Router 6 在设计上更加简…...

基于LNMP部署wordpress

目录 一.环境准备 二.配置源并安装 三.配置Nginx 四.配置数据库 五.上传源码并替换 六.打开浏览器&#xff0c;输入虚拟机ip访问安装部署 七.扩展增加主题 一.环境准备 centos7虚拟机 关闭防火墙和seliunx stop firewalld #关闭防火墙 setenforce 0 …...

openGauss_5.1.0 企业版快速安装及数据库连接:单节点容器化安装

目录 &#x1f4da;第一章 官网信息&#x1f4da;第二章 安装&#x1f4d7;下载源码&#x1f4d7;下载安装包&#x1f4d7;修改版本&#x1f4d7;解压安装包&#x1f4d7;运行buildDockerImage.sh脚本&#x1f4d7;docker操作&#x1f4d5;查看docker镜像&#x1f4d5;启动dock…...

微信小程序 uniapp+vue城市公交线路查询系统dtjl3

小程序Android端运行软件 微信开发者工具/hbuiderx uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 前端&#xff1a;HTML5,CSS3 VUE 后端&#xff1a;java(springbootssm)/python(flaskdja…...

2024年MathorCup数模竞赛B题问题一二三+部分代码分享

inputFolderPath E:\oracle\images\; outputFolderPath E:\oracle\process\; % 获取文件夹中所有图片的文件列表 imageFiles dir(fullfile(inputFolderPath, *.jpg)); % 设置colorbar范围阈值 threshold 120; % 遍历每个图片文件 for i 1:length(imageFiles) % 读…...

Ubuntu日常配置

目录 修改网络配置 xshell连不上怎么办 解析域名失败 永久修改DNS方法 临时修改DNS方法 修改网络配置 1、先ifconfig确认本机IP地址&#xff08;刚装的机子没有ifconfig&#xff0c;先apt install net-tools&#xff09; 2、22.04版本的ubuntu网络配置在netplan目录下&…...

GMSSL-通信

死磕GMSSL通信-C/C++系列(一) 最近再做国密通信的项目开发,以为国密也就简单的集成一个库就可以完事了,没想到能有这么多坑。遂写下文章,避免重复踩坑。以下国密通信的坑有以下场景 1、使用GMSSL guanzhi/GmSSL进行通信 2、使用加密套件SM2-WITH-SMS4-SM3 使用心得 ​…...

linux 磁盘分区Inode使用率达到100%,导致网站无法创建文件报错 failed:No space leftondevice(

linux 磁盘分区Inode使用率达到100%&#xff0c;导致网站无法创建文件报错 failed:No space left on device 由于这问题直接导致了&#xff0c;网站无法正常运行&#xff01; 提交工单求助阿里后&#xff0c;得到了答案&#xff01; 工程师先让我执行 df -h 和 df -i 通过分析…...

探索Python库的奇妙世界

探索Python库的奇妙世界 Python作为一种流行的编程语言&#xff0c;因其简洁的语法、强大的库支持和广泛的应用场景而备受开发者青睐。在这篇文章中&#xff0c;我们将深入探讨Python库的世界&#xff0c;了解它们如何帮助我们更高效地编写代码&#xff0c;并展示一些最有用的…...

SQL Server 存储函数(funGetId):唯一ID

系统测试时批量生成模拟数据&#xff0c;通过存储函数生成唯一ID。 根据当前时间生成唯一ID&#xff08;17位&#xff09; --自定义函数&#xff1a;根据当前时间组合成一个唯一ID字符串:yearmonthdayhourminutesecondmillisecond drop function funGetId;go--自定义函数&…...

当你的项目体积比较大?你如何做性能优化

在前端开发中&#xff0c;项目体积优化是一个重要的环节&#xff0c;它直接影响到网页的加载速度和用户体验。随着前端项目越来越复杂&#xff0c;引入的依赖也越来越多&#xff0c;如何有效地减少最终打包文件的大小&#xff0c;成为了前端工程师需要面对的挑战。以下是一些常…...

第6章:6.3.2 一张表总结正则表达式的语法 (MATLAB入门课程)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 本节我们用一张表来回顾和总结MATLAB正则表达式的基本语法。这个…...

VBA 实现outlook 当邮件设置category: red 即触发自动创建jira issue

1. 打开: Outlook VBA&#xff08;Visual Basic for Applications&#xff09; 方法一: 在邮件直接搜索:Visual Basic editor 方法二: File -> Options -> Customize Ribbon-> 打钩 如下图: 2.设置运行VBA 脚本: File -> Options -> Trust center -> Trus…...

办公软件巨头CCED、WPS迎来新挑战,新款办公软件已形成普及之势

办公软件巨头CCED、WPS的成长经历 CCED与WPS&#xff0c;这两者均是中国办公软件行业的佼佼者&#xff0c;为人们所熟知。 然而&#xff0c;它们的成功并非一蹴而就&#xff0c;而是经过了长时间的积累与沉淀。 CCED&#xff0c;这款中国大陆早期的文本编辑器&#xff0c;在上…...

架构设计-订单系统之订单系统的架构进化

1、单数据库架构 产品初期&#xff0c;技术团队的核心目标是&#xff1a;“快速实现产品需求&#xff0c;尽早对外提供服务”。 彼时的专车服务都连同一个 SQLServer 数据库&#xff0c;服务层已经按照业务领域做了一定程度的拆分。 这种架构非常简单&#xff0c;团队可以分开…...

性能升级,INDEMIND机器人AI Kit助力产业再蜕变

随着机器人进入到越来越多的生产生活场景中&#xff0c;作业任务和环境变得更加复杂&#xff0c;机器人需要更精准、更稳定、更智能、更灵敏的自主导航能力。 自主导航技术作为机器人技术的核心&#xff0c;虽然经过了多年发展&#xff0c;取得了长足进步&#xff0c;但在实践…...

2024年妈妈杯数学建模C题思路分析-物流网络分拣中心货量预测及人员排班

# 1 赛题 C 题 物流网络分拣中心货量预测及人员排班 电商物流网络在订单履约中由多个环节组成&#xff0c;图 ’ 是一个简化的物流 网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同 流向进行分拣并发往下一个场地&#xff0c;最终使包裹…...

prometheus\skywalking\splunk功能的区别

Prometheus、SkyWalking和Splunk这三个工具在功能上各有特色&#xff0c;以下是它们各自的主要功能特点&#xff1a; Prometheus是一个开源的系统监控和警报工具。它的主要功能包括&#xff1a; 实时监控与警报&#xff1a;Prometheus可以实时监控各种指标&#xff0c;并根据…...

Harmony鸿蒙南向驱动开发-SPI接口使用

功能简介 SPI指串行外设接口&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。SPI是由Motorola公司开发&#xff0c;用于在主设备和从设备之间进行通信。 SPI接口定义了操作SPI设备的通用方法集合…...

芒果YOLOv7改进96:检测头篇DynamicHead动态检测头:即插即用|DynamicHead检测头,尺度感知、空间感知、任务感知

该专栏完整目录链接: 芒果YOLOv7深度改进教程 该创新点:在原始的Dynamic Head的基础上,对核心部位进行了二次的改进,在 原论文 《尺度感知、空间感知、任务感知》 的基础上,在 通道感知 的层级上进行了增强,关注每个像素点的比重。 在自己的数据集上改进,有效涨点就可以…...

独一无二:探索单例模式在现代编程中的奥秘与实践

设计模式在软件开发中扮演着至关重要的角色&#xff0c;它们是解决特定问题的经典方法。在众多设计模式中&#xff0c;单例模式因其独特的应用场景和简洁的实现而广受欢迎。本文将从多个角度详细介绍单例模式&#xff0c;帮助你理解它的定义、实现、应用以及潜在的限制。 1. 什…...

centos7 安装 rabbitmq3.8.5

1.首先安装 erlang 环境&#xff1a; curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash sudo yum install erlang-21.3.8.14-1.el7.x86_64 yum install socat -y 2.安装 rabbitmq https://github.com/rabbitmq/rabbitmq-s…...

利用SOCKS5代理和代理IP提升网络安全与匿名性

一、引言 随着网络技术的迅猛发展&#xff0c;数据安全和隐私保护已成为业界关注的热点。企业和个人用户越来越依赖于各种网络技术来保护敏感信息免受未授权访问。本文将探讨SOCKS5代理、代理IP以及HTTP协议在提升网络安全和匿名性方面的作用和实践应用。 二、基础技术概述 2.…...

C++list模拟实现

Clist模拟实现 list接口总结结点类的模拟实现迭代器的模拟实现迭代器模板参数迭代器类中的构造函数迭代器类中的运算符重载operator和operator - -operator&#xff01; 和operatoroperator*operator->总览 list 类构造函数拷贝构造函数赋值运算符重载operatorclear&#xf…...

设计模式(22):解释器模式

解释器 是一种不常用的设计模式用于描述如何构成一个简单的语言解释器&#xff0c;主要用于使用面向对象语言开发的解释器和解释器设计当我们需要开发一种新的语言时&#xff0c;可以考虑使用解释器模式尽量不要使用解释器模式&#xff0c;后期维护会有很大麻烦。在项目中&…...

kubernetes docker版本安装测试

文章目录 测试环境kubernetes安装环境配置安装程序下载镜像初始化reset环境init构建kubernetes配置授权信息配置网络插件查看状态 简单实例测试 测试环境 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)kubernetes安装 参考kuberneter文档…...

策略模式:灵活调整算法的设计精髓

在软件开发中&#xff0c;策略模式是一种行为型设计模式&#xff0c;它允许在运行时选择算法的行为。通过定义一系列算法&#xff0c;并将每个算法封装起来&#xff0c;策略模式使得算法可以互换使用&#xff0c;这使得算法可以独立于使用它们的客户。本文将详细介绍策略模式的…...

[INS-30014]无法检查指定的位置是否位于 CFS 上

文章目录 一、具体错误二、通用解决方案1、可能的问题原因2、解决方案3、常见原因之hosts文件配置问题hosts配置方法hosts文件不可编辑解决办法 一、具体错误 在安装ORACLE19c的时候&#xff0c;出现无法检查指定的位置是否位于CFS上 二、通用解决方案 1、可能的问题原因 遇…...

如何免费弄一个网站/网站推广的作用在哪里

新建基础软件工程 作者&#xff1a; RootCode 申明&#xff1a;该文档仅供个人学习使用 一、写在前面 目前 Keil 的四款产品&#xff08;软件&#xff09;&#xff1a;MDK-ARM、C51、C251、C166&#xff0c;在用法上极为相似&#xff0c;包括本文讲述的新建软件工程。 本文以…...

wordpress安装数据库错误/seo优化顾问服务阿亮

笔者一直使用jquery的click函数来绑定元素发生的click事件和处理函数&#xff0c;如下&#xff1a; $("#new_data").click(function () {}&#xff09;; 但今天在绑定一个由ajax请求生成的table时&#xff0c;发现click没有响应&#xff0c;查阅文档后发现&#xf…...

系部网站开发计划/全面落实疫情防控优化措施

走进异步编程的世界 - 在 GUI 中执行异步操作 【博主】反骨仔  【原文地址】http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继《开始接触 async/await 异步编程》、《走进异步编程的世界 - 剖析异步方法》后的第三篇。主要介绍在 WinForm 中如何执行异步操作。 目…...

wordpress的搭建环境/百度广告运营

常规的这两个校验工具的用法,可以阅读之前的博文,链接如下: @Valid 和@Validated的区别 那么如果大家看完了基础用法,就可以继续深入学习了,本篇博文主要对这个两个注解的用法进行自定义,实现扩展 废话不多说,上正菜…… 案例一 /*** 大版本信息表** @author fangh*…...

电子商务网站有哪些内容/关键词完整版

密集而有序的弹道攻击 以及有序的动态群体角色冲锋 带来的视觉冲击力极强 哪怕是像素级的单位 所造成的画面感也可以是宏伟的 转载于:https://www.cnblogs.com/dandansang/p/7133610.html...

下载类网站做多久才有流量/福州网站优化

首先说一下 出于性能优化的角度 要尽量少用Activity&#xff0c;甚至只用1个&#xff0c;界面用碎片来承载&#xff0c;这样便于内存以及业务逻辑的管理single instance 适合整个安卓系统只有这样一个活动的情形&#xff0c;比如launcher&#xff0c;所以一般用不到single task…...