From 545488d5e0b1a80e8da0e7c94ad561ee0cd218f3 Mon Sep 17 00:00:00 2001 From: lizhengwei Date: Fri, 28 Nov 2025 18:23:21 +0800 Subject: [PATCH] =?UTF-8?q?rustfs=20=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-site/ruoyi-h5/pom.xml | 11 ++ .../com/luban/api/config/WebMvcConfig.java | 12 ++ .../api/s3/config/S3LoginUserInterceptor.java | 59 +++++++++ .../api/s3/dao/FileMetadataInfoMapper.java | 9 ++ .../api/s3/entity/FileMetadataInfoEntity.java | 102 +++++++++++++++ .../s3/service/MetadataRepositoryImpl.java | 121 ++++++++++++++++++ .../src/main/resources/application.yml | 7 + script/ruoyi_docker/docker-compose.yml | 15 +++ 8 files changed, 336 insertions(+) create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/config/S3LoginUserInterceptor.java create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/dao/FileMetadataInfoMapper.java create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/entity/FileMetadataInfoEntity.java create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/service/MetadataRepositoryImpl.java diff --git a/ruoyi-site/ruoyi-h5/pom.xml b/ruoyi-site/ruoyi-h5/pom.xml index 204bc0ced..1e4e90aa8 100644 --- a/ruoyi-site/ruoyi-h5/pom.xml +++ b/ruoyi-site/ruoyi-h5/pom.xml @@ -84,6 +84,17 @@ dubbo-nacos-spring-boot-starter + + me.liuxp + minio-plus-all-springboot-starter + 1.0.5 + + + me.liuxp + s3-api-minio + 1.0.5 + + org.springframework.boot spring-boot-starter-test diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/config/WebMvcConfig.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/config/WebMvcConfig.java index e6dcc72d2..50a943410 100644 --- a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/config/WebMvcConfig.java +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/config/WebMvcConfig.java @@ -1,7 +1,9 @@ package com.luban.api.config; +import com.luban.api.s3.config.S3LoginUserInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -19,4 +21,14 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); } + + /** + * 前置拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 登录用户获取用户id + registry.addInterceptor(new S3LoginUserInterceptor()).addPathPatterns("/storage/**"); + } + } diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/config/S3LoginUserInterceptor.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/config/S3LoginUserInterceptor.java new file mode 100644 index 000000000..595642d4c --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/config/S3LoginUserInterceptor.java @@ -0,0 +1,59 @@ +package com.luban.api.s3.config; + + +import cn.hutool.core.text.CharSequenceUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.liuxp.minioplus.extension.context.UserHolder; +import org.springframework.lang.Nullable; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Nonnull; + + +/** + * 登录用户拦截器 + * + * @author contact@liuxp.me + * @since 2024/06/11 + */ +@Slf4j +public class S3LoginUserInterceptor implements HandlerInterceptor { + + /** + * 处理登录用户信息 + * + * @param request 请求 + * @param response 返回 + * @param handler 要执行的处理程序 + * @return 是否继续执行下一个拦截器 + */ + @Override + public boolean preHandle(HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull Object handler) { + String userId = request.getHeader("Authorization"); + if (CharSequenceUtil.isBlank(userId)) { + UserHolder.set(""); + } else { + //TODO 用户id还未生成 + UserHolder.set(userId); + } + + return true; + } + + /** + * 清理资源 + * + * @param request 请求 + * @param response 返回 + * @param handler 要执行的处理程序 + * @param exception 异常信息 + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception exception) { + log.debug("Project ThreadLocal 清理之前:{}", UserHolder.get()); + UserHolder.clean(); + log.debug("Project ThreadLocal 清理之之后:{}", UserHolder.get()); + } +} diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/dao/FileMetadataInfoMapper.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/dao/FileMetadataInfoMapper.java new file mode 100644 index 000000000..0af9a293f --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/dao/FileMetadataInfoMapper.java @@ -0,0 +1,9 @@ +package com.luban.api.s3.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import com.luban.api.s3.entity.FileMetadataInfoEntity; + +@Mapper +public interface FileMetadataInfoMapper extends BaseMapper { +} diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/entity/FileMetadataInfoEntity.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/entity/FileMetadataInfoEntity.java new file mode 100644 index 000000000..4393a6470 --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/entity/FileMetadataInfoEntity.java @@ -0,0 +1,102 @@ +package com.luban.api.s3.entity; + +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 io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Date; + +/** + * 文件元数据信息表Entity + * + * @author contact@liuxp.me + * @since 2024-05-22 + **/ +@Getter +@Setter +@ToString +@TableName(value = "file_metadata_info") +public class FileMetadataInfoEntity { + + @TableId(value = "id", type = IdType.AUTO) + @Schema(description = "主键") + private Long id; + + @Schema(description = "文件KEY") + @TableField(value = "file_key") + private String fileKey; + + @Schema(description = "文件md5") + @TableField(value = "file_md5") + private String fileMd5; + + @Schema(description = "文件名") + @TableField(value = "file_name") + private String fileName; + + @Schema(description = "MIME类型") + @TableField(value = "file_mime_type") + private String fileMimeType; + + @Schema(description = "文件后缀") + @TableField(value = "file_suffix") + private String fileSuffix; + + @Schema(description = "文件长度") + @TableField(value = "file_size") + private Long fileSize; + + @Schema(description = "预览图 0:无 1:有") + @TableField(value = "is_preview") + private Boolean isPreview; + + @Schema(description = "是否私有 0:否 1:是") + @TableField(value = "is_private") + private Boolean isPrivate; + + @Schema(description = "存储桶") + @TableField(value = "bucket") + private String storageBucket; + + @Schema(description = "存储桶路径") + @TableField(value = "bucket_path") + private String storagePath; + + @Schema(description = "上传任务id,用于合并切片") + @TableField(value = "upload_id") + private String uploadTaskId; + + @Schema(description = "状态 0:未完成 1:已完成") + @TableField(value = "is_finished") + private Boolean isFinished; + + @Schema(description = "是否分块 0:否 1:是") + @TableField(value = "is_part") + private Boolean isPart; + + @Schema(description = "分块数量") + @TableField(value = "part_number") + private Integer partNumber; + + @Schema(description = "创建人") + @TableField("create_user") + private String createUser; + + @Schema(description = "创建时间") + @TableField("create_time") + private Date createTime; + + @Schema(description = "修改人") + @TableField("update_user") + private String updateUser; + + @Schema(description = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/service/MetadataRepositoryImpl.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/service/MetadataRepositoryImpl.java new file mode 100644 index 000000000..0051e6097 --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/service/MetadataRepositoryImpl.java @@ -0,0 +1,121 @@ +package com.luban.api.s3.service; + + +import cn.hutool.core.text.CharSequenceUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.liuxp.minioplus.api.model.dto.FileMetadataInfoDTO; +import org.liuxp.minioplus.api.model.dto.FileMetadataInfoSaveDTO; +import org.liuxp.minioplus.api.model.dto.FileMetadataInfoUpdateDTO; +import org.liuxp.minioplus.api.model.vo.FileMetadataInfoVo; +import org.liuxp.minioplus.core.repository.MetadataRepository; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import com.luban.api.s3.dao.FileMetadataInfoMapper; +import com.luban.api.s3.entity.FileMetadataInfoEntity; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 文件元数据接口实现类 + * + * @author contact@liuxp.me + * @since 2024/05/22 + */ +@Slf4j +@Service +public class MetadataRepositoryImpl extends ServiceImpl implements MetadataRepository { + + @Override + public List list(FileMetadataInfoDTO searchDTO) { + + // 组装查询参数 + QueryWrapper queryWrapper = buildParams(searchDTO); + + List fileMetadataInfoEntityList = super.list(queryWrapper); + + List fileMetadataInfoVoList = new ArrayList<>(); + + for (FileMetadataInfoEntity fileMetadataInfoEntity : fileMetadataInfoEntityList) { + FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo(); + BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo); + fileMetadataInfoVoList.add(fileMetadataInfoVo); + } + + return fileMetadataInfoVoList; + } + + @Override + public FileMetadataInfoVo one(FileMetadataInfoDTO searchDTO) { + + // 组装查询参数 + QueryWrapper queryWrapper = buildParams(searchDTO); + queryWrapper.last("limit 1"); + + FileMetadataInfoEntity fileMetadataInfoEntity = super.getOne(queryWrapper); + + FileMetadataInfoVo fileMetadataInfoVo = null; + + if (null != fileMetadataInfoEntity) { + fileMetadataInfoVo = new FileMetadataInfoVo(); + BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo); + } + + return fileMetadataInfoVo; + } + + @Override + public FileMetadataInfoVo save(FileMetadataInfoSaveDTO saveDTO) { + + FileMetadataInfoEntity fileMetadataInfoEntity = new FileMetadataInfoEntity(); + BeanUtils.copyProperties(saveDTO, fileMetadataInfoEntity); + fileMetadataInfoEntity.setCreateTime(new Date()); + fileMetadataInfoEntity.setUpdateTime(new Date()); + + boolean result = super.save(fileMetadataInfoEntity); + + FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo(); + if (result) { + BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo); + } + + return fileMetadataInfoVo; + } + + @Override + public FileMetadataInfoVo update(FileMetadataInfoUpdateDTO updateDTO) { + + FileMetadataInfoEntity fileMetadataInfoEntity = new FileMetadataInfoEntity(); + BeanUtils.copyProperties(updateDTO, fileMetadataInfoEntity); + fileMetadataInfoEntity.setUpdateTime(new Date()); + boolean result = super.updateById(fileMetadataInfoEntity); + + FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo(); + if (result) { + BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo); + } + + return fileMetadataInfoVo; + } + + @Override + public Boolean remove(Long id) { + return super.removeById(id); + } + + private QueryWrapper buildParams(FileMetadataInfoDTO searchDTO) { + // 组装查询参数 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getFileKey()), "file_key", searchDTO.getFileKey()); + queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getFileMd5()), "file_md5", searchDTO.getFileMd5()); + queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getBucket()), "bucket", searchDTO.getBucket()); + queryWrapper.eq(null != searchDTO.getIsPrivate(), "is_private", searchDTO.getIsPrivate()); + queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getCreateUser()), "create_user", searchDTO.getCreateUser()); + + return queryWrapper; + } + +} diff --git a/ruoyi-site/ruoyi-h5/src/main/resources/application.yml b/ruoyi-site/ruoyi-h5/src/main/resources/application.yml index da568eed3..3e1e3a85e 100644 --- a/ruoyi-site/ruoyi-h5/src/main/resources/application.yml +++ b/ruoyi-site/ruoyi-h5/src/main/resources/application.yml @@ -73,3 +73,10 @@ mybatis-plus: # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID # 如需改为自增 需要将数据库表全部设置为自增 idType: ASSIGN_ID +minioplus: + # rustfs 部署地址 + backend: http://localhost:9000 + # 授权key + key: rustfsadmin + # 密钥 + secret: rustfsadmin diff --git a/script/ruoyi_docker/docker-compose.yml b/script/ruoyi_docker/docker-compose.yml index 1d976fa91..95ba9afab 100644 --- a/script/ruoyi_docker/docker-compose.yml +++ b/script/ruoyi_docker/docker-compose.yml @@ -7,3 +7,18 @@ services: - /d/Program Files/docker/path/mall-swarm/mongo/db:/data/db #数据文件挂载 ports: - 27017:27017 + rustfs: + image: registry.cn-shanghai.aliyuncs.com/study-03/rustfs:latest + container_name: rustfs + ports: + - "19000:9000" # 管理控制台 + - "9090:9090" # API服务端口 + volumes: + - rustfs_data_volume:/data + environment: + - RUSTFS_ROOT_USER=admin + - RUSTFS_ROOT_PASSWORD=admin123 + restart: unless-stopped + +volumes: + rustfs_data_volume: