Browse Source

1.增加文件去重校验

2.增加删除过期文件任务
master
xsx 12 months ago
parent
commit
111720c709
  1. 14
      db/im-platform.sql
  2. 2
      im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java
  3. 9
      im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java
  4. 2
      im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java
  5. 5
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  6. 9
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  7. 67
      im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java
  8. 6
      im-platform/src/main/java/com/bx/implatform/enums/FileType.java
  9. 8
      im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java
  10. 15
      im-platform/src/main/java/com/bx/implatform/service/FileService.java
  11. 191
      im-platform/src/main/java/com/bx/implatform/service/FileServiceImpl.java
  12. 121
      im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java
  13. 78
      im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java
  14. 25
      im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java
  15. 1
      im-platform/src/main/resources/application-dev.yml
  16. 1
      im-platform/src/main/resources/application-prod.yml
  17. 1
      im-platform/src/main/resources/application-test.yml
  18. BIN
      im-platform/src/main/resources/static/favicon.ico
  19. 6
      im-uniapp/components/image-upload/image-upload.vue
  20. 2
      im-uniapp/pages/group/group-edit.vue
  21. 2
      im-uniapp/pages/mine/mine-edit.vue
  22. 6
      im-web/src/components/common/FileUpload.vue
  23. 239
      im-web/src/components/setting/Setting.vue

14
db/im-platform.sql

