当前位置: 首页 > news >正文

day4--上传图片、视频

1. 分布式文件系统

1.1 什么是分布式文件系统

        文件系统是负责管理和存储文件的系统软件,操作系统通过文件系统提供的接口去存取文件,用户通过操作系统访问磁盘上的文件。

        下图指示了文件系统所处的位置:

常见的文件系统:FAT16/FAT32、NTFS、HFS、UFS、APFS、XFS、Ext4等 。

        现在有个问题,一此短视频平台拥有大量的视频、图片,这些视频文件、图片文件该如何存储呢?如何存储可以满足互联网上海量用户的浏览。

        通过分布式文件系统实现海量用户查阅海量文件

好处

1、一台计算机的文件系统处理能力扩充到多台计算机同时处理。

2、一台计算机挂了还有另外副本计算机提供数据。

3、每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。

1.2 MinIo

1.2.1 介绍

        本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

        它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。

官网:https://min.io

中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/

        在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。

        它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。如下图:

1.2.2 测试Docker环境

开发阶段和生产阶段统一使用Docker下的MINIO。

        虚拟机中已安装了MinIO的镜像和容器,执行sh /data/soft /restart.sh启动Docker下的MinIO

启动完成登录MinIO查看是否正常。访问 http://192.168.101.65:9000,账号和密码为:minioadmin / minioadmin

本项目创建两个buckets:

mediafiles: 普通文件

video:视频文件

1.2.4 SDK

1)上传文件

MinIO提供多个语言版本SDK的支持,下边找到java版本的文档:

地址:https://docs.min.io/docs/java-client-quickstart-guide.html

  • media-service工程中导入依赖

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.3</version>
</dependency>
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.8.1</version>
</dependency>

参数说明:

需要三个参数才能连接到minio服务。

参数说明
Endpoint对象存储服务的URL
Access KeyAccess key就像用户ID,可以唯一标识你的账户。
Secret KeySecret key是你账户的密码。
  • 创建一个桶进行测试

  • 在xuecheng-plus-media-service工程 的test下编写测试代码如下

package com.xuecheng.media;import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;/*** 测试 minio*/
public class MinioTest {MinioClient minioClient =MinioClient.builder().endpoint("http://192.168.101.65:9000").credentials("minioadmin", "minioadmin").build();@Testpublic void test_upload() throws Exception {//通过扩展名得到媒体资源类型 mimeType//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".jpg");String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}//上传文件的参数信息UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder().bucket("testbucket")//桶.filename("D:\\学习\\images\\3.jpg") //指定本地文件路径
//                .object("3.jpg")//对象名 在桶下存储该文件.object("test/01/3.jpg")//对象名 放在子目录下.contentType(mimeType)//设置媒体文件类型.build();//上传文件minioClient.uploadObject(uploadObjectArgs);}//删除文件@Testpublic void test_delete() throws Exception {//RemoveObjectArgsRemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("testbucket").object("3.jpg").build();//删除文件minioClient.removeObject(removeObjectArgs);}//查询文件 从minio中下载@Testpublic void test_getFile() throws Exception {GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("test/01/1.mp4").build();//查询远程服务获取到一个流对象FilterInputStream inputStream = minioClient.getObject(getObjectArgs);//指定输出流FileOutputStream outputStream = new FileOutputStream(new File("D:\\develop\\upload\\1a.mp4"));IOUtils.copy(inputStream,outputStream);//校验文件的完整性对文件的内容进行md5FileInputStream fileInputStream1 = new FileInputStream(new File("D:\\develop\\upload\\1.mp4"));String source_md5 = DigestUtils.md5Hex(fileInputStream1);FileInputStream fileInputStream = new FileInputStream(new File("D:\\develop\\upload\\1a.mp4"));String local_md5 = DigestUtils.md5Hex(fileInputStream);if(source_md5.equals(local_md5)){System.out.println("下载成功");}}}

2. 上传图片

2.1 需求分析

2.1.1 业务流程

        课程图片是宣传课程非常重要的信息,在新增课程界面上传课程图片,也可以修改课程图片。

上传课程图片总体上包括两部分:

