完善minio上传

This commit is contained in:
none 2023-03-08 15:56:44 +08:00
parent 1486e518cc
commit e8455a3a58
16 changed files with 321 additions and 67 deletions

View File

@ -38,19 +38,26 @@ public class BackendConstant {
put("ppt", RESOURCE_TYPE_PPT);
put("pptx", RESOURCE_TYPE_PPT);
}};
public final static HashMap<String, String> RESOURCE_TYPE_2_DIR = new HashMap<>() {{
put(RESOURCE_TYPE_VIDEO, UPLOAD_VIDEO_DIR);
put(RESOURCE_TYPE_IMAGE, UPLOAD_IMAGE_DIR);
put(RESOURCE_TYPE_PDF, UPLOAD_PDF_DIR);
put(RESOURCE_TYPE_WORD, UPLOAD_WORD_DIR);
put(RESOURCE_TYPE_PPT, UPLOAD_PPT_DIR);
}};
public final static String[] RESOURCE_DISK_WHITELIST = {"minio"};
public final static String STORAGE_DRIVER_MINIO = "minio";
public final static String[] RESOURCE_DISK_WHITELIST = {STORAGE_DRIVER_MINIO};
public final static String[] COURSE_HOUR_TYPE_WHITELIST = {"VIDEO"};
public final static String[] COURSE_HOUR_TYPE_WHITELIST_TEXT = {"视频"};
// 图片上传相关配置
public final static String[] UPLOAD_IMAGE_EXT_WL = {"png", "jpg", "jpeg", "gif"};
public final static String[] UPLOAD_IMAGE_CONTENT_TYPE_WL = {"image/png", "image/jpg", "image/jpeg", "image/gif"};
public final static String UPLOAD_IMAGE_DIR = "images/";
// 视频上传配置
public final static String UPLOAD_VIDEO_DIR = "videos/";
public final static String UPLOAD_PDF_DIR = "pdf/";
public final static String UPLOAD_WORD_DIR = "word/";
public final static String UPLOAD_PPT_DIR = "word/";
public final static String PRIVACY_FIELD_TYPE_EMAIL = "email";
public final static String PRIVACY_FIELD_TYPE_PHONE = "phone";

View File

