【八】Netty HTTP协议--文件服务系统开发

news2024/11/28 17:52:33

Netty HTTP协议--文件服务系统开发

  • 介绍
  • HTTP-文件系统场景
    • 描述
    • 流程图
  • 代码展示
    • netty依赖
    • 服务端启动类 HttpFileServer
    • 服务端业务逻辑处理类 HttpFileServerHandler
  • 结果展示
    • 错误路径
    • 文件夹路径
    • 文件路径
  • 遗留bug
    • bug版本
  • 总结

介绍

由于Netty天生是异步事件驱动的架构,因此基于NIO TCP 协议栈开发的HTTP协议栈也是异步非阻塞的。Netty的HTTP协议栈无论在性能还是可靠性上,都表现优异,非常适合在非Web容器的场景下应用,相比于传统的Tomcat,Jetty等Web容器,它更加轻量和小巧,灵活性和定制性也更好。

HTTP-文件系统场景

描述

文件服务器使用HTTP协议对外提供服务,当客户通过浏览器访问文件服务器时,对访问路径进行检查,检查失败时返回HTTP403错误,如果校验通过,以链接的方式打开当前文件目录,每个目录或者文件都是个超链接,可以递归访问。
如果是目录,可以继续递归访问它下面的子目录或者文件,如果是文件且可读,则可以在浏览器直接打开。或者通过【目标另存为】下载该文件。

流程图

在这里插入图片描述

代码展示

netty依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty5-all' for 5.0-->
            <version>5.0.0.Alpha1</version>
            <scope>compile</scope>
        </dependency>

服务端启动类 HttpFileServer

public class HttpFileServer {
    //默认路径,我们可以自定义自己的
    private static final String DEFAULT_URL="/Netty-protobuf/";

    public void run(final int port,final String url){
        EventLoopGroup boosGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(boosGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //HTTP请求消息解码器
                            socketChannel.pipeline()
                                    .addLast("http-decoder",new HttpRequestDecoder());
                            //HttpObjectAggregator解码器,它的作用是将多个消息转换为单一的FullHttpRequest或者FullHttpResponse
                            //原因是Http解码器在每个HTTP消息中会生成多个消息对象
                            socketChannel.pipeline()
                                    .addLast("http-aggregator",new HttpObjectAggregator(65536));
                            socketChannel.pipeline()
                                    .addLast("http-encoder",new HttpResponseEncoder());
                            //ChunkedWriteHandler 支持异步发送大的码流,但不占用过多的内存,防止发生Java内存溢出错误
                            socketChannel.pipeline()
                                    .addLast("http-chunked",new ChunkedWriteHandler());
                            //HttpFileServerHandler文件服务器业务逻辑处理
                            socketChannel.pipeline()
                                    .addLast(new HttpFileServerHandler(url));
                        }
                    });
            ChannelFuture future=bootstrap.bind("127.0.0.1",port).sync();
            System.out.println("Http 文件目录服务器启动,网址是: http://127.0.0.1:"+port+url);
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //优雅退出
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new HttpFileServer().run(8080,DEFAULT_URL);
    }
}

服务端业务逻辑处理类 HttpFileServerHandler

