第 4 篇 : Netty客户端互发图片和音/视频

2024-04-29 07:04

本文主要是介绍第 4 篇 : Netty客户端互发图片和音/视频,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说明

因为图片和音/视频不能确定其具体大小, 故引入MinIO。客户端之间只发送消息, 通过上传/下载来获取额外信息

1. MinIO搭建(参考前面文章), 并启动

2. 登录MinIO创建3个Bucket: image、voice、video

创建桶
3个桶

3. 客户端改造

3.1 修改 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.hahashou.netty</groupId><artifactId>client</artifactId><version>1.0-SNAPSHOT</version><name>client</name><description>Netty Client Project For Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.100.Final</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency><dependency><groupId>com.hahashou</groupId><artifactId>minio</artifactId><version>2023.11.16</version></dependency><!-- 此包在引入minio时必须添加--><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

3.2 修改 application.yml, 限制单文件大小为128M以内, 总大小为256M以内

server:port: 32001logging:level:com.hahashou.netty: infospring:servlet:multipart:max-file-size: 128MBmax-request-size: 256MBuserCode: Aaminio:endpoint: http://127.0.0.1:9000accessKey: rootsecretKey: root123456

3.3 config包下新增 MinIOConfig类

package com.hahashou.netty.client.config;import io.minio.MinioClient;
import io.minio.http.HttpUtils;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @description: MinIO配置* @author: 哼唧兽* @date: 9999/9/21**/
@Configuration
public class MinIOConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinioClient minioClient() {OkHttpClient okHttpClient = HttpUtils.newDefaultHttpClient(60 * 1000,50 * 1000, 30 * 1000);MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).httpClient(okHttpClient).build();return minioClient;}
}

3.4 新增utils包, 放入 MinioUtils类

package com.hahashou.netty.client.utils;import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Component
@Slf4j
public class MinioUtils {@Resourceprivate MinioClient minioClient;/*** 文件上传* @param bucketName* @param fileName* @param multipartFile* @return*/public void upload(String bucketName, String multiLevelFolders, String fileName, MultipartFile multipartFile) {try {boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!found) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}String suffix = fileName.substring(fileName.lastIndexOf("."));File file = File.createTempFile(fileName, suffix);multipartFile.transferTo(file);UploadObjectArgs objectArgs = UploadObjectArgs.builder().bucket(bucketName).object(StringUtils.hasText(multiLevelFolders) ? multiLevelFolders + fileName : fileName).filename(file.getAbsolutePath()).contentType(multipartFile.getContentType()).build();minioClient.uploadObject(objectArgs);} catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException exception) {log.error("MinIO文件上传异常 : {}", exception.getMessage());}}/*** 预览地址* @param fileName* @param bucketName* @return*/public String preview(String fileName, String bucketName){// 查看文件地址GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder().bucket(bucketName).expiry(30, TimeUnit.MINUTES).object(fileName).method(Method.GET).build();try {return minioClient.getPresignedObjectUrl(build);} catch (Exception exception) {log.error("获取MinIO预览地址异常 : {}", exception.getMessage());}return null;}
}

3.5 修改 Message类

package com.hahashou.netty.client.config;import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import lombok.Data;
import lombok.Getter;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Data
public class Message {/** 发送者用户code */private String userCode;/** 接收者用户code */private String friendUserCode;/** 连接时专用 */private String channelId;/** 消息类型 */private Integer type;public enum TypeEnum {TEXT(0, "文字", "", new ArrayList<>()),IMAGE(1, "图片", "image", Arrays.asList("bmp", "gif", "jpeg", "jpg", "png")),VOICE(2, "语音", "voice", Arrays.asList("mp3", "amr", "flac", "wma", "aac")),VIDEO(3, "视频", "video", Arrays.asList("mp4", "avi", "rmvb", "flv", "3gp", "ts", "mkv")),;@Getterprivate Integer key;@Getterprivate String describe;@Getterprivate String bucketName;@Getterprivate List<String> formatList;TypeEnum(int key, String describe, String bucketName, List<String> formatList) {this.key = key;this.describe = describe;this.bucketName = bucketName;this.formatList = formatList;}public static TypeEnum select(String format) {TypeEnum result = null;for (TypeEnum typeEnum : TypeEnum.values()) {if (typeEnum.getFormatList().contains(format)) {result = typeEnum;break;}}return result;}}/** 文字或文件的全路径名称 */private String text;public static ByteBuf transfer(Message message) {return Unpooled.copiedBuffer(JSON.toJSONString(message), CharsetUtil.UTF_8);}/*** 生成指定长度的随机字符串* @param length* @return*/public static String randomString (int length) {if (length > 64) {length = 64;}List<String> list = new ArrayList<>();for (int i = 0; i < 10; i++) {list.add(i + "");}for (char i = 'A'; i <= 'Z'; i++) {list.add(String.valueOf(i));}for (char i = 'a'; i <= 'z'; i++) {list.add(String.valueOf(i));}list.add("α");list.add("ω");Collections.shuffle(list);String string = list.toString();return string.replace("[", "").replace("]", "").replace(", ", "").substring(0, length);}
}

