2.上传图片到Minio服务中

上传图片

界面原型

第一步: 用户在课程信息编辑界面可以上传课程图片或者修改上传的课程图片

第二步: 请求媒资管理服务将课程图片上传至分布式文件系统同时在媒资管理数据库保存文件信息,上传成功后返回图片在MinIO中的地址

第三步: 请求内容管理服务保存课程信息含课程封面对应图片所在的地址

在这里插入图片描述

数据模型

MediaFiles表保存上传的文件信息,文件的Id和file_id都是文件的md5值,file_path(文件Minio中存储的路径)和url(文件访问地址)类似,对于视频需要转码
在这里插入图片描述

CourseBase表保存课程信息含课程图片地址

在这里插入图片描述

请求响应模型类

请求模型类上传文件一般需要文件名称、文件内容类型、文件类型(对应数据字典表中的类型)、文件大小、标签、上传人、备注

 @Data@ToString
public class UploadFileParamsDto {/*** 文件名称*/private String filename;/*** 文件content-type(扩展属性)*/private String contentType;/*** 文件类型(文档,图片,视频)*/private String fileType;/*** 文件大小*/private Long fileSize;/*** 标签*/private String tags;/*** 上传人*/private String username;/*** 备注*/private String remark;
}
# 上传文件
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary # 指定上传文件的内容类型--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="1.jpg"# filedata表示上传文件请求参数中的name,1.jpg表示原始文件的名称
Content-Type: application/octet-stream< d:/develop/upload/1.jpg # 本地文件

响应模型类: 由于PO类的属性和数据表的字段一一映射不方便修改,所以单独定义一个DTO类直接继承MediaFiles,如果后期要修改响应结果可以修改响应模型类

/*** @description 上传普通文件成功响应结果* @author Mr.M* @date 2022/9/12 18:49* @version 1.0*/
@Data
public class UploadFileResultDto extends MediaFiles {
}
{
"id": "a16da7a132559daf9e1193166b3e7f52",
"companyId": 1232141425,
"companyName": null,
"filename": "1.jpg",
"fileType": "001001",
"tags": "",
"bucket": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg",
"fileId": "a16da7a132559daf9e1193166b3e7f52",
"url": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg",
"timelength": null,
"username": null,
"createDate": "2022-09-12T21:57:18",
"changeDate": null,
"status": "1",
"remark": "",
"auditStatus": null,
"auditMind": null,
"fileSize": 248329
}

配置环境

第一步: 在minio中创建一个名为mediafiles的bucket,并将其权限设置为public,这样我们就可以在浏览器中通过URL直接访问桶内的文件

第二步: 在nacos开发环境dev中的media-service-dev.yam文件中新增配置

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/xc_media?serverTimezone=UTC&userUnicode=true&useSSL=falseusername: rootpassword: 123456cloud:config:override-none: trueminio:endpoint: http://127.0.0.1:9000accessKey: minioadminsecretKey: minioadminbucket:files: mediafilesvideofiles: video

第三步: 在本地的media-service工程中添加bootstrap.yml文件

spring:application:name: media-servicecloud:nacos:server-addr: 127.0.0.1:8848discovery:namespace: ${spring.profiles.active}group: xuecheng-plus-projectconfig:namespace: ${spring.profiles.active}group: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: trueshared-configs:- data-id: logging-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true#profiles默认为devprofiles:active: dev

第四步: 在media-service工程中编写config/MinioConfigminio配置类,根据yaml文件中的minio配置信息创建一个MinioClient对象并交给容器管理

@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() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();}
}

api工程定义接口

api接口工程中定义一个通用的上传文件的接口,可以上传图片或其他文件