public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String url;
    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    public HttpFileServerHandler(String url) {
        this.url = url;
    }

    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
        //如果解码失败,直接400返回
        if (!fullHttpRequest.getDecoderResult().isSuccess()) {
            sendError(channelHandlerContext, BAD_REQUEST);
            return;
        }
        //如果不是get请求,则 405 返回
        if (fullHttpRequest.getMethod() != HttpMethod.GET) {
            sendError(channelHandlerContext, METHOD_NOT_ALLOWED);
            return;
        }

        final String uri = fullHttpRequest.getUri();
        final String path = sanitizeUri(uri);
        System.out.println("请求的uri is = "+uri);
        //路径不合法,path==null.返回403
        if (path == null) {
            sendError(channelHandlerContext, FORBIDDEN);
            return;
        }

        File file = new File(path);
        //如果文件不存在获知是系统隐藏文件,则 404 返回
        if (file.isHidden() || !file.exists()) {
            sendError(channelHandlerContext, NOT_FOUND);
            return;
        }

        //如果是目录
        if (file.isDirectory()) {
            if (uri.endsWith("/")) {
                //返回给客户端
                sendListing(channelHandlerContext, file);
            } else {
                //进行重定向
                sendRedirect(channelHandlerContext, uri + "/");
            }
            return;
        }

        //如果不是合法文件,则 403
        if (!file.isFile()) {
            sendError(channelHandlerContext, FORBIDDEN);
        }

        RandomAccessFile randomAccessFile = null;

        try {
            //以只读的方式打开文件
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException exception) {
            //打开失败,返回 404
            sendError(channelHandlerContext, NOT_FOUND);
            return;
        }
        //获取文件的长度
        long fileLength = randomAccessFile.length();
        //构造响应体
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK);
        //设置返回字节数,就是文件的字节数
        //setContentLength(response, fileLength);
        //设置content type
        setContentTypeHeader(response, file);
        //设置 connection
        if (isKeepAlive(fullHttpRequest)) {
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
        //直接将 randomAccessFile 转换为 ByteBuf对象然后 flush。
        //没有采用 ChannelFuture方式。这个方式 一直有个Bug无法解决。
        byte[] b=new byte[(int)randomAccessFile.length()];
        randomAccessFile.read(b);
        ByteBuf byteBuf=Unpooled.copiedBuffer( b);
        response.content().writeBytes(byteBuf);
        byteBuf.release();
        channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
       /* channelHandlerContext.write(response);
        //通过ChunkedFile 对象之间将文件写入到缓冲区中
        ChannelFuture sendFileFuture = channelHandlerContext.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192),
                channelHandlerContext.newProgressivePromise());
        //增加 ChannelProgressiveFutureListener ,如果发送完成,打印 Transfer complete
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationProgressed(ChannelProgressiveFuture channelProgressiveFuture, long progress, long total) throws Exception {
                //打印进度条
                if (total < 0) {
                    System.err.println("Transfer progress : " + progress);
                } else {
                    System.err.println("Transfer progress : " + progress + "/" + total);
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture channelProgressiveFuture) throws Exception {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);*/

        System.out.println("do writeAndFlush ");

        //if (isKeepAlive(fullHttpRequest)) {
            //lastContentFuture.addListener(ChannelFutureListener.CLOSE);
      //  }


    }

    @Override
    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
        cause.printStackTrace();
        if (context.channel().isActive()) {
            sendError(context, INTERNAL_SERVER_ERROR);
        }
    }

    private String sanitizeUri(String uri) {
        try {
            //使用JDK 的 java.net.URLDecoder 对uri 进行解码,使用UTF-8字符集
            uri = URLDecoder.decode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            try {
                //出现异常 再用字符集 ISO-8859-1 解码
                uri = URLDecoder.decode(uri, "ISO-8859-1");
            } catch (UnsupportedEncodingException ex) {
                throw new Error();
            }
        }
        //解码成功后 对uri 进行校验
        if (!uri.startsWith(url)) {
            return null;
        }
        //进行校验
        if (!uri.startsWith("/")) {
            return null;
        }

        uri = uri.replace('/', File.separatorChar);
        //进行校验
        if (uri.contains(File.separator + ".")
                || uri.contains('.' + File.separator)
                || uri.startsWith(".")
                || uri.endsWith(".")
                || INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        //对文件进行拼接,使用当前运行程序所在的工程目录+URL 构造绝对路径
        return System.getProperty("user.dir") + File.separator + uri;
    }

    private static void sendListing(ChannelHandlerContext context,File dir){
        //构造响应消息
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,OK);
        //设置消息头类型
        response.headers().set(CONTENT_TYPE,"text/html;charset=UTF-8");
        String dirPath=dir.getPath();
        StringBuilder builder=new StringBuilder();
        //编辑html 格式
        builder.append("<!DOCTYPE html>\r\n");
        builder.append("<html><head><title>");
        builder.append(dirPath);
        builder.append(" 目录: ");
        builder.append("</title></head><body>\r\n");
        builder.append("<h3>");
        builder.append(dirPath).append(" 目录: ");
        builder.append("</h3>\r\n");
        builder.append("<ul>");
        //..链接
        builder.append("<li>链接: <a href=\"../\">..</a></li>\r\n");
        //展示根目录下的所有文件和文件夹,同时用超链接来标识
        for (File f:dir.listFiles()){
            if (f.isHidden()||!f.canRead()){
                continue;
            }
            String name=f.getName();
            if (!ALLOWED_FILE_NAME.matcher(name).matches()){
                continue;
            }
            //超链接
            builder.append("<li> 链接: <a href=\"");
            builder.append(name);
            builder.append("\">");
            builder.append(name);
            builder.append("</a></li>\r\n");
        }
        builder.append("</ul></body></html>\r\n");
        //构造缓冲对象
        ByteBuf byteBuf= Unpooled.copiedBuffer(builder, CharsetUtil.UTF_8);
        response.content().writeBytes(byteBuf);
        byteBuf.release();
        //将response 信息刷新到SocketChannel中
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendRedirect(ChannelHandlerContext context,String newUri){
        // 构造 响应体,302  重定向
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,FOUND);
        response.headers().set(LOCATION,newUri);
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext context,HttpResponseStatus status){
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status,Unpooled.copiedBuffer("Failure : "+status+"\r\n",CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    }

    private static void setContentTypeHeader(HttpResponse response,File file){
        MimetypesFileTypeMap mimetypesFileTypeMap=new MimetypesFileTypeMap();
        response.headers().set(CONTENT_TYPE,mimetypesFileTypeMap.getContentType(file.getPath()));
    }
}