3.6 新增service包, 放入 ClientService接口

package com.hahashou.netty.client.service;import com.hahashou.netty.client.config.Message;import javax.servlet.http.HttpServletRequest;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
public interface ClientService {/*** 上传* @param userCode* @param httpServletRequest* @return*/Message upload(String userCode, HttpServletRequest httpServletRequest);/*** 下载链接* @param fileName* @return*/String link(String fileName);
}

3.7 service包下新建impl包, 放入 ClientServiceImpl类

package com.hahashou.netty.client.service.imol;import com.hahashou.netty.client.config.Message;
import com.hahashou.netty.client.service.ClientService;
import com.hahashou.netty.client.utils.MinioUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Service
@Slf4j
public class ClientServiceImpl implements ClientService {@Resourceprivate MinioUtils minioUtils;@Overridepublic Message upload(String userCode, HttpServletRequest httpServletRequest) {Message result = new Message();MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) httpServletRequest;List<MultipartFile> multipartFileList = multipartHttpServletRequest.getFiles("file");if (!CollectionUtils.isEmpty(multipartFileList)) {MultipartFile multipartFile = multipartFileList.get(0);String originalFilename = multipartFile.getOriginalFilename();String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);Message.TypeEnum typeEnum = Message.TypeEnum.select(suffix);if (typeEnum != null) {String fileName = generateFileName(suffix);String multiLevelFolders = userCode + "/"+ LocalDate.now().toString().replace("-", "") + "/";minioUtils.upload(typeEnum.getBucketName(), multiLevelFolders, fileName, multipartFile);result.setType(typeEnum.getKey());result.setText(multiLevelFolders + fileName);}}return result;}public static String generateFileName(String suffix) {LocalTime now = LocalTime.now();return now.toString().replace(":", "").replace(".", "-") + Message.randomString(4) + "." + suffix;}@Overridepublic String link(String fileName) {String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);Message.TypeEnum typeEnum = Message.TypeEnum.select(suffix);if (typeEnum != null) {return minioUtils.preview(fileName, typeEnum.getBucketName());}return null;}
}

3.8 修改 ClientController类