/*** @description 上传文件接口* @param objectName 对象名称,如果传入objectname则需要按照其指定的目录存储,如果不传默认按年月日目录结构去存储* @return com.xuecheng.media.model.dto.UploadFileResultDto*/
@ApiOperation	("上传文件")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata,@RequestParam(value = "objectName",required=false) String objectName) throws IOException {Long companyId = 1232141425L;// 准备上传文件的信息UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();// 文件名称uploadFileParamsDto.setFilename(filedata.getOriginalFilename());// 文件大小uploadFileParamsDto.setFileSize(filedata.getSize());// 获取请求参数中上传文件的内容类型String contentType = upload.getContentType();if (contentType.contains("image")) {// 图片uploadFileParamsDto.setFileType("001001");} else {// 其他uploadFileParamsDto.setFileType("001003");}// 创建临时文件File tempFile = File.createTempFile("minio", "temp");// 将上传的文件拷贝到临时文件filedata.transferTo(tempFile);// 获取File对象在硬盘中对应临时文件的绝对路径String absolutePath = tempFile.getAbsolutePath();// 上传文件UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, absolutePath,objectName);return uploadFileResultDto;
}

service工程定义业务类

上传文件时需要指定上传的机构ID,上传的文件信息,上传源文件的磁盘路径(这里的源文件是服务器接收的临时文件)

  • getMimeType: 根据上传文件的扩展名获取文件的媒体类型MimeType
  • getDefaultFolderPath: 获取文件的默认存储目录,遵循年/月/日/文件名规范
  • addMediaFilesToMinIO: 将文件上传到MinIO
  • addMediaFilesToDb: 将上传的文件信息存储到数据库
/*** 上传文件* @param companyId 上传文件的机构ID* @param uploadFileParamsDto 上传的文件信息* @param localFilePath 上传文件对应的临时文件在服务器的绝对路径* @param objectname 对象名称,包含存储目录* @return 文件信息*/
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath,String objectname);
@Autowired
MediaFilesMapper mediaFilesMapper;
@Autowired
MinioClient minioClient;
// 存储普通文件的桶
@Value("${minio.bucket.files}")
private String bucket_files;// 这里把事务添加到uploadFile上,细粒度比较大
@Transactional
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath,String objectname) {// 获取临时文件对象的File对象File file = new File(localFilePath);if (!file.exists()) {XueChengPlusException.cast("文件不存在");}// 文件名称String filename = uploadFileParamsDto.getFilename();// 文件扩展名String extension = filename.substring(filename.lastIndexOf("."));// 根据文件扩展名获取文件对应的mimeTypeString mimeType = getMimeType(extension);// 获取文件在Minio中默认的存储目录String defaultFolderPath = getDefaultFolderPath();// 根据File对象获取文件对应的md5值String fileMd5 = getFileMd5(file);if(StringUtils.isEmpty(objectName)){// 默认存储方式,存储到minio中的完整对象名,默认目录/文件MD5值.扩展名objectName = defaultFolderPath + fileMd5 + extension;}// 将文件上传到minioboolean b = addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);if(!result){XueChengPlusException.cast("上传文件失败");}// 将文件信息存储到数据库MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);if(mediaFiles==null){XueChengPlusException.cast("文件上传后保存信息失败");}// 准备返回的UploadFileResultDto对象UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;
}

在base工程中创建一个根据上传文件的扩展名取出mimeType媒体类型的工具类,后续可以供其他微服务使用

  • 在base工程中需要添加simplemagic依赖,它提供的方法可以根据文件扩展名得到资源的媒体类型
<dependency><groupId>com.j256.simplemagic</groupId><artifactId>simplemagic</artifactId><version>1.17</version>
</dependency>
private String getMimeType(String extension){if(extension==null)extension = "";// 根据文件扩展名取出mimeType,extension不能为null,否则会报空指针异常ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);// 对于未知扩展名的文件采用通用的mimeTypeString mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;
}

获取文件默认存储目录路径,遵守年/月/日规范

private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/")+"/";return folder;
}

根据文件的字节流获取文件的MD5

private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}
}