  • 上传课程图片前端请求媒资管理服务将文件上传至分布式文件系统,并且在媒资管理数据库保存文件信息。
  • 上传图片成功保存图片地址到课程基本信息表中。

详细流程如下:

  • 前端进入上传图片界面
  • 上传图片,请求媒资管理服务。
  • 媒资管理服务将图片文件存储在MinIO。
  • 媒资管理记录文件信息到数据库。
  • 前端请求内容管理服务保存课程信息,在内容管理数据库保存图片地址。

2.1.2 数据模型

        涉及到的数据表有:课程信息表中的图片字段、媒资数据库的文件表,下边主要看媒资数据库的文件表。

2.2 准备

        首先在minio配置bucket,bucket名称为:mediafiles,并设置bucket的权限为公开

        在nacos配置中minio的相关信息,进入media-service-dev.yaml:

minio:endpoint: http://192.168.101.65:9000accessKey: minioadminsecretKey: minioadminbucket:files: mediafilesvideofiles: video

        在media-service工程编写minio的配置类

package com.xuecheng.media.config;import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** minio配置类*/
@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() {// 创建Minio客户端实例,使用endpoint、accessKey和secretKey进行配置MinioClient minioClient =MinioClient.builder().endpoint(endpoint) // 设置Minio服务地址.credentials(accessKey, secretKey) // 设置访问凭证.build(); // 构建客户端实例return minioClient; // 返回构建好的Minio客户端}}

2.3 接口定义

        根据需求分析,下边进行接口定义,此接口定义为一个通用的上传文件接口,可以上传图片或其它文件。

请求地址:/media/upload/coursefile

请求内容Content-Type: multipart/form-data;

form-data; name="filedata"; filename="具体的文件名称"

响应参数:文件信息,如下

{"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
}
  • 定义上传响应模型类

package com.xuecheng.media.model.dto;import com.xuecheng.media.model.po.MediaFiles;
import lombok.Data;
import lombok.ToString;@Data
@ToString
public class UploadFileResultDto extends MediaFiles {}
  • controller层接口

        前端传过来的文件名称是filedata

package com.xuecheng.media.api;import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;/*** 媒资文件管理接口*/@Api(value = "媒资文件管理接口",tags = "媒资文件管理接口")@RestController
public class MediaFilesController {@AutowiredMediaFileService mediaFileService;@ApiOperation("媒资列表查询接口")@PostMapping("/files")public PageResult<MediaFiles> list(PageParams pageParams, @RequestBody QueryMediaParamsDto queryMediaParamsDto){Long companyId = 1232141425L;return mediaFileService.queryMediaFiels(companyId,pageParams,queryMediaParamsDto);}@ApiOperation("上传图片")@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata")MultipartFile filedata) throws IOException {//准备上传文件的信息UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();//原始文件名称uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件大小uploadFileParamsDto.setFileSize(filedata.getSize());//文件类型uploadFileParamsDto.setFileType("001001");//创建一个临时文件File tempFile = File.createTempFile("minio", ".temp");filedata.transferTo(tempFile);Long companyId = 1232141425L;//文件路径String localFilePath = tempFile.getAbsolutePath();//调用service上传图片UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, localFilePath);return uploadFileResultDto;}}

2.4 接口开发

2.4.1 Dao层

  • mapper层
@Mapper
public interface MediaFilesMapper extends BaseMapper<MediaFiles> {}
  • sql映射
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuecheng.media.mapper.MediaFilesMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.xuecheng.media.model.po.MediaFiles"><id column="id" property="id" /><result column="company_id" property="companyId" /><result column="company_name" property="companyName" /><result column="filename" property="filename" /><result column="file_type" property="fileType" /><result column="tags" property="tags" /><result column="bucket" property="bucket" /><result column="file_id" property="fileId" /><result column="url" property="url" /><result column="timelength" property="timelength" /><result column="username" property="username" /><result column="create_date" property="createDate" /><result column="change_date" property="changeDate" /><result column="status" property="status" /><result column="remark" property="remark" /><result column="audit_status" property="auditStatus" /><result column="audit_mind" property="auditMind" /></resultMap><!-- 通用查询结果列 --><sql id="Base_Column_List">id, company_id, company_name, filename, file_type, tags, bucket, file_id, url, timelength, username, create_date, change_date, status, remark, audit_status, audit_mind</sql></mapper>

2.4.2 Service层

1)定义请求参数类

@Data
@ToString
public class UploadFileParamsDto {/*** 文件名称*/private String filename;/*** 文件类型(文档,音频,视频)*/private String fileType;/*** 文件大小*/private Long fileSize;/*** 标签*/private String tags;/*** 上传人*/private String username;/*** 备注*/private String remark;}
2)定义service方法
 /*** 上传文件* @param companyId 机构id* @param uploadFileParamsDto 文件信息* @param localFilePath 文件本地路径* @return UploadFileResultDto*/public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath);
3)impl实现类实现service接口,重写方法

 