结果展示

错误路径

在这里插入图片描述

文件夹路径

在这里插入图片描述

文件路径

在这里插入图片描述

遗留bug

其实最开始实现文件打开的逻辑,并不是 上面代码所展示的,直接将 RandomAccessFile 转换为ByteBuf,然后 writeFlush 到channel中去。而是采用的ChannelFuture方式。可以添加事件,打印下载打开文件的进度。比较优雅。但是一直无法解决Bug。网上也没有比较好的方案。所以也是一个遗憾。希望有大神可以解决这个问题。

bug版本

HttpFileServerHandler

public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String url;
    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    public HttpFileServerHandler(String url) {
        this.url = url;
    }

    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
        //如果解码失败,直接400返回
        if (!fullHttpRequest.getDecoderResult().isSuccess()) {
            sendError(channelHandlerContext, BAD_REQUEST);
            return;
        }
        //如果不是get请求,则 405 返回
        if (fullHttpRequest.getMethod() != HttpMethod.GET) {
            sendError(channelHandlerContext, METHOD_NOT_ALLOWED);
            return;
        }

        final String uri = fullHttpRequest.getUri();
        final String path = sanitizeUri(uri);
        System.out.println("请求的uri is = "+uri);
        //路径不合法,path==null.返回403
        if (path == null) {
            sendError(channelHandlerContext, FORBIDDEN);
            return;
        }

        File file = new File(path);
        //如果文件不存在获知是系统隐藏文件,则 404 返回
        if (file.isHidden() || !file.exists()) {
            sendError(channelHandlerContext, NOT_FOUND);
            return;
        }

        //如果是目录
        if (file.isDirectory()) {
            if (uri.endsWith("/")) {
                //返回给客户端
                sendListing(channelHandlerContext, file);
            } else {
                //进行重定向
                sendRedirect(channelHandlerContext, uri + "/");
            }
            return;
        }

        //如果不是合法文件,则 403
        if (!file.isFile()) {
            sendError(channelHandlerContext, FORBIDDEN);
        }

        RandomAccessFile randomAccessFile = null;

        try {
            //以只读的方式打开文件
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException exception) {
            //打开失败,返回 404
            sendError(channelHandlerContext, NOT_FOUND);
            return;
        }
        //获取文件的长度
        long fileLength = randomAccessFile.length();
        //构造响应体
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK);
        //设置返回字节数,就是文件的字节数
        //setContentLength(response, fileLength);
        //设置content type
        setContentTypeHeader(response, file);
        //设置 connection
        if (isKeepAlive(fullHttpRequest)) {
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
       /* byte[] b=new byte[(int)randomAccessFile.length()];
        randomAccessFile.read(b);
        ByteBuf byteBuf=Unpooled.copiedBuffer( b);
        response.content().writeBytes(byteBuf);
        byteBuf.release();
        channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);*/
       channelHandlerContext.write(response);
        //通过ChunkedFile 对象之间将文件写入到缓冲区中
        ChannelFuture sendFileFuture = channelHandlerContext.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192),
                channelHandlerContext.newProgressivePromise());
        //增加 ChannelProgressiveFutureListener ,如果发送完成,打印 Transfer complete
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationProgressed(ChannelProgressiveFuture channelProgressiveFuture, long progress, long total) throws Exception {
                //打印进度条
                if (total < 0) {
                    System.err.println("Transfer progress : " + progress);
                } else {
                    System.err.println("Transfer progress : " + progress + "/" + total);
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture channelProgressiveFuture) throws Exception {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

        System.out.println("do writeAndFlush ");

        if (!isKeepAlive(fullHttpRequest)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }


    }

    @Override
    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
        cause.printStackTrace();
        if (context.channel().isActive()) {
            sendError(context, INTERNAL_SERVER_ERROR);
        }
    }

    private String sanitizeUri(String uri) {
        try {
            //使用JDK 的 java.net.URLDecoder 对uri 进行解码,使用UTF-8字符集
            uri = URLDecoder.decode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            try {
                //出现异常 再用字符集 ISO-8859-1 解码
                uri = URLDecoder.decode(uri, "ISO-8859-1");
            } catch (UnsupportedEncodingException ex) {
                throw new Error();
            }
        }
        //解码成功后 对uri 进行校验
        if (!uri.startsWith(url)) {
            return null;
        }
        //进行校验
        if (!uri.startsWith("/")) {
            return null;
        }

        uri = uri.replace('/', File.separatorChar);
        //进行校验
        if (uri.contains(File.separator + ".")
                || uri.contains('.' + File.separator)
                || uri.startsWith(".")
                || uri.endsWith(".")
                || INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        //对文件进行拼接,使用当前运行程序所在的工程目录+URL 构造绝对路径
        return System.getProperty("user.dir") + File.separator + uri;
    }

    private static void sendListing(ChannelHandlerContext context,File dir){
        //构造响应消息
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,OK);
        //设置消息头类型
        response.headers().set(CONTENT_TYPE,"text/html;charset=UTF-8");
        String dirPath=dir.getPath();
        StringBuilder builder=new StringBuilder();
        //编辑html 格式
        builder.append("<!DOCTYPE html>\r\n");
        builder.append("<html><head><title>");
        builder.append(dirPath);
        builder.append(" 目录: ");
        builder.append("</title></head><body>\r\n");
        builder.append("<h3>");
        builder.append(dirPath).append(" 目录: ");
        builder.append("</h3>\r\n");
        builder.append("<ul>");
        //..链接
        builder.append("<li>链接: <a href=\"../\">..</a></li>\r\n");
        //展示根目录下的所有文件和文件夹,同时用超链接来标识
        for (File f:dir.listFiles()){
            if (f.isHidden()||!f.canRead()){
                continue;
            }
            String name=f.getName();
            if (!ALLOWED_FILE_NAME.matcher(name).matches()){
                continue;
            }
            //超链接
            builder.append("<li> 链接: <a href=\"");
            builder.append(name);
            builder.append("\">");
            builder.append(name);
            builder.append("</a></li>\r\n");
        }
        builder.append("</ul></body></html>\r\n");
        //构造缓冲对象
        ByteBuf byteBuf= Unpooled.copiedBuffer(builder, CharsetUtil.UTF_8);
        response.content().writeBytes(byteBuf);
        byteBuf.release();
        //将response 信息刷新到SocketChannel中
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendRedirect(ChannelHandlerContext context,String newUri){
        // 构造 响应体,302  重定向
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,FOUND);
        response.headers().set(LOCATION,newUri);
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext context,HttpResponseStatus status){
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status,Unpooled.copiedBuffer("Failure : "+status+"\r\n",CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    }

    private static void setContentTypeHeader(HttpResponse response,File file){
        MimetypesFileTypeMap mimetypesFileTypeMap=new MimetypesFileTypeMap();
        response.headers().set(CONTENT_TYPE,mimetypesFileTypeMap.getContentType(file.getPath()));
    }
}