将服务器中接收的临时文件上传到minIO,如果前端没有指定存储文件的对象名称objectName,默认由默认目录/文件MD5值.扩展名组成

/*** @param localFilePath  上传文件对应的临时文件在服务器的绝对路径* @param objectName 待存储文件的对象名称,默认目录/文件名.扩展名* @return void*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName) {try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket(bucket).object(objectName).filename(localFilePath).contentType(mimeType).build();minioClient.uploadObject(testbucket);log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);System.out.println("上传成功");return true;} catch (Exception e) {e.printStackTrace();log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket,objectName,e.getMessage(),e);XueChengPlusException.cast("上传文件到文件系统失败");}return false;
}

将上传到Minio中的文件信息添加到media_files

/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){// 从数据库查询文件MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);if (mediaFiles == null) {mediaFiles = new MediaFiles();// 拷贝请求参数中的基本信息BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);mediaFiles.setId(fileMd5);mediaFiles.setFileId(fileMd5);mediaFiles.setCompanyId(companyId);mediaFiles.setUrl("/" + bucket + "/" + objectName);mediaFiles.setBucket(bucket);mediaFiles.setFilePath(objectName);mediaFiles.setCreateDate(LocalDateTime.now());// 对于上传的图片默认审核通过mediaFiles.setAuditStatus("002003");mediaFiles.setStatus("1");// 保存文件信息到文件表int insert = mediaFilesMapper.insert(mediaFiles);if (insert < 0) {log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());XueChengPlusException.cast("保存文件信息失败");}log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());}return mediaFiles;}

事务失效优化

updateFile方法上添加@Transactional注解表示在调用updateFile方法前开启数据库事务

  • 缺点: 如果上传文件过程时间较长,数据库的事务持续时间也会变长,这样会导致数据库连接释放就慢,最终导致数据库连接不够用

addMediaFilesToDb添加事务控制表示在调用addMediaFilesToDb方法前开启数据库事务,但需要避免事务失效的问题

在一个非事务控制的方法里直接使用this调用一个被事务控制的方法,此时的事务不会生效

  • 方法可以被事务控制的条件: 在此方法上添加了@Transactional注解并且需要通过代理对象(Spring注入的对象)执行该方法事务才会生效

在这里插入图片描述

第一步: 在addMediaFilesToDb方法上加上@Transactional注解并将其提取成MediaFileService的接口方法,这样我们就可以通过代理对象调用该方法

/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称*/
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);

第二步: 在MediaFileService的实现类MediaFileServiceImpl中注入MediaFileService的代理对象

// 注入代理对象
@Autowired
MediaFileService currentProxy;@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath,String objectname) {// 使用代理对象调用方法,将文件信息存储到数据库MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);// ........
}

查看上传的媒资信息

上传图片完成后我们可以在媒资管理界面查看刚刚上传的图片信息

在这里插入图片描述

MediaFileServiceImpl中编写queryMediaFiles方法查询上传的文件信息

