媒体文件切片上传(不同平台+Message)

1. 目标

        内网需要将文件上传到外网显示。

2. 背景

        内网(edge)将拍摄的媒体文件上传到外网(core)上,并在前端页面显示。

3. 方案

        使用消息中间件来传递消息,将文件信息放到消息中传输。由于消息有大小限制,文件可以通过分片,多次消息上报,接收方获取到消息之后,将其中的切片信息组装,还原可以得到原文件。

3.1 时序图

 3.2 示例伪代码

消息处理接口

interface MessageBroker {
    void sendMessage(String message);
    boolean hasPendingMessages();
    void waitForAllMessagesSent();
}

/**
 * Author:yang
 * Date:2024-09-11 10:02
 * 实现消息处理接口MessageBroker
 */
public class HandleMessage implements MessageBroker {

    /**
     * 消息发送处理逻辑
     */
    @Override
    public void sendMessage(String message) {
        System.out.println("消息发送逻辑");
    }

    /**
     * 检查是否有待发送的消息
     */
    @Override
    public boolean hasPendingMessages() {
        // 消息队列为空判断
        return false;
    }

    /**
     * 等待所有消息发送完成
     */
    @Override
    public void waitForAllMessagesSent() {

    }
}

边缘设备端(媒体文件产生端)


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Author:yang
 * Date:2024-09-11 9:49
 */
public class FileSliceUploader {

    private static final int CHUNK_SIZE = 1024 * 1024; // 定义每个切片的大小,例如1MB
    private static final Queue<String> messageQueue = new ConcurrentLinkedQueue<>();

    /**
     * 上传文件
     * @param filePath 文件路径
     * @param broker 消息中间件
     */
    public static void uploadFile(String filePath, MessageBroker broker) {
        File file = new File(filePath);
        FileInputStream fis = null;
        byte[] buffer = new byte[CHUNK_SIZE];
        int bytesRead;

        try {
            fis = new FileInputStream(file);
            int totalChunks = (int) Math.ceil((double) file.length() / CHUNK_SIZE);
            String md5 = calculateMD5(file); // 计算文件的MD5值

            // 发送文件信息消息
            messageQueue.offer(createFileInfoMessage(file.getName(), totalChunks, md5));
            broker.sendMessage(messageQueue.poll());

            // 读取文件并分片发送
            int chunkIndex = 0;
            while ((bytesRead = fis.read(buffer, 0, CHUNK_SIZE)) != -1) {
                byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
                String base64Data = Base64.getEncoder().encodeToString(chunkData);
                messageQueue.offer(createFileChunkMessage(file.getName(), chunkIndex, base64Data));
                broker.sendMessage(messageQueue.poll());
                chunkIndex++;
            }

            // 冗余设计:启动一个线程池,不断检查队列并发送消息
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.submit(() -> {
                while (!messageQueue.isEmpty() || broker.hasPendingMessages()) {
                    String message = messageQueue.poll();
                    if (message != null) {
                        broker.sendMessage(message);
                    }
                }
                broker.waitForAllMessagesSent();
            });

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static String calculateMD5(File file) {
        // 实现MD5计算逻辑
        return "md5value"; // 假设的MD5值
    }

    private static String createFileInfoMessage(String fileName, int totalChunks, String md5) {
        // 创建包含文件信息的消息
        return "File: " + fileName + ", TotalChunks: " + totalChunks + ", MD5: " + md5;
    }

    private static String createFileChunkMessage(String fileName, int chunkIndex, String base64Data) {
        // 创建包含文件切片的消息
        return "File: " + fileName + ", ChunkIndex: " + chunkIndex + ", Data: " + base64Data;
    }

    public static void main(String[] args) {
        MessageBroker broker = new HandleMessage(); // 假设的消息中间件
        // 在实际项目中可以使用存在媒体文件是触发回调
        uploadFile("path/to/your/file.jpg", broker);
    }

}

核心业务端

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;

/**
 * Author:yang
 * Date:2024-09-11 9:52
 */


public class CoreReceiver{

    public void receiveMessage(String message,MessageBroker broker) {
        // 处理接收到的消息
        if (message.startsWith("File:")) {
            // 解析文件信息
            String[] parts = message.split(", ");
            String fileName = parts[1].split(": ")[1];
            int totalChunks = Integer.parseInt(parts[2].split(": ")[1]);
            String md5 = parts[3].split(": ")[1];

            // 初始化文件组装
            File file = new File("output/" + fileName);
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                for (int i = 0; i < totalChunks; i++) {
                    String chunkMessage = receiveNextChunkMessage();
                    if (chunkMessage != null) {
                        String base64Data = chunkMessage.split(", ")[2].split(": ")[1];
                        byte[] chunkData = Base64.getDecoder().decode(base64Data);
                        fos.write(chunkData);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 校验MD5
            if (calculateMD5(file).equals(md5)) {
                // 发送确认消息给edge
                broker.sendMessage("File " + fileName + " received and verified.");
            } else {
                // 发送错误消息
                broker.sendMessage("File " + fileName + " verification failed.");
            }

            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String receiveNextChunkMessage() {
        // 从消息队列中接收下一个切片消息
        return null; // 实现具体逻辑
    }

    private String calculateMD5(File file) {
        // 实现MD5计算逻辑
        return "md5value"; // 假设的MD5值
    }
}

4. 示例代码优化

4.1 消息中间件

        消息中间件可以是EMQX,RocketMQ以及其他的一些消息中间件。代码中使用对应的客户端去处理消息发送、消息接收以及其他的一些消息逻辑。通过publish和subscribe。

4.2 文件属性处理

        可以定义一个文件属性类,其中包含切片数量、索引、大小以及其他的一些相关信息。

4.3 消息处理

        设备端发送的切片数据消息可以通过列表存储或者其他什么方式,使用定时任务不断的轮训重发消息(防止部分消息丢失导致数据不完整)。当核心端收到全部的切片数据组装好之后,给设备端发送对应的文件已经收到,设备端将消息从列表中删除,相当于取消切片数据重发

4.4 切片数据处理

        文件的切片数据可以将其通过Base64.getEncoder().encodeToString(chunkData)转换为String类型,有特殊要求,如JSON,可以再转换一次。核心端收到消息后,采用逆过程去解析数据,可以得到Base64的String字符串,通过Base64.getDecoder().decode(base64Data)得到对应的字节数组,将切片数据按照顺序写入文件,即为原媒体文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值