总结

好了,代码比较简单,但是实现的功能比较cool。这个功能和nginx 的文件功能比较像。nginx 只需要配置参数就可以实现对应的功能。大家可以 敲敲代码,实现了功能,是不是发现对HTTP和Netty的理解又进了一步。

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

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

相关文章

java EE初阶 — Synchronized 的原理

文章目录1. Synchronized 的优化操作1.1 偏向锁1.2 轻量级锁&#xff08;自旋锁&#xff09;1.3 重量级锁2. 其他的优化操作2.1 锁消除2.2 锁粗化3. 相关面试题1. Synchronized 的优化操作 两个线程针对同一个对象加锁&#xff0c;就会产生阻塞等待。 Synchronized 内部其实还有…

ubuntu docker elasticsearch kibana安装部署

ubuntu docker elasticsearch 安装部署 所有操作尽量在root下操作. 安装docker 1. 由于是基于宝塔面板安装的所以简答的点击操作即可完成安装. 我这里已经是正常的安装好了. 2.dcoker 镜像加速 https://cr.console.aliyun.com/cn-hangzhou/instances访问这个网址进去进行了…

快速上手Golang

自动推导赋值:自动推导赋值Go中 不同的数据类型不能进行计算对于浮点型默认都是float64 精确到小数点后15位单引号的 为字节类型 一位0~255的字符转换双引号的 为字符串类型多重赋值多重赋值a,b:1,2格式输出格式输出printf“%3d”三位整数&#xff0c;不满足三位时头部补空格“…