@Autowired
MinioClient minioClient;@Autowired
MediaFilesMapper mediaFilesMapper;//普通文件桶
@Value("${minio.bucket.files}")
private String bucket_Files;//获取文件默认存储目录路径 年/月/日
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;}
}private String getMimeType(String extension){if(extension==null)extension = "";//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);//通用mimeType,字节流String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;
}
/*** @description 将文件写入minIO* @param localFilePath  文件地址* @param bucket  桶* @param objectName 对象名称*/
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;
}/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称* @return com.xuecheng.media.model.po.MediaFiles*/
@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;}
@Transactional
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {File file = new File(localFilePath);if (!file.exists()) {XueChengPlusException.cast("文件不存在");}//文件名称String filename = uploadFileParamsDto.getFilename();//文件扩展名String extension = filename.substring(filename.lastIndexOf("."));//文件mimeTypeString mimeType = getMimeType(extension);//文件的md5值String fileMd5 = getFileMd5(file);//文件的默认目录String defaultFolderPath = getDefaultFolderPath();//存储到minio中的对象名(带目录)String  objectName = defaultFolderPath + fileMd5 + exension;//将文件上传到minioboolean b = addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);//文件大小uploadFileParamsDto.setFileSize(file.length());//将文件信息存储到数据库MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);//准备返回数据UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;}

2.5 前后端联调测试

 

2.6 代码优化

        目前是在uploadFile方法上添加@Transactional,当调用uploadFile方法前会开启数据库事务,如果上传文件过程时间较长那么数据库的事务持续时间就会变长,这样数据库链接释放就慢,最终导致数据库链接不够用。

        我们只将addMediaFilesToDb方法添加事务控制即可,uploadFile方法上的@Transactional注解去掉。

方法上已经添加了@Transactional注解为什么该方法不能被事务控制呢?

如果是在uploadFile方法上添加@Transactional注解就可以控制事务,去掉则不行。

        现在的问题其实是一个非事务方法调同类一个事务方法,事务无法控制,这是为什么?

原因分析如下:

        如果在uploadFile方法上添加@Transactional注解,代理对象执行此方法前会开启事务,如下图

         如果在uploadFile方法上没有@Transactional注解,代理对象执行此方法前不进行事务控制,如下图:

        所以判断该方法是否可以事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解 。现在在addMediaFilesToDb方法上添加@Transactional注解,也不会进行事务控制是因为并不是通过代理对象执行的addMediaFilesToDb方法。为了判断在uploadFile方法中去调用addMediaFilesToDb方法是否是通过代理对象去调用,我们可以打断点跟踪

        我们发现在uploadFile方法中去调用addMediaFilesToDb方法不是通过代理对象去调用 

解决方法是在MediaFileService的实现类中注入MediaFileService的代理对象,将addMediaFilesToDb方法提成接口

完整代码如下:

@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {@Autowiredprivate MediaFilesMapper mediaFilesMapper;@AutowiredMinioClient minioClient;@AutowiredMediaFileService currentProxy;//存储普通文件@Value("${minio.bucket.files}")private String bucket_mediafiles;//存储视频@Value("${minio.bucket.videofiles}")private String bucket_video;@Overridepublic PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {//构建查询条件对象LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();//分页对象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;}//根据扩展名获取mimeTypeprivate String getMimeType(String extension){if(extension == null){extension = "";}//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;}/*** 将文件上传到minio* @param localFilePath 文件本地路径* @param mimeType 媒体类型* @param bucket 桶* @param objectName 对象名* @return*/public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName){try {UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder().bucket(bucket)//桶.filename(localFilePath) //指定本地文件路径.object(objectName)//对象名 放在子目录下.contentType(mimeType)//设置媒体文件类型.build();//上传文件minioClient.uploadObject(uploadObjectArgs);log.debug("上传文件到minio成功,bucket:{},objectName:{},错误信息:{}",bucket,objectName);return true;} catch (Exception e) {e.printStackTrace();log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());}return false;}//获取文件默认存储目录路径 年/月/日private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/")+"/";return folder;}//获取文件的md5private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}}@Overridepublic UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {//文件名String filename = uploadFileParamsDto.getFilename();//先得到扩展名String extension = filename.substring(filename.lastIndexOf("."));//得到mimeTypeString mimeType = getMimeType(extension);//子目录String defaultFolderPath = getDefaultFolderPath();//文件的md5值String fileMd5 = getFileMd5(new File(localFilePath));String objectName = defaultFolderPath+fileMd5+extension;//上传文件到minioboolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);if(!result){XueChengPlusException.cast("上传文件失败");}//入库文件信息MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);if(mediaFiles==null){XueChengPlusException.cast("文件上传后保存信息失败");}//准备返回的对象UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);return uploadFileResultDto;}/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称*/@Transactionalpublic 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);//文件idmediaFiles.setId(fileMd5);//机构idmediaFiles.setCompanyId(companyId);//桶mediaFiles.setBucket(bucket);//file_pathmediaFiles.setFilePath(objectName);//file_idmediaFiles.setFileId(fileMd5);//urlmediaFiles.setUrl("/"+bucket+"/"+objectName);//上传时间mediaFiles.setCreateDate(LocalDateTime.now());//状态mediaFiles.setStatus("1");//审核状态mediaFiles.setAuditStatus("002003");//插入数据库int insert = mediaFilesMapper.insert(mediaFiles);if(insert<=0){log.debug("向数据库保存文件失败,bucket:{},objectName:{}",bucket,objectName);return null;}return mediaFiles;}return mediaFiles;}
}

http://www.dtcms.com/a/275324.html

相关文章:

  • WebGL简易教程——结语
  • JVM--虚拟线程
  • 【springcloud】快速搭建一套分布式服务springcloudalibaba(四)
  • leetcode:HJ18 识别有效的IP地址和掩码并进行分类统计[华为机考][字符串]
  • 华为IPD(集成产品开发)流程是其研发管理的核心体系
  • 华为 GaussDB :技术特性、应用局限与市场争议
  • Vue Vue-route (5)
  • C++11的整理笔记
  • 快速排序递归和非递归方法的简单介绍
  • Java文件传输要点
  • 接口测试及常用接口测试工具总结
  • 啤酒自动装箱机构设计cad【10张】+三维图+设计说明书
  • 138-EMD-KPCA-CPO-CNN-BiGRU-Attention模型!
  • Java——面向对象
  • Python-魔术方法-创建、初始化与销毁-hash-bool-可视化-运算符重载-容器和大小-可调用对象-上下文管理-反射-描述器-二分-学习笔记
  • script中crossorigin=“anonymous“是什么意思
  • 从0到1搭建个人技术博客:用GitHub Pages+Hexo实现
  • day03-链表part1
  • 秋招笔试考什么?如何针对性去练习?
  • 10. 垃圾回收的算法
  • python excel处理
  • 实用技巧 Excel 与 XML互转
  • Go 设计模式 - 组合复用
  • 校园幸运抽(抽奖系统)测试报告
  • Agent 设计模式
  • 大模型KV缓存量化误差补偿机制:提升推理效率的关键技术
  • 【设计模式】外观模式(门面模式)
  • pdf合并
  • Git系列--4.Git分支设计规范
  • 计算机视觉 之 经典模型汇总