netty编程之实现断点续传(分片发送)功能

news2024/11/15 8:21:26

写在前面

在我们使用各种网盘的时候,可以随时的暂停上传,然后继续上传,这其实就是断点续传的功能,本文就看下在netty中如何实现断点续传的功能。

1:核心点介绍

1.1:RandomAccessFile

RandomAccessFile类有一个seek方法,通过该方法可以从文件的指定位置开始读取内容,基于此,我们就可以实现从断点处继续上传的效果,其实也就是实现断点续传了。

1.1:client和server交互协议的封装

定义如下的类来封装交互协议:

public class FileTransferProtocol {

    private Integer transferType; //0请求传输文件、1文件传输指令、2文件传输数据
    private Object transferObj;   //数据对象;(0)FileDescInfo、(1)FileBurstInstruct、(2)FileBurstData

    public Integer getTransferType() {
        return transferType;
    }

    public void setTransferType(Integer transferType) {
        this.transferType = transferType;
    }

    public Object getTransferObj() {
        return transferObj;
    }

    public void setTransferObj(Object transferObj) {
        this.transferObj = transferObj;
    }

}

其中transferType有如下的值:

1:0请求传输文件 
    客户端请求开始上传文件,对应的信息封装类是FileDescInfo,描述了要上传的文件的名称大小等信息
2:1文件传输指令
    客户端和服务端共同使用,对应的信息封装类是FileBurstInstruct,通过抽象的指令值来标记当前传输处于哪个阶段
3:2文件传输数据
    用来封装具体要上传的数据,位置信息等

1.3:protostuff

数据传输的序列化方式采用protostuff,因为其在对象序列化上的性能表现还是比较优秀(序列化的速度以及序列化的大小),并且使用方式也比较简单。

2:正式编码

2.1:server

server main:

package com.dahuyou.netty.transferfile.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {

    //配置服务端NIO线程组
    private EventLoopGroup parentGroup = new NioEventLoopGroup(); //NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    private EventLoopGroup childGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture bing(int port) {
        ChannelFuture channelFuture = null;
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)    //非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());
            channelFuture = b.bind(port).syncUninterruptibly();
            this.channel = channelFuture.channel();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                System.out.println("netty server start done. {}");
            } else {
                System.out.println("netty server start error. {}");
            }
        }
        return channelFuture;
    }

    public void destroy() {
        if (null == channel) return;
        channel.close();
        parentGroup.shutdownGracefully();
        childGroup.shutdownGracefully();
    }

    public Channel getChannel() {
        return channel;
    }

}

MyChannelInitializer:

package com.dahuyou.netty.transferfile.server;

import com.dahuyou.netty.transferfile.codec.ObjDecoder;
import com.dahuyou.netty.transferfile.codec.ObjEncoder;
import com.dahuyou.netty.transferfile.domain.FileTransferProtocol;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        //对象传输处理
        channel.pipeline().addLast(new ObjDecoder(FileTransferProtocol.class));
        channel.pipeline().addLast(new ObjEncoder(FileTransferProtocol.class));
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());
    }

}

这里设置了基于protostuff的编解码器,以及消息处理的handler:

package com.dahuyou.netty.transferfile.server;