@ -92,3 +92,17 @@ create table `im_sensitive_word`(
`creator` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb4 comment '敏感词';
CREATE TABLE `im_file_info` (
`id` BIGINT NOT NULL auto_increment PRIMARY key comment 'id',
`file_name` VARCHAR(255) NOT NULL comment '文件名',
`file_path` VARCHAR(255) NOT NULL comment '文件地址',
`file_size` INTEGER NOT NULL comment '文件大小',
`file_type` tinyint NOT NULL comment '0:普通文件 1:图片 2:视频',
`compressed_path` VARCHAR(255) DEFAULT NULL comment '压缩文件路径',
`cover_path` VARCHAR(255) DEFAULT NULL comment '封面文件路径,仅视频文件有效',
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP comment '上传时间',
`is_permanent` tinyint DEFAULT 0 comment '是否永久文件',
`md5` VARCHAR(64) NOT NULL comment '文件md5',
UNIQUE KEY `idx_md5` (md5),
) ENGINE = InnoDB CHARSET = utf8mb4 comment '文件';

2
im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java

@ -21,7 +21,7 @@ public @interface RedisLock {
/**
* spel 表达式
*/
String key();
String key() default "";
/**
* 等待锁的时间默认-1不等待直接失败,redisson默认也是-1

9
im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java

@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import com.bx.implatform.annotation.RedisLock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@ -61,18 +62,22 @@ public class RedisLockAspect {
private String parseKey(ProceedingJoinPoint joinPoint){
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RedisLock annotation = method.getAnnotation(RedisLock.class);
String key = annotation.key();
if(StrUtil.isEmpty(key)){
return Strings.EMPTY;
}
// el解析需要的上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 参数名
String[] params = parameterNameDiscoverer.getParameterNames(method);
if(Objects.isNull(params)){
return annotation.key();
return key;
}
Object[] args = joinPoint.getArgs();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
}
Expression expression = parser.parseExpression(annotation.key());
Expression expression = parser.parseExpression(key);
return expression.getValue(context, String.class);
}

2
im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java

@ -29,4 +29,6 @@ public class MinioProperties {
private String filePath;
private String videoPath;
private Integer expireIn;
}

5
im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java

@ -53,4 +53,9 @@ public final class RedisKey {
*/
public static final String IM_REPEAT_SUBMIT = "im:repeat:submit";
/**
* 分布式锁-清理过期文件
*/
public static final String IM_LOCK_FILE_TASK = "im:lock:task:file";
}

9
im-platform/src/main/java/com/bx/implatform/controller/FileController.java

@ -2,7 +2,7 @@ package com.bx.implatform.controller;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.thirdparty.FileService;
import com.bx.implatform.service.FileService;
import com.bx.implatform.vo.UploadImageVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -24,15 +24,16 @@ public class FileController {
@Operation(summary = "上传图片", description = "上传图片,上传后返回原图和缩略图的url")
@PostMapping("/image/upload")
public Result<UploadImageVO> uploadImage(@RequestParam("file") MultipartFile file) {
return ResultUtils.success(fileService.uploadImage(file));
public Result<UploadImageVO> uploadImage(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "true") Boolean isPermanent) {
return ResultUtils.success(fileService.uploadImage(file,isPermanent));
}
@CrossOrigin
@Operation(summary = "上传文件", description = "上传文件,上传后返回文件url")
@PostMapping("/file/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
return ResultUtils.success(fileService.uploadFile(file), "");
return ResultUtils.success(fileService.uploadFile(file));
}
}

67
im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java

@ -0,0 +1,67 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* @author Blue
* @version 1.0
*/
@Data
@TableName("im_file_info")
public class FileInfo {
/**
* 文件ID
*/
@TableId
private Long id;
/**
* 文件名
*/
private String fileName;
/**
* 原始文件存储路径
*/
private String filePath;
/**
* 压缩文件存储路径
*/
private String compressedPath;
/**
* 封面文件路径
*/
private String coverPath;
/**
* 原始文件大小(字节)
*/
private Long fileSize;
/**
* 上传时间
*/
private Date uploadTime;
/**
* 文件类型枚举: FileType
*/
private Integer fileType;
/**
* 是否永久存储
*/
private Boolean isPermanent;
/**
* 文件MD5哈希值
*/
private String md5;
}

6
im-platform/src/main/java/com/bx/implatform/enums/FileType.java

@ -16,11 +16,7 @@ public enum FileType {
/**
* 视频
*/
VIDEO(2, "视频"),
/**
* 声音
*/
AUDIO(3, "声音");
VIDEO(2, "视频");
private final Integer code;

8
im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java

@ -0,0 +1,8 @@
package com.bx.implatform.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bx.implatform.entity.FileInfo;
public interface FileInfoMapper extends BaseMapper<FileInfo> {
}

15
im-platform/src/main/java/com/bx/implatform/service/FileService.java

@ -0,0 +1,15 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.entity.FileInfo;
import com.bx.implatform.vo.UploadImageVO;
import org.springframework.web.multipart.MultipartFile;
public interface FileService extends IService<FileInfo> {
String uploadFile(MultipartFile file);
UploadImageVO uploadImage(MultipartFile file,Boolean isPermanent);
}

191
im-platform/src/main/java/com/bx/implatform/service/FileServiceImpl.java

@ -0,0 +1,191 @@
package com.bx.implatform.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.config.props.MinioProperties;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.entity.FileInfo;
import com.bx.implatform.enums.FileType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.FileInfoMapper;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.thirdparty.MinioService;
import com.bx.implatform.util.FileUtil;
import com.bx.implatform.util.ImageUtil;
import com.bx.implatform.vo.UploadImageVO;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;
/**
* 文件上传服务
*
* author: Blue date: 2024-09-28 version: 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements FileService {
private final MinioService minioSerivce;
private final MinioProperties minioProps;
@PostConstruct
public void init() {
if (!minioSerivce.bucketExists(minioProps.getBucketName())) {
// 创建bucket
minioSerivce.makeBucket(minioProps.getBucketName());
// 公开bucket
minioSerivce.setBucketPublic(minioProps.getBucketName());
}
}
@Override
public String uploadFile(MultipartFile file) {
try {
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if (file.getSize() > Constant.MAX_FILE_SIZE) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件大小不能超过20M");
}
// 如果文件已存在,直接复用
String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
FileInfo fileInfo = findByMd5(md5);
if (!Objects.isNull(fileInfo)) {
// 更新上传时间
fileInfo.setUploadTime(new Date());
this.updateById(fileInfo);
// 返回
return fileInfo.getFilePath();
}
// 上传
String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getFilePath(), file);
if (StringUtils.isEmpty(fileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件上传失败");
}
String url = generUrl(FileType.FILE, fileName);
// 保存文件
saveFileInfo(file, md5, url);
log.info("文件文件成功,用户id:{},url:{}", userId, url);
return url;
} catch (IOException e) {
log.error("上传图片失败,{}", e.getMessage(), e);
throw new GlobalException(ResultCode.PROGRAM_ERROR, "上传图片失败");
}
}
@Transactional
@Override
public UploadImageVO uploadImage(MultipartFile file, Boolean isPermanent) {
try {
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if (file.getSize() > Constant.MAX_IMAGE_SIZE) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片大小不能超过20M");
}
// 图片格式校验
if (!FileUtil.isImage(file.getOriginalFilename())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片格式不合法");
}
UploadImageVO vo = new UploadImageVO();
// 如果文件已存在,直接复用
String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
FileInfo fileInfo = findByMd5(md5);
if (!Objects.isNull(fileInfo)) {
// 更新上传时间和持久化标记
fileInfo.setIsPermanent(isPermanent || fileInfo.getIsPermanent());
fileInfo.setUploadTime(new Date());
this.updateById(fileInfo);
// 返回
vo.setOriginUrl(fileInfo.getFilePath());
vo.setThumbUrl(fileInfo.getCompressedPath());
return vo;
}
// 上传原图
String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(), file);
if (StringUtils.isEmpty(fileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
vo.setOriginUrl(generUrl(FileType.IMAGE, fileName));
if (file.getSize() > 50 * 1024) {
// 大于50K的文件需上传缩略图
byte[] imageByte = ImageUtil.compressForScale(file.getBytes(), 30);
String thumbFileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(),
file.getOriginalFilename(), imageByte, file.getContentType());
if (StringUtils.isEmpty(thumbFileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
vo.setThumbUrl(generUrl(FileType.IMAGE, thumbFileName));
// 保存文件信息
saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), isPermanent);
}else{
// 小于50k,用原图充当缩略图
vo.setThumbUrl(generUrl(FileType.IMAGE, fileName));
// 保存文件信息,由于缩略图不允许删除,此时原图也不允许删除
saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), true);
}
log.info("文件图片成功,用户id:{},url:{}", userId, vo.getOriginUrl());
return vo;
} catch (IOException e) {
log.error("上传图片失败,{}", e.getMessage(), e);
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
}
private String generUrl(FileType fileType, String fileName) {
return StrUtil.join("/", minioProps.getDomain(), minioProps.getBucketName(), getBucketPath(fileType), fileName);
}
private String getBucketPath(FileType fileType) {
return switch (fileType) {
case FILE -> minioProps.getFilePath();
case IMAGE -> minioProps.getImagePath();
case VIDEO -> minioProps.getVideoPath();
};
}
private FileInfo findByMd5(String md5) {
LambdaQueryWrapper<FileInfo> wrapper = Wrappers.lambdaQuery();
wrapper.eq(FileInfo::getMd5, md5);
return getOne(wrapper);
}
private void saveImageFileInfo(MultipartFile file, String md5, String filePath, String compressedPath,
Boolean isPermanent) throws IOException {
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(file.getOriginalFilename());
fileInfo.setFileSize(file.getSize());
fileInfo.setFileType(FileType.IMAGE.code());
fileInfo.setFilePath(filePath);
fileInfo.setCompressedPath(compressedPath);
fileInfo.setMd5(md5);
fileInfo.setIsPermanent(isPermanent);
fileInfo.setUploadTime(new Date());
this.save(fileInfo);
}
private void saveFileInfo(MultipartFile file, String md5, String filePath) throws IOException {
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(file.getOriginalFilename());
fileInfo.setFileSize(file.getSize());
fileInfo.setFileType(FileType.FILE.code());
fileInfo.setFilePath(filePath);
fileInfo.setMd5(md5);
fileInfo.setIsPermanent(false);
fileInfo.setUploadTime(new Date());
this.save(fileInfo);
}
}

121
im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java

@ -1,121 +0,0 @@
package com.bx.implatform.service.thirdparty;
import com.bx.implatform.config.props.MinioProperties;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.enums.FileType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.util.FileUtil;
import com.bx.implatform.util.ImageUtil;
import com.bx.implatform.util.MinioUtil;
import com.bx.implatform.vo.UploadImageVO;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Objects;
/**
* todo 通过校验文件MD5实现重复文件秒传
* 文件上传服务
*
* @author Blue
* @date 2022/10/28
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {
private final MinioUtil minioUtil;
private final MinioProperties minioProps;
@PostConstruct
public void init() {
if (!minioUtil.bucketExists(minioProps.getBucketName())) {
// 创建bucket
minioUtil.makeBucket(minioProps.getBucketName());
// 公开bucket
minioUtil.setBucketPublic(minioProps.getBucketName());
}
}
public String uploadFile(MultipartFile file) {
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if (file.getSize() > Constant.MAX_FILE_SIZE) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件大小不能超过20M");
}
// 上传
String fileName = minioUtil.upload(minioProps.getBucketName(), minioProps.getFilePath(), file);
if (StringUtils.isEmpty(fileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件上传失败");
}
String url = generUrl(FileType.FILE, fileName);
log.info("文件文件成功,用户id:{},url:{}", userId, url);
return url;
}
public UploadImageVO uploadImage(MultipartFile file) {
try {
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if (file.getSize() > Constant.MAX_IMAGE_SIZE) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片大小不能超过20M");
}
// 图片格式校验
if (!FileUtil.isImage(file.getOriginalFilename())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片格式不合法");
}
// 上传原图
UploadImageVO vo = new UploadImageVO();
String fileName = minioUtil.upload(minioProps.getBucketName(), minioProps.getImagePath(), file);
if (StringUtils.isEmpty(fileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
vo.setOriginUrl(generUrl(FileType.IMAGE, fileName));
// 大于30K的文件需上传缩略图
if (file.getSize() > 30 * 1024) {
byte[] imageByte = ImageUtil.compressForScale(file.getBytes(), 30);
fileName = minioUtil.upload(minioProps.getBucketName(), minioProps.getImagePath(), Objects.requireNonNull(file.getOriginalFilename()), imageByte, file.getContentType());
if (StringUtils.isEmpty(fileName)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
}
vo.setThumbUrl(generUrl(FileType.IMAGE, fileName));
log.info("文件图片成功,用户id:{},url:{}", userId, vo.getOriginUrl());
return vo;
} catch (IOException e) {
log.error("上传图片失败,{}", e.getMessage(), e);
throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败");
}
}
public String generUrl(FileType fileTypeEnum, String fileName) {
String url = minioProps.getDomain() + "/" + minioProps.getBucketName();
switch (fileTypeEnum) {
case FILE:
url += "/" + minioProps.getFilePath() + "/";
break;
case IMAGE:
url += "/" + minioProps.getImagePath() + "/";
break;
case VIDEO:
url += "/" + minioProps.getVideoPath() + "/";
break;
default:
break;
}
url += fileName;
return url;
}
}

78
im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java

@ -0,0 +1,78 @@
package com.bx.implatform.task.schedule;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bx.implatform.annotation.RedisLock;
import com.bx.implatform.config.props.MinioProperties;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.FileInfo;
import com.bx.implatform.service.FileService;
import com.bx.implatform.thirdparty.MinioService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
/**
* 过期文件清理任务
*
* @author Blue
* @version 1.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FileExpireTask {
private final FileService fileService;
private final MinioService minioService;
private final MinioProperties minioProps;
@RedisLock(prefixKey = RedisKey.IM_LOCK_FILE_TASK)
@Scheduled(cron = "0 * * * * ?")
public void run() {
log.info("【定时任务】过期文件处理...");
int batchSize = 100;
List<FileInfo> files = loadBatch(batchSize);
while (true) {
for (FileInfo fileInfo : files) {
String url = fileInfo.getFilePath();
String relativePath = url.substring(fileInfo.getFilePath().indexOf(minioProps.getBucketName()));
String[] arr = relativePath.split("/");
String bucket = minioProps.getBucketName();
String path = arr[1];
String fileNme = StrUtil.join("/", arr[2], arr[3]);
if (minioService.isExist(bucket, path, fileNme)) {
if (!minioService.remove(bucket, path, fileNme)) {
// 删除失败,不再往下执行
log.error("删除过期文件异常, id:{},文件名:{}", fileInfo.getId(), fileInfo.getFileName());
return;
}
// 删除文件信息
fileService.removeById(fileInfo.getId());
}
}
if (files.size() < batchSize) {
break;
}
// 下一批
files = loadBatch(batchSize);
}
}
List<FileInfo> loadBatch(int size) {
Date minDate = DateUtils.addDays(new Date(), -minioProps.getExpireIn());
LambdaQueryWrapper<FileInfo> wrapper = Wrappers.lambdaQuery();
wrapper.eq(FileInfo::getIsPermanent, false);
wrapper.le(FileInfo::getUploadTime, minDate);
wrapper.orderByAsc(FileInfo::getId);
wrapper.last("limit " + size);
return fileService.list(wrapper);
}
}

25
im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java → im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java

@ -1,5 +1,6 @@
package com.bx.implatform.util;
package com.bx.implatform.thirdparty;
import com.bx.implatform.util.DateTimeUtils;
import io.minio.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -14,7 +15,7 @@ import java.util.Date;
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {
public class MinioService {
private final MinioClient minioClient;
@ -137,11 +138,29 @@ public class MinioUtil {
*/
public boolean remove(String bucketName, String path, String fileName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path + fileName).build());
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build());
} catch (Exception e) {
log.error("删除文件失败,", e);
return false;
}
return true;
}
/**
* 判断文件是否存在
*
* @param bucketName bucket名称
* @param path 路径
* @param fileName 文件名
* @return
*/
public Boolean isExist(String bucketName, String path, String fileName) {
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build());
} catch (Exception e) {
return false;
}
return true;
}
}