@Override
public PageResult<MediaFiles> queryMediaFiles(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {//构建查询条件对象LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(!StringUtils.isEmpty(queryMediaParamsDto.getFilename()), MediaFiles::getFilename, queryMediaParamsDto.getFilename());queryWrapper.eq(!StringUtils.isEmpty(queryMediaParamsDto.getFileType()), MediaFiles::getFileType, queryMediaParamsDto.getFileType());// 分页对象Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());// 查询数据内容获得结果Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);// 获取数据列表List<MediaFiles> list = pageResult.getRecords();// 获取数据总数long total = pageResult.getTotal();// 构建结果集PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());return mediaListResult;}

使用字节数组

api工程定义接口

MultipartFile是SpringMVC提供简化上传操作的工具类,为了使接口更通用可以使用字节数组代替MultpartFile类型

  • HttpServletRequest: 用来接收上传的数据,如果上传的是文件将以二进制流的形式传递到后端
@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile upload,@RequestParam(value = "folder", required = false) String folder,@RequestParam(value = "objectName", required = false) String objectName) {// 封装上传文件的信息UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();// 文件名称uploadFileParamsDto.setFilename(upload.getOriginalFilename());// 文件大小uploadFileParamsDto.setFileSize(upload.getSize());// 获取请求参数中上传文件的内容类型String contentType = upload.getContentType();if (contentType.contains("image")) {// 图片uploadFileParamsDto.setFileType("001001");} else {// 其他uploadFileParamsDto.setFileType("001003");}uploadFileParamsDto.setContentType(contentType);Long companyId = 1232141425L;try {// 获取上传文件对应的字节UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, upload.getBytes(), folder, objectName);return uploadFileResultDto;} catch (IOException e) {XueChengPlusException.cast("上传文件过程出错");}return null;
}

service工程定义业务类

/*** @description 上传文件的通用接口* @param companyId           机构id* @param uploadFileParamsDto 文件信息* @param bytes               文件字节数组* @param folder              桶下边的子目录* @param objectName          对象名称不包含存储目录* @return com.xuecheng.media.model.dto.UploadFileResultDto*/
UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName);
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {// 根据上传文件对应的字节获取对应的MD5值,md5Hex方法是根据上传文件的字节流获取对应的MD5值String fileMD5 = DigestUtils.md5DigestAsHex(bytes);// 获取存储的目录if (StringUtils.isEmpty(folder)) {// 如果桶下的子目录不存在,则按照年/月/日的规范自动生成一个目录folder = getFileFolder(true, true, true);} else if (!folder.endsWith("/")) {// 如果目录末尾没有/后缀则加一个folder = folder + "/";}// 这里接收的objectName不包含存储目录if (StringUtils.isEmpty(objectName)) {// 如果文件名为空,则设置其默认文件名为文件的md5码+文件后缀名String filename = uploadFileParamsDto.getFilename();objectName = fileMD5 + filename.substring(filename.lastIndexOf("."));}objectName = folder + objectName;try {// 将服务中接收的临时文件上传到minioaddMediaFilesToMinIO(bytes, bucket_files, objectName);// 将上传文件的信息保存到数据库MediaFiles mediaFiles = addMediaFilesToDB(companyId, uploadFileParamsDto, objectName, fileMD5, bucket_files);// 准备返回的UploadFileResultDto对象UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;} catch (Exception e) {XueChengPlusException.cast("上传过程中出错");}return null;
}

根据上传文件的对象名称中包含的扩展名获取文件对应的mimeType媒体类型

// 根
private static String getContentType(String objectName) {// 对于未知扩展名的文件默认content-type为通用的二进制流String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;if (objectName.indexOf(".") >= 0) {// 对象名称包含`.`则划分出扩展名String extension = objectName.substring(objectName.lastIndexOf("."));if(extension==null)extension = "";// 根据扩展名得到content-type,extension为null会报空指针异常,对于未知扩展名的则会返回nullContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);// 如果得到了正常的content-type则重新赋值覆盖默认类型if (extensionMatch != null) {contentType = extensionMatch.getMimeType();}}return contentType;
}

按照条件生成文件的存储目录,按照年/月/日规范

/*** 自动生成目录* @param year  是否包含年* @param month 是否包含月* @param day   是否包含日* @return*/
private String getFileFolder(boolean year, boolean month, boolean day) {StringBuffer stringBuffer = new StringBuffer();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");String dateString = dateFormat.format(new Date());String[] split = dateString.split("-");if (year) {stringBuffer.append(split[0]).append("/");}if (month) {stringBuffer.append(split[1]).append("/");}if (day) {stringBuffer.append(split[2]).append("/");}return stringBuffer.toString();
}

将服务器中接收的临时文件上传到minIO,如果前端没有指定存储文件的目录folder和对象名称objectName,默认由默认目录/文件MD5值.扩展名组成

