第 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

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

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

相关文章

苍穹外卖绕过微信支付

经过以下改动可实现&#xff1a; 1、不用微信支付端口 2、弹出支付成功的界面 3、数据库修改支付成功后的数据 #在OrderServiceImpl.java里加入Autowiredprivate OrderService orderService; #在OrderServiceImpl.java里的payment函数做以下改动 #图片里有&#xff0c;红色为原…

2024李卜常识王小晨申论类比刷题课

2024年&#xff0c;李卜常识与王小晨申论类比刷题课成为备考公务员考试的热门选择。李卜老师以其深厚的学识&#xff0c;为学员们剖析常识的精髓&#xff1b;而王小晨老师则通过类比刷题的方式&#xff0c;帮助学员们掌握申论的技巧。这两门课程相互补充&#xff0c;让学员们在…

03-JAVA设计模式-观察者模式

观察者模式 什么是观察者模式 Java中的观察者模式是一种常见的设计模式&#xff0c;它允许对象&#xff08;观察者&#xff09;订阅另一个对象&#xff08;被观察者&#xff09;的状态变化&#xff0c;并在状态变化时自动得到通知。 核心&#xff1a; 观察者模式主要用于1&a…

手搓带头双向循环链表(C语言)

目录 List.h List.c ListTest.c 测试示例 带头双向循环链表优劣分析 List.h #pragma once#include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int LTDataType;typedef struct ListNode {struct ListNode* prev;struct ListNode* next…

如何提升WordPress网站安全

上周遇到Hostease的客户反馈他想要提升wordpress网站的安全性。提升WordPress网站安全是网站所有者必须重视的事项。以下是一些有效的安全措施&#xff0c;可帮助您保护WordPress网站免受潜在威胁&#xff1a; 1.选择可靠的WordPress主机 选择一个可靠的WordPress主机提供商至…

关于文档中心的英文快捷替换方案

背景&#xff1a;文档中心需要接入国际化&#xff0c;想节省时间做统一英文方案处理&#xff1b; 文档中心是基于vuepress框架编写的&#xff1b; 1、利用百度翻译 API 的接口去做底层翻译处理&#xff0c;https://api.fanyi.baidu.com/需要在该平台上注册账号&#xff0c;个人…

unittest自动化测试框架详解

一、单元测试的定义 1. 什么是单元测试&#xff1f; ​ 单元测试是指&#xff0c;对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作&#xff0c;这里的最小可测试单元通常是指函数或者类&#xff0c;一般是开发来做的&#xff0c;按照测试阶段来…

day5 c++

#include <iostream> using namespace std; class Person { public:string name;int *age;//Person():name(name),age(new int(100)){cout<<"无参构造"<<endl;}Person(string name,int age):name(name),age(new int(100)){cout <<"P的有…

创新科技赋能旅游服务:智慧文旅引领旅游发展新篇章,智能体验助力产业转型升级

随着科技的飞速发展和人们生活水平的提高&#xff0c;旅游业正迎来前所未有的发展机遇。创新科技在旅游服务领域的广泛应用&#xff0c;不仅提升了旅游体验的品质&#xff0c;也为旅游产业的转型升级注入了新的动力。智慧文旅作为旅游业与信息技术深度融合的产物&#xff0c;正…

Linux内核广泛采用的侵入式数据结构设计

Linux内核广泛采用的侵入式数据结构设计恐怕很难应用到一般程序开发中。基本上是个高维十字链表&#xff0c;一个节点(struct)可以同时位于多个hash/list/tree中。我分享下我的经历&#xff0c;我刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;一年时间从3k薪…

如何安全高效地进行网点文件下发?

随着IT技术的飞速发展&#xff0c;以银行为代表的企业数字化技术转型带来了大量的电子化文档传输需求。文件传输数量呈几何级数增长&#xff0c;传统集中式文件传输模式在爆炸式的增长需求下&#xff0c;银行网点文件下发的效率、可靠性、安全性等方面&#xff0c;都需要重点关…

工具:如何在国内高速下载SRA

如何在国内高速下载SRA 下载公共测序数据&#xff0c;一般是通过在NCBI上搜索到study的Run SRA号&#xff0c;然后使用NCBI提供的prefetch下载数据&#xff0c;但因NCBI国内访问较为缓慢导致下载速度巨慢&#xff0c;因此推荐使用EBI提供的enaBrowserToolsAspera的方式从ENA下…

艾体宝案例 | 使用Redis和Spring Ai构建rag应用程序

随着AI技术的不断进步&#xff0c;开发者面临着如何有效利用现有工具和技术来加速开发过程的挑战。Redis与Spring AI的结合为Java开发者提供了一个强大的平台&#xff0c;以便快速构建并部署响应式AI应用。探索这一整合如何通过简化的开发流程&#xff0c;让开发者能够更专注于…

大核注意力 LKA | Visual Attention Network

论文名称&#xff1a;《Visual Attention Network》 论文地址&#xff1a;2202.09741 (arxiv.org) 尽管最初是为自然语言处理任务而设计的&#xff0c;但自注意力机制最近在各个计算机视觉领域迅速崭露头角。然而&#xff0c;图像的二维特性给计算机视觉中的自注意力应用带来了…

【Vue】组件化编程

定义 实现应用中局部功能代码和资源的集合 为什么要用组件化编程? 传统方式编写:依赖关系混乱,不好维护,且代码复用率不高 模块化编写:只关注解决js,复用js,简化js的编写与效率 组件方式编写:好维护、复用率更高、提高运行效率 在组件出现之前,我们开发基本都是用htm…

GD32E103C8T6 封装LQFP-48 GigaDevice(兆易创新) 单片机

GD32E103C8T6 是由GigaDevice&#xff08;兆易创新&#xff09;公司生产的一款基于ARM Cortex-M4内核的32位MCU&#xff08;微控制器&#xff09;。以下是GD32E103C8T6的一些主要功能和参数介绍&#xff1a; 主要功能&#xff1a; 高性能ARM Cortex-M4内核: 采用120MHz的ARM …

修复所有 bug 并不能解决所有问题

原文&#xff1a;jeffpsherman - 2024.04.08 在软件领域&#xff0c;如同在制造业&#xff0c;有些问题是由于 bug 或“特殊原因”引发的&#xff0c;而有些则是“常见原因”&#xff0c;这是由于系统设计和实现的性质所导致的。修复 bug 就是移除特殊原因&#xff0c;消除 bu…

基于SpringBoot的合家云社区物业管理平台 - 项目介绍

合家云社区物业管理平台 2.合家云需求&设计 2.1 项目概述 2.1.1 项目介绍 合家云社区物业管理平台是一个全新的 ”智慧物业解决方案“&#xff0c;是一款互联网的专业社区物业管理系统。平台通过社区资产管理、小区管理、访客管理、在线报修、意见投诉等多种功能模块&a…

指针笔试题模拟

题目一 int main() {int a[5] { 1, 2, 3, 4, 5 };int *ptr (int *)(&a 1);printf( "%d,%d", *(a 1), *(ptr - 1));return 0; } 1*&#xff08;a1&#xff09;可以等价于a[1],即第一个打印的是数组第二个下标的元素&#xff1a;2 2 而ptr拿到的是整个数组的地…

C语言之详细讲解文件操作

什么是文件 与普通文件载体不同&#xff0c;文件是以硬盘为载体存储在计算机上的信息集合&#xff0c;文件可以是文本文档、图片、程序等等。文件通常具有点三个字母的文件扩展名&#xff0c;用于指示文件类型&#xff08;例如&#xff0c;图片文件常常以KPEG格式保存并且文件…