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.Driver
    url: jdbc:mysql://127.0.0.1:3306/xc_media?serverTimezone=UTC&userUnicode=true&useSSL=false
    username: root
    password: 123456
  cloud:
   config:
    override-none: true

minio:
  endpoint: http://127.0.0.1:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket:
    files: mediafiles
    videofiles: video

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

spring:
  application:
    name: media-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        namespace: ${spring.profiles.active}
        group: xuecheng-plus-project
      config:
        namespace: ${spring.profiles.active}
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
      shared-configs:
        - data-id: logging-${spring.profiles.active}.yaml
          group: xuecheng-plus-common
          refresh: true

#profiles默认为dev
  profiles:
    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;
    @Bean
    public 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("."));
    // 根据文件扩展名获取文件对应的mimeType
    String mimeType = getMimeType(extension);
    // 获取文件在Minio中默认的存储目录
    String defaultFolderPath = getDefaultFolderPath();
    // 根据File对象获取文件对应的md5值
    String fileMd5 = getFileMd5(file);
    if(StringUtils.isEmpty(objectName)){
        // 默认存储方式,存储到minio中的完整对象名,默认目录/文件MD5值.扩展名
        objectName = defaultFolderPath + fileMd5 + extension;
    }
    // 将文件上传到minio
    boolean 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);
    // 对于未知扩展名的文件采用通用的mimeType
    String 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 {
        // 将服务中接收的临时文件上传到minio
        addMediaFilesToMinIO(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会报空指针异常,对于未知扩展名的则会返回null
        ContentInfo 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/coursefile

HTTP/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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网站项目】基于SSM的274办公自动化管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【Linux系统编程】进程优先级

文章目录 1. 优先级的基本概念2. 为什么存在优先级3. 查看系统进程4. PRI and NI5. top命令修改已存在进程的nice值6. 其他概念 1. 优先级的基本概念 本篇文章讲解进程优先级&#xff0c;首先我们来了解一下进程优先级的概念&#xff1a; cpu资源分配的先后顺序&#xff0c;就…

burp靶场--访问控制【越权】

【Burp系列】超全越权漏洞实验总结 https://portswigger.net/web-security/access-control/lab-unprotected-admin-functionality 1. 访问控制【越权】 https://portswigger.net/web-security/access-control#what-is-access-control ### 什么是访问控制&#xff1a; 访问控…

php基础学习之常量

php常量的基本概念 常量是在程序运行中的一种不可改变的量&#xff08;数据&#xff09;&#xff0c;常量一旦定义&#xff0c;通常不可改变&#xff08;用户级别&#xff09;。 php常量的定义形式 使用define函数&#xff1a;define("常量名字", 常量值);使用cons…

Mac NTFS 磁盘读写工具选哪个好?Tuxera 还是 Paragon?

在使用 Mac 电脑时&#xff0c;我们经常需要读写 NTFS 格式的硬盘或 U 盘。然而&#xff0c;由于 Mac 系统不支持 NTFS 格式的读写&#xff0c;因此我们需要借助第三方工具来实现这个功能。而在市场上&#xff0c;Tuxera 和 Paragon 是两款备受推崇的 Mac NTFS 磁盘读写工具。那…

GSP专版软件系统(医疗器械进销存)

产品概述 软件完全符合药监局GSP认证要求&#xff0c;可订制其它平台的数据对接; 业务流程清晰&#xff0c;操作简单合理&#xff0c;软件速度非常快; 完善的序列号(UDI)管理,并与整个系统融合在一起; 财务账和业务账完美结合; 可自定义的界面、布局管理;灵活的打印样式设计; 可…

有关软件测试的,任何时间都可以,软件测试主要服务项目:测试用例 报告 计划

有关软件测试的&#xff0c;任何时间都可以&#xff0c;软件测试主要服务项目&#xff1a; 1. 测试用例 2. 测试报告 3. 测试计划 4. 白盒测试 5. 黑盒测试 6. 接口测试 7.自动…

Vuex的基础使用

在使用之前要先了解Vuex的组成结构&#xff0c;跟对应的使用关系。 在上图的结构图中可以看到四个组成部分&#xff0c;首先是Components&#xff08;组件&#xff09;、Actions&#xff08;行动&#xff09;、Mutations&#xff08;变化&#xff09;、state&#xff08;状态/数…

Mysql运维篇(三) MySQL数据库分库分表方案

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人&#xff0c;如有侵权请留言&#xff0c;我及时删除。 一、前言 关系型数据库本身比较容易成为系统瓶颈&#xff0c;单机存储容量、连接数、处理能力都有限。当单表的数…

【MySQL进阶】SQL优化

文章目录 SQL 优化主键优化数据组织方式页分裂页合并主键设计原则 insert优化order by优化group by优化limit优化count优化 SQL 优化 主键优化 数据组织方式 在InnoDB存储引擎中&#xff0c;表数据都是根据主键顺序组织存放的&#xff0c;这种存储方式的表称为索引组织表 在In…

【5G 接口协议】N2接口协议NGAP(NG Application Protocol)介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

PIG框架学习3——Redisson 实现业务接口幂等

零、前言 ​ 业务接口幂等问题是在开发中遇到的&#xff0c;如果对业务接口代码不进行幂等控制&#xff0c;并且在前端没有对请求进行限制的情况下&#xff0c;可能会出现多次对接口调用&#xff0c;导致错误异常的发生。就上述情况&#xff0c;对PIGX自带的业务接口幂等实现进…

无法找到mfc100.dll的解决方法分享,如何快速修复mfc100.dll文件

在日常使用电脑时&#xff0c;我们可能会碰到一些系统错误提示&#xff0c;比如“无法找到mfc100.dll”的信息。这种错误通常会阻碍代码的执行或某些应用程序的启动。为了帮助您解决这一问题&#xff0c;本文将深入探讨其成因&#xff0c;并提供几种不同的mfc100.dll解决方案。…

lv14 内核定时器 11

一、时钟中断 硬件有一个时钟装置&#xff0c;该装置每隔一定时间发出一个时钟中断&#xff08;称为一次时钟嘀嗒-tick&#xff09;&#xff0c;对应的中断处理程序就将全局变量jiffies_64加1 jiffies_64 是一个全局64位整型, jiffies全局变量为其低32位的全局变量&#xff0…

三.Winform使用Webview2加载本地HTML页面

Winform使用Webview2加载本地HTML页面 往期目录创建Demo2界面创建HTML页面在Demo2窗体上添加WebView2和按钮加载HTML查看效果 往期目录 往期相关文章目录 专栏目录 创建Demo2界面 经过前面两小节 一.Winform使用Webview2(Edge浏览器核心) 创建demo(Demo1)实现回车导航到指定…

用立创EDA(专业版)设计原理图

简介 立创EDA&#xff0c;国产的EDA工具。 下载 官方下载链接 下载后&#xff0c;直接一直下一步&#xff0c;最后安装提示免费激活即可。 注意 嘉立创EDA专业版的数据和立创EDA标准版的数据不互通&#xff0c; 常用原理图绘制设置 开发流程 新建工程绘制PCB 文件->新…

【QT+QGIS跨平台编译】之一:【sqlite+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、sqlite3介绍二、文件下载三、文件分析四、pro文件五、编译实践 一、sqlite3介绍 SQLite是一款轻型的数据库&#xff0c;是遵守ACID的关系型数据库管理系统&#xff0c;它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的&…

【数据结构】 双链表的基本操作 (C语言版)

目录 一、双链表 1、双链表的定义&#xff1a; 2、双链表表的优缺点&#xff1a; 二、双链表的基本操作算法&#xff08;C语言&#xff09; 1、宏定义 2、创建结构体 3、双链表的初始化 4、双链表表插入 5、双链表的查找 6、双链表的取值 7、求双链表长度 8、双链表…

2023年12月 Scratch 图形化(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch图形化等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 观察下列每个圆形中的四个数,找出规律,在括号里填上适当的数?( ) A:9 B:17 C:21 D:5 答案:C 左上角的数=下面两个数的和+右上角的数

142.环形链表 II 、141. 环形链表(附带源码)

目录 一、142问题的分析与解决&#xff1a; 二、怎么做&#xff1f; 三、142代码 四、141代码 一、142问题的分析与解决&#xff1a; 核心&#xff1a;定义快慢指针&#xff1a;slow、fast 思路是当快指针fast进环时&#xff0c;慢指针slow一定没有进环 这个时候就是就变…