@ -116,11 +116,20 @@ public class ResourceController {
return JsonResponse.error("duration参数必须存在且大于0");
}
if (poster == null || poster.trim().length() == 0) {
return JsonResponse.error("视频封面为空");
return JsonResponse.error("poster参数值不能为空");
}
}
Resource res = resourceService.create(req.getCategoryId(), type, req.getName(), extension, req.getSize(), disk, req.getFileId(), req.getPath(), req.getUrl());
Resource res = resourceService.create(
req.getCategoryId(),
type, req.getName(),
extension,
req.getSize(),
disk,
req.getFileId(),
req.getPath(),
req.getUrl()
);
if (isVideoType) {
resourceVideoService.create(res.getId(), duration, poster);

View File

@ -3,17 +3,20 @@ package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.request.backend.UploadVideoMergeRequest;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.service.ResourceCategoryService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.service.UploadService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import java.util.Arrays;
import java.util.HashMap;
/**
@ -24,74 +27,49 @@ import java.util.HashMap;
@Slf4j
@RequestMapping("/backend/v1/upload")
public class UploadController {
@Autowired
private ResourceService resourceService;
@Autowired
private ResourceCategoryService resourceCategoryService;
@Autowired
private MinioService minioService;
@PostMapping("/image")
public JsonResponse image(@RequestParam HashMap<String, Object> params, MultipartFile file) {
if (file == null || file.isEmpty() || file.getOriginalFilename() == null) {
return JsonResponse.error("请上传文件");
}
@Autowired
private UploadService uploadService;
String contentType = file.getContentType();
if (contentType == null || !Arrays.asList(BackendConstant.UPLOAD_IMAGE_CONTENT_TYPE_WL).contains(contentType)) {
return JsonResponse.error("格式不支持");
}
@Autowired
private ResourceService resourceService;
@PostMapping("/file")
public JsonResponse image(@RequestParam HashMap<String, Object> params, MultipartFile file) throws ServiceException {
Integer cid = MapUtils.getInteger(params, "category_id");
if (cid != null && !cid.equals(0) && resourceCategoryService.getById(cid) == null) {
return JsonResponse.error("分类不存在");
}
String filename = file.getOriginalFilename();
String ext = HelperUtil.fileExt(filename);
if (!Arrays.asList(BackendConstant.UPLOAD_IMAGE_EXT_WL).contains(ext)) {
return JsonResponse.error("格式不支持");
}
String oldFilename = filename.replaceAll("." + ext, "");
String newFilename = HelperUtil.randomString(32) + "." + ext;
String savePath = BackendConstant.UPLOAD_IMAGE_DIR + newFilename;
// 保存文件
String url = minioService.saveFile(file, savePath, contentType);
// 上传记录
Resource res = resourceService.create(cid, BackendConstant.RESOURCE_TYPE_IMAGE, oldFilename, ext, file.getSize(), "minio", "", savePath, url);
Resource res = uploadService.storeMinio(file, cid);
return JsonResponse.data(res);
}
@GetMapping("/minio-upload-id")
@GetMapping("/minio/upload-id")
public JsonResponse minioUploadId(@RequestParam HashMap<String, Object> params) {
String extension = MapUtils.getString(params, "extension");
if (extension == null || extension.trim().length() == 0) {
return JsonResponse.error("extension参数为空");
}
String contentType = BackendConstant.RESOURCE_EXT_2_CONTENT_TYPE.get(extension.toLowerCase());
if (contentType == null) {
return JsonResponse.error("该格式不支持上传");
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(extension.toLowerCase());
if (type == null) {
return JsonResponse.error("该格式文件不支持上传");
}
String filename = HelperUtil.randomString(32) + "." + extension;//文件名
String path = BackendConstant.UPLOAD_VIDEO_DIR + filename;//存储路径
String path = BackendConstant.RESOURCE_TYPE_2_DIR.get(type) + filename;//存储路径
String uploadId = minioService.uploadId(path);
HashMap<String, String> data = new HashMap<>();
data.put("resource_type", BackendConstant.RESOURCE_EXT_2_TYPE.get(extension.toLowerCase()));
data.put("resource_type", type);
data.put("upload_id", uploadId);
data.put("filename", path);
return JsonResponse.data(data);
}
@GetMapping("/minio-pre-sign-url")
@GetMapping("/minio/pre-sign-url")
public JsonResponse minioPreSignUrl(@RequestParam HashMap<String, Object> params) {
String uploadId = MapUtils.getString(params, "upload_id");
Integer partNumber = MapUtils.getInteger(params, "part_number");
@ -105,7 +83,46 @@ public class UploadController {
return JsonResponse.data(data);
}
@GetMapping("/minio-merge")
@PostMapping("/minio/merge-video")
public JsonResponse minioMergeVideo(@RequestBody @Validated UploadVideoMergeRequest req) throws ServiceException {
Integer cid = req.getCategoryId();
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(req.getExtension());
if (type == null) {
return JsonResponse.error("当前格式不支持上传");
}
if (cid != null && resourceCategoryService.find(cid, type) == null) {
return JsonResponse.error("资源分类不存在");
}
// 合并视频文件
String url = minioService.merge(req.getFilename(), req.getUploadId());
// 视频素材保存
Resource videoResource = resourceService.create(
cid,
type,
req.getOriginalFilename(),
req.getExtension(),
req.getSize(),
BackendConstant.STORAGE_DRIVER_MINIO,
"",
req.getFilename(),
url
);
// 视频封面素材保存
Resource posterResource = uploadService.storeBase64Image(req.getPoster(), 0);
// 视频的封面素材改为[隐藏 && 属于视频的子素材]
resourceService.changeParentId(posterResource.getId(), videoResource.getId());
// 视频信息
resourceService.storeResourceVideo(videoResource.getId(), req.getDuration(), posterResource.getUrl());
HashMap<String, Object> data = new HashMap<>();
data.put("url", url);
return JsonResponse.data(data);
}
@GetMapping("/minio/merge")
public JsonResponse minioMerge(@RequestParam HashMap<String, Object> params) {
String filename = MapUtils.getString(params, "filename");
String uploadId = MapUtils.getString(params, "upload_id");

View File

@ -4,21 +4,22 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
*
* @TableName resources
*/
@TableName(value ="resources")
@TableName(value = "resources")
@Data
public class Resource implements Serializable {
/**
*
*
*/
@TableId(type = IdType.AUTO)
private Integer id;
@ -71,11 +72,23 @@ public class Resource implements Serializable {
private String url;
/**
*
*
*/
@JsonProperty("created_at")
private Date createdAt;
/**
* 所属素材
*/
@JsonProperty("parent_id")
private Integer parentId;
/**
* 隐藏[0:,1:]
*/
@JsonIgnore
private Integer isHidden;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@ -92,16 +105,18 @@ public class Resource implements Serializable {
}
Resource other = (Resource) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getType() == null ? other.getType() == null : this.getType().equals(other.getType()))
&& (this.getCategoryId() == null ? other.getCategoryId() == null : this.getCategoryId().equals(other.getCategoryId()))
&& (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
&& (this.getExtension() == null ? other.getExtension() == null : this.getExtension().equals(other.getExtension()))
&& (this.getSize() == null ? other.getSize() == null : this.getSize().equals(other.getSize()))
&& (this.getDisk() == null ? other.getDisk() == null : this.getDisk().equals(other.getDisk()))
&& (this.getFileId() == null ? other.getFileId() == null : this.getFileId().equals(other.getFileId()))
&& (this.getPath() == null ? other.getPath() == null : this.getPath().equals(other.getPath()))
&& (this.getUrl() == null ? other.getUrl() == null : this.getUrl().equals(other.getUrl()))
&& (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()));
&& (this.getType() == null ? other.getType() == null : this.getType().equals(other.getType()))
&& (this.getCategoryId() == null ? other.getCategoryId() == null : this.getCategoryId().equals(other.getCategoryId()))
&& (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
&& (this.getExtension() == null ? other.getExtension() == null : this.getExtension().equals(other.getExtension()))
&& (this.getSize() == null ? other.getSize() == null : this.getSize().equals(other.getSize()))
&& (this.getDisk() == null ? other.getDisk() == null : this.getDisk().equals(other.getDisk()))
&& (this.getFileId() == null ? other.getFileId() == null : this.getFileId().equals(other.getFileId()))
&& (this.getPath() == null ? other.getPath() == null : this.getPath().equals(other.getPath()))
&& (this.getUrl() == null ? other.getUrl() == null : this.getUrl().equals(other.getUrl()))
&& (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
&& (this.getParentId() == null ? other.getParentId() == null : this.getParentId().equals(other.getParentId()))
&& (this.getIsHidden() == null ? other.getIsHidden() == null : this.getIsHidden().equals(other.getIsHidden()));
}
@Override
@ -119,6 +134,8 @@ public class Resource implements Serializable {
result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode());
result = prime * result + ((getUrl() == null) ? 0 : getUrl().hashCode());
result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
result = prime * result + ((getParentId() == null) ? 0 : getParentId().hashCode());
result = prime * result + ((getIsHidden() == null) ? 0 : getIsHidden().hashCode());
return result;
}
@ -139,6 +156,8 @@ public class Resource implements Serializable {
sb.append(", path=").append(path);
sb.append(", url=").append(url);
sb.append(", createdAt=").append(createdAt);
sb.append(", parentId=").append(parentId);
sb.append(", isHidden=").append(isHidden);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();

View File

@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author tengteng
* @description 针对表resources的数据库操作Mapper
* @createDate 2023-03-06 10:06:57
* @createDate 2023-03-08 13:43:00
* @Entity xyz.playedu.api.domain.Resource
*/
@Mapper

View File

@ -49,4 +49,6 @@ public class ResourceRequest {
private String poster;
private Integer parentId;
}

View File

@ -0,0 +1,40 @@
package xyz.playedu.api.request.backend;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/8 14:49
*/
@Data
public class UploadVideoMergeRequest {
@NotBlank(message = "请输入课程标题")
private String filename;
@JsonProperty("upload_id")
@NotBlank(message = "请输入upload_id")
private String uploadId;
@JsonProperty("original_filename")
@NotBlank(message = "请输入original_filename")
private String originalFilename;
@NotNull(message = "请输入size")
private Long size;
@NotNull(message = "请输入duration")
private Integer duration;
@NotBlank(message = "请输入extension")
private String extension;
private Integer categoryId;
@NotNull(message = "请上传视频封面")
private String poster;
}

View File

@ -12,6 +12,8 @@ public interface MinioService {
String saveFile(MultipartFile file, String savePath, String contentType);
String saveBytes(byte[] file, String savePath, String contentType);
String uploadId(String path);
String chunkPreSignUrl(String filename, String partNumber, String uploadId);

View File

@ -19,6 +19,8 @@ public interface ResourceCategoryService extends IService<ResourceCategory> {
ResourceCategory findOrFail(Integer id) throws NotFoundException;
ResourceCategory find(Integer id, String type);
void update(ResourceCategory category, Integer sort, String name);
}

View File

@ -19,4 +19,8 @@ public interface ResourceService extends IService<Resource> {
Resource findOrFail(Integer id) throws NotFoundException;
void changeParentId(Integer id, Integer parentId);
void storeResourceVideo(Integer rid, Integer duration, String poster);
}

View File

@ -0,0 +1,15 @@
package xyz.playedu.api.service;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.exception.ServiceException;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/8 14:02
*/
public interface UploadService {
Resource storeMinio(MultipartFile file, Integer categoryId) throws ServiceException;
Resource storeBase64Image(String content,Integer categoryId) throws ServiceException;
}

View File

@ -13,6 +13,8 @@ import xyz.playedu.api.config.MinioConfig;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.vendor.PlayEduMinioClient;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@ -34,7 +36,7 @@ public class MinioServiceImpl implements MinioService {
@Override
public String url(String path) {
return c.getDomain() + c.getBucket() + path;
return c.getDomain() + c.getBucket() + "/" + path;
}
@Override
@ -85,4 +87,21 @@ public class MinioServiceImpl implements MinioService {
public void removeByPath(String path) {
client.removeObject(RemoveObjectArgs.builder().bucket(c.getBucket()).object(path).build());
}
@Override
@SneakyThrows
public String saveBytes(byte[] file, String savePath, String contentType) {
InputStream inputStream = new ByteArrayInputStream(file);
PutObjectArgs objectArgs = PutObjectArgs.builder()
.bucket(c.getBucket())
.object(savePath)
.stream(inputStream, file.length, -1)
.contentType(contentType)
.build();
client.putObject(objectArgs);
return url(savePath);
}
}

View File

@ -53,6 +53,11 @@ public class ResourceCategoryServiceImpl extends ServiceImpl<ResourceCategoryMap
newCategory.setName(name);
updateById(newCategory);
}
@Override
public ResourceCategory find(Integer id, String type) {
return getOne(query().getWrapper().eq("id", id).eq("type", type));
}
}

View File

@ -4,11 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.mapper.ResourceMapper;
import org.springframework.stereotype.Service;
import xyz.playedu.api.service.ResourceVideoService;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.api.types.paginate.ResourcePaginateFilter;
@ -24,9 +26,12 @@ import java.util.Date;
@Service
public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, Resource> implements ResourceService {
@Autowired
private ResourceVideoService resourceVideoService;
@Override
public PaginationResult<Resource> paginate(int page, int size, ResourcePaginateFilter filter) {
QueryWrapper<Resource> wrapper = query().getWrapper().eq("1", "1");
QueryWrapper<Resource> wrapper = query().getWrapper().eq("is_hidden", 0);
if (filter.getName() != null) {
wrapper.like("name", "%" + filter.getName() + "%");
@ -93,6 +98,20 @@ public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, Resource> i
}
return resource;
}
@Override
public void changeParentId(Integer id, Integer parentId) {
Resource resource = new Resource();
resource.setId(id);
resource.setParentId(parentId);
resource.setIsHidden(1);
updateById(resource);
}
@Override
public void storeResourceVideo(Integer rid, Integer duration, String poster) {
resourceVideoService.create(rid, duration, poster);
}
}

View File

@ -0,0 +1,91 @@
package xyz.playedu.api.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.service.ResourceCategoryService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.service.UploadService;
import xyz.playedu.api.util.Base64Util;
import xyz.playedu.api.util.HelperUtil;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/8 14:02
*/
@Service
public class UploadServiceImpl implements UploadService {
@Autowired
private ResourceCategoryService resourceCategoryService;
@Autowired
private ResourceService resourceService;
@Autowired
private MinioService minioService;
@Override
public Resource storeMinio(MultipartFile file, Integer cid) throws ServiceException {
if (file == null || file.isEmpty() || file.getOriginalFilename() == null) {
throw new ServiceException("请上传文件");
}
// 文件后缀名校验
String filename = file.getOriginalFilename();
String ext = HelperUtil.fileExt(filename);
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(ext);
if (type == null) {
throw new ServiceException("格式不支持");
}
// content-type校验
String contentType = file.getContentType();
String safeContentType = BackendConstant.RESOURCE_EXT_2_CONTENT_TYPE.get(ext);
if (safeContentType == null || !safeContentType.equals(contentType)) {
throw new ServiceException("格式不支持");
}
// 分类校验
if (cid != null && !cid.equals(0) && resourceCategoryService.find(cid, type) == null) {
throw new ServiceException("分类不存在");
}
// 上传原文件的文件名
String oFilename = filename.replaceAll("." + ext, "");
// 自定义新的存储文件名
String newFilename = HelperUtil.randomString(32) + "." + ext;
String savePath = BackendConstant.RESOURCE_TYPE_2_DIR.get(type) + newFilename;
// 保存文件
String url = minioService.saveFile(file, savePath, contentType);
// 上传记录
return resourceService.create(cid, type, oFilename, ext, file.getSize(), BackendConstant.STORAGE_DRIVER_MINIO, "", savePath, url);
}
@Override
public Resource storeBase64Image(String content, Integer categoryId) throws ServiceException {
// data:image/jpeg;base64,
String[] base64Rows = content.split(",");
String contentType = base64Rows[0].replaceAll("data:", "").replaceAll(";base64", "").toLowerCase();
String ext = contentType.replaceAll("image/", "");
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(ext);
String safeContentType = BackendConstant.RESOURCE_EXT_2_CONTENT_TYPE.get(ext);
if (type == null || safeContentType == null || !safeContentType.equals(contentType)) {
throw new ServiceException("格式不支持");
}
byte[] binary = Base64Util.decode(base64Rows[1]);
String filename = HelperUtil.randomString(32) + "." + ext;
String savePath = BackendConstant.RESOURCE_TYPE_2_DIR.get(type) + filename;
// 保存文件
String url = minioService.saveBytes(binary, savePath, contentType);
// 上传记录
return resourceService.create(categoryId, type, filename, ext, (long) binary.length, BackendConstant.STORAGE_DRIVER_MINIO, "", savePath, url);
}
}

View File

@ -16,12 +16,15 @@
<result property="path" column="path" jdbcType="VARCHAR"/>
<result property="url" column="url" jdbcType="VARCHAR"/>
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
<result property="parentId" column="parent_id" jdbcType="INTEGER"/>
<result property="isHidden" column="is_hidden" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,type,category_id,
name,extension,size,
disk,file_id,path,
url,created_at
url,created_at,parent_id,
is_hidden
</sql>
</mapper>