import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import com.dahuyou.netty.transferfile.domain.*;
import com.dahuyou.netty.transferfile.util.CacheUtil;
import com.dahuyou.netty.transferfile.util.FileUtil;
import com.dahuyou.netty.transferfile.util.MsgUtil;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("链接报告开始");
        /*System.out.println("链接报告信息:有一客户端链接到本服务端。channelId:" + channel.id());
        System.out.println("链接报告IP:" + channel.localAddress().getHostString());
        System.out.println("链接报告Port:" + channel.localAddress().getPort());
        System.out.println("链接报告完毕");*/
    }

    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端断开链接" + ctx.channel().localAddress().toString());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //数据格式验证
        if (!(msg instanceof FileTransferProtocol)) return;

        FileTransferProtocol fileTransferProtocol = (FileTransferProtocol) msg;
        //0传输文件'请求'、1文件传输'指令'、2文件传输'数据'
        switch (fileTransferProtocol.getTransferType()) {
            case 0:
                FileDescInfo fileDescInfo = (FileDescInfo) fileTransferProtocol.getTransferObj();

                //断点续传信息,实际应用中需要将断点续传信息保存到数据库中
                FileBurstInstruct fileBurstInstructOld = CacheUtil.burstDataMap.get(fileDescInfo.getFileName());
                if (null != fileBurstInstructOld) {
                    if (fileBurstInstructOld.getStatus() == Constants.FileStatus.COMPLETE) {
                        CacheUtil.burstDataMap.remove(fileDescInfo.getFileName());
                    }
                    //传输完成删除断点信息
                    System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 服务端,接收客户端传输文件请求[断点续传]。" + JSON.toJSONString(fileBurstInstructOld));
                    ctx.writeAndFlush(MsgUtil.buildTransferInstruct(fileBurstInstructOld));
                    return;
                }

                //发送信息
                FileTransferProtocol sendFileTransferProtocol = MsgUtil.buildTransferInstruct(Constants.FileStatus.BEGIN, fileDescInfo.getFileUrl(), 0);
                ctx.writeAndFlush(sendFileTransferProtocol);
                System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 服务端,接收客户端传输文件请求。" + JSON.toJSONString(fileDescInfo));
                break;
            case 2:
                FileBurstData fileBurstData = (FileBurstData) fileTransferProtocol.getTransferObj();
                FileBurstInstruct fileBurstInstruct = FileUtil.writeFile("E://", fileBurstData);

                //保存断点续传信息
                CacheUtil.burstDataMap.put(fileBurstData.getFileName(), fileBurstInstruct);

                ctx.writeAndFlush(MsgUtil.buildTransferInstruct(fileBurstInstruct));
                System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 服务端,接收客户端传输文件数据。" + JSON.toJSONString(fileBurstData));

                //传输完成删除断点信息
                if (fileBurstInstruct.getStatus() == Constants.FileStatus.COMPLETE) {
                    CacheUtil.burstDataMap.remove(fileBurstData.getFileName());
                }
                break;
            default:
                break;
        }

    }

    /**
     * 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }

}

主要看方法channelRead,分为如下几种情况:

0:
    根据是否是续传返回不同的消息,控制client上传的不同行为
2:
    如果是上传文件,则保存文件,完成当前文件内容的上传,并返回续传信息给client,client继续上传

2.2:client

client main:

package com.dahuyou.netty.transferfile.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {

    //配置服务端NIO线程组
    private EventLoopGroup workerGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture connect(String inetHost, int inetPort) {
        ChannelFuture channelFuture = null;
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.AUTO_READ, true);
            b.handler(new MyChannelInitializer());
            channelFuture = b.connect(inetHost, inetPort).syncUninterruptibly();
            this.channel = channelFuture.channel();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                System.out.println("netty client start done. {}");
            } else {
                System.out.println("netty client start error. {}");
            }
        }
        return channelFuture;
    }

    public void destroy() {
        if (null == channel) return;
        channel.close();
        workerGroup.shutdownGracefully();
    }

}

MyChannelInitializer:

package com.dahuyou.netty.transferfile.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import com.dahuyou.netty.transferfile.codec.ObjDecoder;
import com.dahuyou.netty.transferfile.codec.ObjEncoder;
import com.dahuyou.netty.transferfile.domain.FileTransferProtocol;

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        //对象传输处理
        channel.pipeline().addLast(new ObjDecoder(FileTransferProtocol.class));
        channel.pipeline().addLast(new ObjEncoder(FileTransferProtocol.class));
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyClientHandler());
    }

}

同样设置了protostuff的编解码器,以及消息处理类:

package com.dahuyou.netty.transferfile.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import com.dahuyou.netty.transferfile.domain.Constants;
import com.dahuyou.netty.transferfile.domain.FileBurstData;
import com.dahuyou.netty.transferfile.domain.FileBurstInstruct;
import com.dahuyou.netty.transferfile.domain.FileTransferProtocol;
import com.dahuyou.netty.transferfile.util.FileUtil;
import com.dahuyou.netty.transferfile.util.MsgUtil;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("链接报告开始");
        /*System.out.println("链接报告信息:本客户端链接到服务端。channelId:" + channel.id());
        System.out.println("链接报告IP:" + channel.localAddress().getHostString());
        System.out.println("链接报告Port:" + channel.localAddress().getPort());
        System.out.println("链接报告完毕");*/
    }

    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("断开链接" + ctx.channel().localAddress().toString());
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //数据格式验证
        if (!(msg instanceof FileTransferProtocol)) return;

        FileTransferProtocol fileTransferProtocol = (FileTransferProtocol) msg;
        //0传输文件'请求'、1文件传输'指令'、2文件传输'数据'
        switch (fileTransferProtocol.getTransferType()) {
            case 1:
                FileBurstInstruct fileBurstInstruct = (FileBurstInstruct) fileTransferProtocol.getTransferObj();
                //Constants.FileStatus {0开始、1中间、2结尾、3完成}
                if (Constants.FileStatus.COMPLETE == fileBurstInstruct.getStatus()) {
                    ctx.flush();
                    ctx.close();
                    System.exit(-1);
                    return;
                }
                FileBurstData fileBurstData = FileUtil.readFile(fileBurstInstruct.getClientFileUrl(), fileBurstInstruct.getReadPosition());
                ctx.writeAndFlush(MsgUtil.buildTransferData(fileBurstData));
                System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 客户端传输文件信息。 FILE:" + fileBurstData.getFileName() + " SIZE(byte):" + (fileBurstData.getEndPos() - fileBurstData.getBeginPos()));
                break;
            default:
                break;
        }

        /**模拟传输过程中断,场景测试可以注释掉
         *

        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " [主动断开链接,模拟断点续传]");
        ctx.flush();
        ctx.close();
        System.exit(-1);*/
    }

    /**
     * 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }

}

主要看方法channelRead,处理文件传输,根据是首次上传还是续传,从要上传的文件中获取字节码数据写到server,其中,体现续传的代码为FileUtil.readFile:

public class FileUtil {

    private static final int READ_BYTE_ONCE = 1024;
    public static FileBurstData readFile(String fileUrl, Integer readPosition) throws IOException {
        File file = new File(fileUrl);
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");//r: 只读模式 rw:读写模式
        // 这里体现了断点续传的续哦!!!
        randomAccessFile.seek(readPosition);

    }

}

randomAccessFile.seek(readPosition);这里跳一下子就体现了断点续传的续哦!!!

2.3:测试

server启动类:

package com.dahuyou.netty.transferfile.test;

import com.dahuyou.netty.transferfile.server.NettyServer;

public class NettyServerTest {

    public static void main(String[] args) {
        System.out.println("hi netty server");
        //启动服务
        new NettyServer().bing(7397);
    }

}

client启动类:

package com.dahuyou.netty.transferfile.test;

import com.dahuyou.netty.transferfile.client.NettyClient;
import com.dahuyou.netty.transferfile.domain.FileTransferProtocol;
import com.dahuyou.netty.transferfile.util.MsgUtil;
import io.netty.channel.ChannelFuture;
import java.io.File;

public class NettyClientTest {

    public static void main(String[] args) {

        //启动客户端
        ChannelFuture channelFuture = new NettyClient().connect("127.0.0.1", 7397);

        //文件信息{文件大于1024kb方便测试断点续传}
//        File file = new File("C:\\Users\\fuzhengwei1\\Desktop\\测试传输文件.rar");
        File file = new File("D:\\xiaofuge_sourcecode\\interview-master\\dahuyou-study-netty\\transferfile\\src\\test\\java\\com\\dahuyou\\netty\\transferfile\\test\\测试传输文件.rar");
        FileTransferProtocol fileTransferProtocol = MsgUtil.buildRequestTransferFile(file.getAbsolutePath(), file.getName(), file.length());

        //发送信息;FILE:测试传输文件请求传输文件
        channelFuture.channel().writeAndFlush(fileTransferProtocol);

    }

}

在client中首次启动发送请求上传文件的协议消息,发起文件上传的流程,我们测试的文件大小为1360字节,而首次上传文件的大小为1024字节,如下代码:

public class FileUtil {

    private static final int READ_BYTE_ONCE = 1024;
    public static FileBurstData readFile(String fileUrl, Integer readPosition) throws IOException {
        File file = new File(fileUrl);
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");//r: 只读模式 rw:读写模式
        randomAccessFile.seek(readPosition);
//        byte[] bytes = new byte[1024 * 100];
        byte[] bytes = new byte[READ_BYTE_ONCE];    
}

所以第一次上传后文件是打不开的如下:
在这里插入图片描述
再次上传后文件就可以正常打开了。
最后看下日志输出:
在这里插入图片描述
在这里插入图片描述

写在后面

参考文章列表

protostuff序列化方式学习 。

netty编程之使用protostuff作为数据传输载体 。

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

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

相关文章

汽车信息安全--MCU Flash读保护真的没有后门吗?

目录 1.修bug修出的灵感 2.串行编程接口协议 3.毛刺攻击 4.RH850 串行编程模式 5.小结 1.修bug修出的灵感 ECU量产后通过密码控制来防止通过Debug口读取Flash的程序和数据。 这是应该是共识了&#xff0c;但是这样做真的就万无一失了吗&#xff1f; 最近解决了个问题&…

Linux操作系统在虚拟机VM上的安装【CentOS版本】

目录 准备工作 "CPU虚拟化"的方法 VMware的安装 Linux镜像文件的下载 开始安装 声明 新建虚拟机 安装CentOS7.6 配置Linux(CentOS7.6)操作系统 配置分区【学习者可以直接点击自动配置分区&#xff0c;不过还是建议学习一下手动分区】 分区原则 添加分区 …

适配制造业的设备管理系统有哪些?本文给你答案!

本文将带大家盘点10款设备管理系统&#xff0c;供企业选型参考。 外包单位数量众多&#xff0c;但难以对他们进行统一协同管理&#xff1f;危险作业的作业申请、作业审批使用线下纸质版&#xff0c;不仅效率低还管控力不足&#xff1f;相关部门想监测、管理设备安全风险&#x…

AcWing898. 数字三角形

线性DP 董晓老师的讲解是从下标0开始算的&#xff0c;其实我们从1开始也可以&#xff0c;我感觉这里从1开始更好理解。是从下往上计算的。j负责列的计算&#xff0c;往上计算时逐步收窄横向的范围&#xff0c;i是纵向的从下往上算&#xff0c; 下面是内存布局 下面是逻辑上的…

【网络安全】打开这份“开学礼” 谨防骗子“冲业绩”

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s…

今年读过最绝的一本书!《自然语言处理原理、方法与应用》,几乎把自然语言处理讲透了【附PDF】

书籍内容介绍&#xff1a; 本书系统阐述自然语言处理基础知识&#xff0c;以及自然语言处理高级模型应用等高级知识。 全书共11章&#xff1a;第1&#xff5e;5章为自然语言处理的基础知识&#xff0c;第6&#xff5e;11章将自然语言处理知识应用于实战。书中主要内容包括预训…

99% 的人都不知道,哪种 Python 循环最快?

大家好&#xff0c;在Python编程中&#xff0c;循环是我们经常使用的工具&#xff0c;用来重复执行一些代码块。我们通常会在 for 循环、while 循环和列表推导式中选择一种来解决问题。但你是否曾想过&#xff1a;哪种循环效率最高&#xff1f; 今天&#xff0c;我们就来一次轻…

Vue -- 总结 02

Vue脚手架 安装Vue脚手架&#xff1a; 在cmd中安装(输入):npm install -g vue/cli 如果下载慢或下载不了&#xff0c;可以安装(cmd输入)淘宝镜像:npm config set registry https://registry.npmmirror.com 用命令创建Vue项目 在要创建的vue项目的文件夹里输入 cmd 回车 创…

CSS 2D3D转换与动画

CSS 2D&3D转换与动画 字体图标 字体图标展示的是图标&#xff0c;本质是字体 处理简单的&#xff0c;颜色单一的图片&#xff0c;使用字体图标 使用&#xff1a; 下载&#xff1a;https://www.iconfont.cn/ 引入字体图标样式表 对应标签上classz增加对应的类名&…

使用pytest的 reporting特性来生成报告

特性 1.HTML 报告&#xff1a;使用 pytest-html 插件&#xff0c;你可以生成 HTML 格式的测试报告。只需在项目的 pytest.ini 文件中添加以下内容&#xff1a; [pytest] addopts --htmlreport.html然后&#xff0c;在运行 pytest 时&#xff0c;将会生成一个名为 report.htm…

Serilog文档翻译系列(二) - 设置AspNetCore应用程序

Serilog 日志记录适用于 ASP.NET Core。此包将 ASP.NET Core 的日志消息通过 Serilog 进行路由&#xff0c;使你可以将有关 ASP.NET 内部操作的信息写入与应用程序事件相同的 Serilog 接收器中。 安装并配置了 Serilog.AspNetCore 后&#xff0c;你可以直接通过 Serilog 或ASP…

CDS Association

CDS view Association 引入的缘由 CDS 视图不会被业务用户直接访问&#xff0c;而是会被 ABAP 程序、Fiori 应用程序或 BI 前端工具使用。假设在 5 个不同的表上使用 JOINS 创建了 CDS 视图&#xff0c;则每次触发此 CDS 视图时都会执行此 JOIN 条件。即使业务用户只查看 2 个…

十分钟弄懂最快的APP自动化工具uiautomator2

相信很多使用appium做过APP自动化的人都深有感触&#xff1a; appium运行慢、时间长uiautomatorviewer定位元素时得关掉appium server在低版本的appium上获取toast需要切换automationName 现在有一款自动化测试工具uiautomator2&#xff0c;它几乎完美的避免了以上的问题。简…

数据结构---循环队列---树的基本概念

目录 一、队列 1.1.队列 1.创建循环队列&#xff08;顺序结构&#xff09; 2.判断队满 3.判断队空 4.进队 5.出队 6.销毁 二、树 2.1.树的特点 2.2.基本概念 1.根节点 2.分支节点 3.叶节点 4.层 5.深度 6.高度 7.度 2.3.二叉树 1.特点 2.遍历方式 2.4.满二…

编程要由“手动挡”变“自动挡”了?Cursor+Claude-3.5-Sonnet,Karpathy大神点赞的AI代码神器!如何使用详细教程

Cursor情况简介 AI大神Andrej Karpathy都被震惊了&#xff01;他最近在试用 VS Code Cursor Claude Sonnet 3.5&#xff0c;结果发现这玩意儿比GitHub Copilot还好用&#xff01; Cursor在短短时间内迅速成为程序员群体的顶流神器&#xff0c;其背后的原因在于其默认使用Open…

在postman中使用javascript脚本生成sign签名

大多数线上api接口服务都需要提供签名才可以正常访问。虽然带来了安全&#xff0c;单有时为了快速验证接口的某个功能&#xff0c;就不得不编写代码&#xff0c;计算签名然后再请求。那么&#xff0c;使用postman提供的script功能&#xff0c;是否能实现签名计算功能吗&#xf…

HarmonyOS开发实战( Beta5版)滑动白块问题解决最佳实践

当应用程序需要使用列表显示内容时&#xff0c;通常会使用ListLazyForEach组件来实现。但是列表中需要显示耗时加载的内容时&#xff0c;仅依靠ListLazyForEach不足以获得最优的用户体验。例如显示在线网络图片&#xff0c;在弱网以及快速滑动浏览的场景下&#xff0c;由于来不…

哪种无线可视挖耳勺最实用?五大优良黑马机型测评

无线可视挖耳勺是热度特别高的个人清洁工具&#xff0c;不仅能够更加清晰地观察自己耳内的状况&#xff0c;从而更加安全、有效地清洁耳朵&#xff0c;可以发挥多种多样的作用&#xff0c;但也存在品牌繁杂的情况&#xff0c;其中还有一些不专业的产品。在市面上&#xff0c;我…

Win11 本地部署大模型 WebUI + ComfyUI

Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 Web 用户界面&#xff08;WebUI&#xff09;&#xff0c;它被设计用于完全离线操作。该项目最初被称为 Ollama WebUI&#xff0c;后来更名为 Open WebUI。Open WebUI 的主要目的是为本地的大语言模型&#xff08;LLMs&…

一条执行24s的SQL产生7小时主备延时(案例详解)

前言 在之前的文章《一条执行24s的SQL竟产生7小时数据延时&#xff0c;数据库高可用做了个寂寞》中&#xff0c;我们描述了一个问题现象&#xff1a;一条在主库执行24秒的SQL语句&#xff0c;却导致了主从延时长达7小时。文章发布后&#xff0c;引发了小伙伴的热烈讨论&#x…