package com.hahashou.netty.client.controller;import com.alibaba.fastjson.JSON;
import com.hahashou.netty.client.config.Message;
import com.hahashou.netty.client.config.NettyClientHandler;
import com.hahashou.netty.client.service.ClientService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@RestController
@RequestMapping("/client")
@Slf4j
public class ClientController {@Resourceprivate ClientService clientService;@PostMapping("/send")public String send(@RequestBody Message dto) {Channel channel = NettyClientHandler.CHANNEL;if (channel == null) {return "服务端已下线";}channel.writeAndFlush(Message.transfer(dto));return "success";}@GetMapping("/upload/{userCode}")public String upload(@PathVariable String userCode, final HttpServletRequest httpServletRequest) {if (StringUtils.isEmpty(userCode)) {return "userCode is null";}Message upload = clientService.upload(userCode, httpServletRequest);return JSON.toJSONString(upload);}@GetMapping("/link")public String link(@RequestParam String fileName) {// 如果Bucket包含多级目录, fileName为Bucket下文件的全路径名if (StringUtils.isEmpty(fileName)) {return "fileName is null";}return clientService.link(fileName);}
}

4. 服务端改造

4.1 复制客户端的 Message类

4.2 修改 NettyServerHandler类

package com.hahashou.netty.server.config;import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {/** key: 用户code; value: channelId */public static Map<String, String> USER_CHANNEL = new ConcurrentHashMap<>(32);/** key: channelId; value: Channel */public static Map<String, Channel> CHANNEL = new ConcurrentHashMap<>(32);/** 用户离线消息 */public static Map<String, List<Message>> USER_MESSAGE = new ConcurrentHashMap<>(32);@Overridepublic void channelActive(ChannelHandlerContext ctx) {Channel channel = ctx.channel();String channelId = channel.id().asLongText();log.info("有客户端连接, channelId : {}", channelId);CHANNEL.put(channelId, channel);Message message = new Message();message.setChannelId(channelId);channel.writeAndFlush(Message.transfer(message));}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {log.info("有客户端断开连接, channelId : {}", ctx.channel().id().asLongText());CHANNEL.remove(ctx.channel().id().asLongText());}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {if (msg != null) {Message message = JSON.parseObject(msg.toString(), Message.class);String userCode = message.getUserCode(),channelId = message.getChannelId();if (StringUtils.hasText(userCode) && StringUtils.hasText(channelId)) {connect(userCode, channelId);} else if (StringUtils.hasText(message.getText())) {if (StringUtils.hasText(message.getFriendUserCode())) {sendOtherClient(message);} else {sendAdmin(ctx.channel(), message);}}}}/*** 建立连接* @param userCode* @param channelId*/private void connect(String userCode, String channelId) {log.info("客户端 {} 连接", userCode);USER_CHANNEL.put(userCode, channelId);}/*** 发送给其他客户端* @param message*/private void sendOtherClient(Message message) {String friendUserCode = message.getFriendUserCode();String queryChannelId = USER_CHANNEL.get(friendUserCode);if (StringUtils.hasText(queryChannelId)) {Channel channel = CHANNEL.get(queryChannelId);if (channel == null) {offlineMessage(friendUserCode, message);return;}channel.writeAndFlush(Message.transfer(message));} else {offlineMessage(friendUserCode, message);}}/*** 离线消息存储* @param friendUserCode* @param message*/public void offlineMessage(String friendUserCode, Message message) {List<Message> messageList = USER_MESSAGE.get(friendUserCode);if (CollectionUtils.isEmpty(messageList)) {messageList = new ArrayList<>();}messageList.add(message);USER_MESSAGE.put(friendUserCode, messageList);}/*** 发送给服务端* @param channel* @param message*/private void sendAdmin(Channel channel, Message message) {message.setUserCode("ADMIN");message.setText(LocalDateTime.now().toString());channel.writeAndFlush(Message.transfer(message));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {log.info("有客户端发生异常, channelId : {}", ctx.channel().id().asLongText());}
}

5. 测试

5.1 上传

上传图片
minio文件界面

5.2 获取下载链接, 之后直接在浏览器中打开链接

获取下载链接

5.3 Aa向Bb发送消息, Bb收到后可以通过下载链接获取

Aa向Bb发送图片消息
Bb收到图片消息

5.4 测试音频

音频上传
音频位置

5.5 测试视频

视频上传
视频位置

5.6 MioIO界面Buckets

minio界面buckets

这篇关于第 4 篇 : Netty客户端互发图片和音/视频的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/945402

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

详解C#如何提取PDF文档中的图片

《详解C#如何提取PDF文档中的图片》提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使用,下面我们就来看看如何使用C#通过代码从PDF文档中提取图片吧... 当 PDF 文件中包含有价值的图片,如艺术画作、设计素材、报告图表等,提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

用js控制视频播放进度基本示例代码

《用js控制视频播放进度基本示例代码》写前端的时候,很多的时候是需要支持要网页视频播放的功能,下面这篇文章主要给大家介绍了关于用js控制视频播放进度的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言html部分:JavaScript部分:注意:总结前言在javascript中控制视频播放

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

基于Python开发批量提取Excel图片的小工具

《基于Python开发批量提取Excel图片的小工具》这篇文章主要为大家详细介绍了如何使用Python中的openpyxl库开发一个小工具,可以实现批量提取Excel图片,有需要的小伙伴可以参考一下... 目前有一个需求,就是批量读取当前目录下所有文件夹里的Excel文件,去获取出Excel文件中的图片,并

Java实现数据库图片上传与存储功能

《Java实现数据库图片上传与存储功能》在现代的Web开发中,上传图片并将其存储在数据库中是常见的需求之一,本文将介绍如何通过Java实现图片上传,存储到数据库的完整过程,希望对大家有所帮助... 目录1. 项目结构2. 数据库表设计3. 实现图片上传功能3.1 文件上传控制器3.2 图片上传服务4. 实现

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到