录制课程用什么软件好?3款超好用的课程视频录课软件

在互联网技术的飞速发展下&#xff0c;在线教学已经成为一种新型的教学形式&#xff0c;与传统的教学方法相比&#xff0c;在线教学具有低成本、突破地域、时间灵活、形式多样的教学方式。那录制课程用什么软件好&#xff1f;今天小编就跟大家分享3款超好用的课程视频录课&…

认真研究MySQL的主从复制(一)

【1】主从复制概述 ① 如何提升数据库并发能力 在实际工作中&#xff0c;我们常常将Redis作为缓存与MySQL配合使用&#xff0c;当有请求的时候&#xff0c;首先会从缓存中进行查找。如果存在就直接取出&#xff0c;如果不存在再访问数据库。这样就提升了读取的效率&#xff0…

中国数据库的诸神之战

作者 | 唐小引出品 | 《新程序员》编辑部“现在的数据库产品实在是太多了&#xff01;”前几天&#xff0c;我和深耕数据库/大数据近 30 年的卢东明老师相聊时&#xff0c;他发出了这样的感慨。将包括 DB-Engines Ranking 以及国内数据库排行等在内的数据库产品列表进行汇总&am…

快速入门Freemarker模块引擎技术

1、 freemarker 介绍 ​ FreeMarker 是一款 模板引擎&#xff1a; 即一种基于模板和要改变的数据&#xff0c; 并用来生成输出文本(HTML网页&#xff0c;电子邮件&#xff0c;配置文件&#xff0c;源代码等)的通用工具。 它不是面向最终用户的&#xff0c;而是一个Java类库&am…

采场的车辆管理及卸料点计数管理有哪些难题需要解决

近期&#xff0c;安环部检查采矿区域工程车辆驾驶人员情况时&#xff0c;发现有部分驾驶员及工作人员存在违规顶替情况&#xff0c;有非注册备案人员驾驶矿用工程车辆违规作业。为了进行统一有效的人员车辆管理&#xff0c;同时能监督安全员定期对采矿作业区进行安全巡查&#…

Camtasia Studio2023喀秋莎新增功能及电脑配置要求介绍

Camtasia Studio2023具有强大的视频播放和视频编辑功能&#xff0c;录制屏幕后&#xff0c;根据时间轴对视频剪辑进行各种标记、媒体库、画中画、画中画、画外音当然&#xff0c;也可以导入现有视频并对其进行编辑操作。编辑完成后&#xff0c;可以将录制的视频输出为最终的视频…