1
im-platform/src/main/resources/application-dev.yml

@ -18,6 +18,7 @@ minio:
imagePath: image
filePath: file
videoPath: video
expireIn: 180 # 文件过期时间,单位:天
webrtc:
max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16

1
im-platform/src/main/resources/application-prod.yml

@ -19,6 +19,7 @@ minio:
imagePath: image
filePath: file
videoPath: video
expireIn: 180 # 文件过期时间,单位:天
webrtc:
max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16

1
im-platform/src/main/resources/application-test.yml

@ -19,6 +19,7 @@ minio:
imagePath: image
filePath: file
videoPath: video
expireIn: 180 # 文件过期时间,单位:天
webrtc:
max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16

BIN
im-platform/src/main/resources/static/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 22 KiB

6
im-uniapp/components/image-upload/image-upload.vue

@ -29,6 +29,10 @@ export default {
type: String,
default: 'album'
},
isPermanent: {
type: Boolean,
default: false
},
onBefore: {
type: Function,
default: null
@ -61,7 +65,7 @@ export default {
},
uploadImage(file) {
uni.uploadFile({
url: UNI_APP.BASE_URL + '/image/upload',
url: UNI_APP.BASE_URL + '/image/upload?isPermanent=' + this.isPermanent,
header: {
accessToken: uni.getStorageSync("loginInfo").accessToken
},

2
im-uniapp/pages/group/group-edit.vue

@ -5,7 +5,7 @@
<view class="form-item">
<view class="label">群聊头像</view>
<view class="value"></view>
<image-upload v-if="isOwner" :onSuccess="onUnloadImageSuccess">
<image-upload v-if="isOwner" :isPermanent="true" :onSuccess="onUnloadImageSuccess">
<image :src="group.headImageThumb" class="group-image"></image>
</image-upload>
<head-image v-else class="group-image" :name="group.showGroupName" :url="group.headImageThumb"

2
im-uniapp/pages/mine/mine-edit.vue

@ -4,7 +4,7 @@
<view class="form">
<view class="form-item">
<view class="label">头像</view>
<image-upload class="value" :onSuccess="onUnloadImageSuccess">
<image-upload class="value" :isPermanent="true" :onSuccess="onUnloadImageSuccess">
<image :src="userInfo.headImageThumb" class="head-image"></image>
</image-upload>
</view>

6
im-web/src/components/common/FileUpload.vue

@ -33,6 +33,10 @@ export default {
type: Boolean,
default: false
},
isPermanent: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
@ -52,7 +56,7 @@ export default {
let formData = new FormData()
formData.append('file', file.file)
this.$http({
url: this.action,
url: this.action + '?isPermanent=' + this.isPermanent,
data: formData,
method: 'post',
headers: {

239
im-web/src/components/setting/Setting.vue

@ -1,140 +1,141 @@
<template>
<el-dialog class="setting" title="设置" :visible.sync="visible" width="420px" :before-close="onClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm" size="small">
<el-form-item label="头像" style="margin-bottom: 0 !important;">
<file-upload class="avatar-uploader" :action="imageAction" :showLoading="true" :maxSize="maxSize"
@success="onUploadSuccess" :fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp']">
<img v-if="userInfo.headImage" :src="userInfo.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</file-upload>
</el-form-item>
<el-form-item label="用户名">
<el-input disabled v-model="userInfo.userName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item prop="nickName" label="昵称">
<el-input v-model="userInfo.nickName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userInfo.sex">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="个性签名">
<el-input type="textarea" v-model="userInfo.signature" :rows="3" maxlength="64"></el-input>
</el-form-item>
</el-form>
<el-dialog class="setting" title="设置" :visible.sync="visible" width="420px" :before-close="onClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm" size="small">
<el-form-item label="头像" style="margin-bottom: 0 !important;">
<file-upload class="avatar-uploader" :action="imageAction" :showLoading="true" :maxSize="maxSize"
:isPermanent="true" @success="onUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp']">
<img v-if="userInfo.headImage" :src="userInfo.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</file-upload>
</el-form-item>
<el-form-item label="用户名">
<el-input disabled v-model="userInfo.userName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item prop="nickName" label="昵称">
<el-input v-model="userInfo.nickName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userInfo.sex">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="个性签名">
<el-input type="textarea" v-model="userInfo.signature" :rows="3" maxlength="64"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="onClose()"> </el-button>
<el-button type="primary" @click="onSubmit()"> </el-button>
</span>
</el-dialog>
<span slot="footer" class="dialog-footer">
<el-button @click="onClose()"> </el-button>
<el-button type="primary" @click="onSubmit()"> </el-button>
</span>
</el-dialog>
</template>
<script>
import FileUpload from "../common/FileUpload.vue";
export default {
name: "setting",
components: {
FileUpload
},
data() {
return {
userInfo: {},
maxSize: 5 * 1024 * 1024,
action: "/image/upload",
rules: {
nickName: [{
required: true,
message: '请输入昵称',
trigger: 'blur'
}]
}
}
},
methods: {
name: "setting",
components: {
FileUpload
},
data() {
return {
userInfo: {},
maxSize: 5 * 1024 * 1024,
action: "/image/upload",
rules: {
nickName: [{
required: true,
message: '请输入昵称',
trigger: 'blur'
}]
}
}
},
methods: {
onClose() {
this.$emit("close");
},
onSubmit() {
this.$refs['settingForm'].validate((valid) => {
if (!valid) {
return false;
}
this.$http({
url: "/user/update",
method: "put",
data: this.userInfo
}).then(() => {
this.$store.commit("setUserInfo", this.userInfo);
this.$emit("close");
this.$message.success("修改成功");
})
});
},
onUploadSuccess(data, file) {
this.userInfo.headImage = data.originUrl;
this.userInfo.headImageThumb = data.thumbUrl;
}
},
props: {
visible: {
type: Boolean
}
},
computed: {
imageAction() {
return `/image/upload`;
}
},
watch: {
visible: function (newData, oldData) {
//
let mine = this.$store.state.userStore.userInfo;
this.userInfo = JSON.parse(JSON.stringify(mine));
}
}
onClose() {
this.$emit("close");
},
onSubmit() {
this.$refs['settingForm'].validate((valid) => {
if (!valid) {
return false;
}
this.$http({
url: "/user/update",
method: "put",
data: this.userInfo
}).then(() => {
this.$store.commit("setUserInfo", this.userInfo);
this.$emit("close");
this.$message.success("修改成功");
})
});
},
onUploadSuccess(data, file) {
this.userInfo.headImage = data.originUrl;
this.userInfo.headImageThumb = data.thumbUrl;
}
},
props: {
visible: {
type: Boolean
}
},
computed: {
imageAction() {
return `/image/upload`;
}
},
watch: {
visible: function(newData, oldData) {
//
let mine = this.$store.state.userStore.userInfo;
this.userInfo = JSON.parse(JSON.stringify(mine));
}
}
}
</script>
<style lang="scss">
.setting {
.el-form {
padding: 10px 0 0 10px;
}
.el-form {
padding: 10px 0 0 10px;
}
.avatar-uploader {
--width: 112px;
.avatar-uploader {
--width: 112px;
.el-upload {
border: 1px dashed #d9d9d9 !important;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.el-upload {
border: 1px dashed #d9d9d9 !important;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.el-upload:hover {
border-color: #409EFF;
}
.el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 24px;
color: #8c939d;
width: var(--width);
height: var(--width);
line-height: var(--width);
text-align: center;
}
.avatar-uploader-icon {
font-size: 24px;
color: #8c939d;
width: var(--width);
height: var(--width);
line-height: var(--width);
text-align: center;
}
.avatar {
width: var(--width);
height: var(--width);
display: block;
}
}
.avatar {
width: var(--width);
height: var(--width);
display: block;
}
}
}
</style>
Loading…
Cancel
Save