/*** @param bytes      文件字节数组* @param bucket     桶* @param objectName 对象名称 23/02/15/porn.mp4*/
private void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName) {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);// 获取上传文件的媒体类型String contentType = getContentType(objectName);try {minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName).stream(byteArrayInputStream, byteArrayInputStream.available(), -1).contentType(contentType).build());} catch (Exception e) {log.debug("上传到文件系统出错:{}", e.getMessage());throw new XueChengPlusException("上传到文件系统出错");}
}

将上传到Minio中的文件信息添加到media_files

/*** 将文件信息添加到文件表* @param companyId             机构id* @param uploadFileParamsDto   上传文件的信息* @param objectName            对象名称* @param fileMD5               文件的md5码* @param bucket                桶* @return*/
private MediaFiles addMediaFilesToDB(Long companyId, UploadFileParamsDto uploadFileParamsDto, String objectName, String fileMD5, String bucket) {// 保存到数据库MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMD5);if (mediaFiles == null) {mediaFiles = new MediaFiles();BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);mediaFiles.setId(fileMD5);mediaFiles.setFileId(fileMD5);mediaFiles.setCompanyId(companyId);mediaFiles.setBucket(bucket);mediaFiles.setCreateDate(LocalDateTime.now());mediaFiles.setStatus("1");mediaFiles.setFilePath(objectName);mediaFiles.setUrl("/" + bucket + "/" + objectName);// 查阅数据字典,002003表示审核通过mediaFiles.setAuditStatus("002003");}int insert = mediaFilesMapper.insert(mediaFiles);if (insert <= 0) {XueChengPlusException.cast("保存文件信息失败");}return mediaFiles;

测试

前后端联调测试

第一步: 使用HTTP Client测试,然后在Minio分布式文件系统对应的bucket中查看上传的图片

// 上传文件
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="test01.jpg"
Content-Type: application/octet-stream< C:\Users\kyle\Desktop\Picture\photo\bg01.jpg// 响应结果如下
POST http://localhost:53050/media/upload/coursefileHTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 16 Feb 2023 09:57:48 GMT
Keep-Alive: timeout=60
Connection: keep-alive{"id": "632fb34166d91865da576032b9330ced","companyId": 1232141425,"companyName": null,"filename": "test01.jpg","fileType": "001003","tags": null,"bucket": "mediafiles","filePath": "2023/57/16/632fb34166d91865da576032b9330ced.jpg","fileId": "632fb34166d91865da576032b9330ced","url": "/mediafiles/2023/57/16/632fb34166d91865da576032b9330ced.jpg","username": null,"createDate": "2023-02-16 17:57:48","changeDate": null,"status": "1","remark": "","auditStatus": "002003","auditMind": null,"fileSize": 22543
}

第二步: 将前端保存图片的服务器地址改为自己的minio服务的地址,这样我们就可以在浏览器中查看上传的图片信息

## 图片服务器地址
VUE_APP_SERVER_PICSERVER_URL=http://127.0.0.1:9000

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

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

相关文章

C语言实战系列一:经典贪食蛇

C语言学习必须实战&#xff0c;并且学完语法后就必须立即用实战来巩固。一般需要10来个比较复杂的程序才能掌握C语言。今天就教大家第一个小程序&#xff0c;贪食蛇。 首先上代码 一、代码 #include <stdio.h> #include <stdlib.h> #include <curses.h> #…

代码随想录 Leetcode239. 滑动窗口最大值

题目&#xff1a; 代码&#xff08;首刷看解析 2024年1月22日&#xff09;&#xff1a; class Solution { private:class MyQueue{public:deque<int> que;void pop(int val){if (!que.empty() && que.front() val) {que.pop_front();}}void push(int val){whil…

C语言总结十三:程序环境和预处理详细总结

了解程序的运行环境可以让我们更加清楚的程序的底层运行的每一个步骤和过程&#xff0c;做到心中有数&#xff0c;预处理阶段是在预编译阶段完成&#xff0c;掌握常用的预处理命令语法&#xff0c;可以让我们正确的使用预处理命令&#xff0c;从而提高代码的开发能力和阅读别人…

亚热带常见病虫害识别系统的系统总体设计

文章目录 系统需求分析系统结构设计系统功能 系统需求分析系统时序图系统活动图 系统数据库设计数据库概念设计数据库逻辑设计数据库物理设计 小结 系统需求分析 系统结构设计 系统功能 &#xff08;1&#xff09;登录/注册 用户可以登陆系统,在已经登录过的情况下,可以输入邮…

BL121ML OPC UA网关实现Modbus、楼宇自控、电力协议转OPC UA

随着物联网技术的迅猛发展&#xff0c;人们深刻认识到在智能化生产和生活中&#xff0c;实时、可靠、安全的数据传输至关重要。在此背景下&#xff0c;高性能的物联网数据传输解决方案——协议转换网关应运而生&#xff0c;广泛应用于工业自动化和数字化工厂应用环境中。 钡铼…

linux杀毒软件clamav安装使用

1、下载 在下面地址下载&#xff1a;https://www.clamav.net/downloads 2、安装 clamav-1.2.1.linux.x86_64.rpm放在/home路径。 执行&#xff1a; chmod -R 777 /home/clamav-1.2.1.linux.x86_64.rpm rpm -ivh clamav-1.2.1.linux.x86_64.rpm3、下载病毒库 下载路径&am…

【论文解读】PV-RCNN: Point-Voxel Feature Set Abstraction for 3D Object Detection

PV-RCNN 摘要引言方法3D Voxel CNN for Efficient Feature Encoding and Proposal GenerationVoxel-to-keypoint Scene Encoding via Voxel Set AbstractionKeypoint-to-grid RoI Feature Abstraction for Proposal Refinement 实验结论 摘要 我们提出了一种新的高性能3D对象检…

100天精通Python(实用脚本篇)——第113天:基于Tesseract-OCR实现OCR图片文字识别实战

文章目录 专栏导读1. OCR技术介绍2. 模块介绍3. 模块安装4. 代码实战4.1 英文图片测试4.2 数字图片测试4.3 中文图片识别 书籍分享 专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到就业》&#xff1a;本专栏专门针对零基础和需要进阶提升的同学所准…

Axios取消请求:AbortController

AbortController AbortController() 构造函数创建了一个新的 AbortController 实例。MDN官网给出了一个利用AbortController取消下载视频的例子。 核心逻辑是&#xff1a;利用AbortController接口的只读属性signal标记fetch请求&#xff1b;然后在需要取消请求的时候&#xff0…

10.编写Shell脚本(1)

1.shell的组成 脚本声明 #!/bin/bash脚本注释 以#开头脚本命令 实现脚本的功能 2.分类 交互式(Interactive):用户每输入一条命令就立即执行。 批处理(Batch):由用户事先编写好一个完整的Shell脚本&#xff0c;Shel会一次性执行脚本中诸多的命令 shel…

HarmonyOS开源软件Notice收集策略说明

开源软件Notice是与项目开源相关的文件&#xff0c;收集这些文件的目的是为了符合开源的规范。 收集目标 只收集打包到镜像里面的模块对应的License&#xff1b;不打包的都不收集&#xff0c;比如构建过程使用的工具&#xff08;如clang、python、ninja等&#xff09;都是不收…

第91讲:MySQL主从复制集群主库与从库状态信息的含义

文章目录 1.主从复制集群正常状态信息2.从库状态信息中重要参数的含义 1.主从复制集群正常状态信息 通过以下命令查看主库的状态信息。 mysql> show processlist;在主库中查询当前数据库中的进程&#xff0c;看到Master has sent all binlog to slave; waiting for more u…