光伏废水深度除氟装置,用于高盐废水除氟的工艺

光伏行业废水根据生产产品可细分为单品硅生产线排水、多品硅生产线排水。其生产工序中有污水排放的工段主要是&#xff1a;制绒和清洗工段。废水中的主要污染物为由异丙醇引起的高浓度COD、氟离子及酸碱污染&#xff0c;其中以含异丙醇的废水一直是水处理中的难题。如果不对废水…

【自学Python】Python input()函数

Python input()函数 Python input()函数教程 在 Python 中&#xff0c;input() 函数用于获取用于的输入&#xff0c;并给出提示。input() 函数&#xff0c;总是返回 string 类型&#xff0c;因此&#xff0c;我们可以使用 input() 函数&#xff0c;获取用户输入的任何数据类型…

【C进阶】第十五篇——内存函数

memcpy - 内存拷贝1 函数介绍 模拟实现 memmove - 内存拷贝2 函数介绍 模拟实现 memcmp - 内存比较 memset - 内存设置 memcpy - 内存拷贝1 函数介绍 void *memcpy( void *dest, const void *src, size_t count );memcpy函数是一个用于拷贝两个不相关的内存块的函数。…

4-2文件管理-文件系统实现

文章目录一.文件系统层次结构二.文件系统的全局结构三.虚拟文件系统与文件系统挂载&#xff08;安装&#xff09;&#xff08;一&#xff09;虚拟文件系统&#xff08;二&#xff09;文件系统挂载&#xff08;安装&#xff09;一.文件系统层次结构 &#xff08;1&#xff09;用…

密码学_MD5算法

MD5即Message-Digest Algorithm 5&#xff08;信息-摘要算法5&#xff09;&#xff0c;用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一&#xff08;又译摘要算法、哈希算法&#xff09;&#xff0c;主流编程语言普遍已有MD5实现。 MD5算法具有以下特点&#xff1a…

php宝塔搭建部署实战易优cms皮具皮包手袋定制网站源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的易优cms皮具皮包手袋定制网站源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&a…

Java 日常开发记录

手动分页 非mybatis 自动分页 service 层 Overridepublic PageInfo<CfLogVo> cfLogList(CfLogQuery cfLogQuery) {if (StrUtil.isNotBlank(cfLogQuery.getRequest()) && cfLogQuery.getRequest().length() >100){throw new ServiceException("请求报文…

Flutter | 使用 typedef 让回调更优雅

今天来谈谈 Flutter 的 typedef。 一. 熟悉的 VoidCallback 之前看 setState 源码的时候&#xff0c;发现它的参数是 VoidCallback&#xff1a; void setState(VoidCallback fn) {}VoidCallback 其实是一个自定义类型的无参数无返回值的匿名函数&#xff1a; /// Signature…

mybatis之配置优化and映射器

环境配置&#xff1a; mybatis可以配置成适应多种环境&#xff0c;但是要记住&#xff0c;尽管可以配置多个环境&#xff0c;但每个SqlSessionFactory实例只能选择一种环境。 虽然&#xff0c;MyBatis 中有两种类型的事务管理器:type("[JDBC|MANAGED]"&#xff09;…

Web进阶:Day3 移动端特点、百分比布局、Flex布局、实战演练

Web进阶&#xff1a;Day3 Date: January 7, 2023 Summary: 移动端特点、百分比布局、Flex布局、实战演练 移动端特点 移动端和PC端网页不同点 PC端网页和移动端网页的有什么不同&#xff1f; PC屏幕大&#xff0c;网页固定版心 手机屏幕小&#xff0c; 网页宽度多数为100%…

【Docker】(五)使用bind mounts修改Docker容器中的Nginx配置

1.前言 本系列文章记录了从0开始学习Docker的过程&#xff0c;Docker系列历史文章&#xff1a; &#xff08;一&#xff09;基本概念与安装使用 &#xff08;二&#xff09;如何使用Docker发布一个SpringBoot服务 &#xff08;三&#xff09;使用registry远程镜像仓库管理镜像…