This commit is contained in:
luke 2025-06-16 19:14:20 +08:00
parent d87266b3e2
commit fa5fa75b0d
8 changed files with 82 additions and 2 deletions

View File

@ -7,6 +7,7 @@
| 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑 | | 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑 |
| 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 | | 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 |
| 1.0.3 | 2025-06-16 | Luke.Ye | 新增文档上传至对象存储接口 | | 1.0.3 | 2025-06-16 | Luke.Ye | 新增文档上传至对象存储接口 |
| 1.0.4 | 2025-06-17 | Luke.Ye | 上传文档记录入库 |

View File

@ -83,6 +83,21 @@ CREATE TABLE IF NOT EXISTS user_file (
UNIQUE INDEX uniq_user_file(user_id, file_id) UNIQUE INDEX uniq_user_file(user_id, file_id)
) COMMENT='用户-文件直接授权关联表(用于特殊文件个别授权)'; ) COMMENT='用户-文件直接授权关联表(用于特殊文件个别授权)';
-- 8. 对象存储上传记录表
CREATE TABLE IF NOT EXISTS oss_upload_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
file_name VARCHAR(255) NOT NULL COMMENT '文件名',
bucket_name VARCHAR(128) NOT NULL COMMENT '目标 bucket 名',
uploader VARCHAR(128) NOT NULL COMMENT '上传人用户名',
upload_time DATETIME NOT NULL COMMENT '上传时间',
object_path VARCHAR(512) NOT NULL COMMENT 'MinIO 存储路径',
url VARCHAR(1024) COMMENT '访问链接如CDN代理后的地址',
add_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_uploader(uploader),
INDEX idx_file_name(file_name),
INDEX idx_bucket(bucket_name)
) COMMENT='对象存储上传记录表';

View File

@ -0,0 +1,8 @@
package com.knowledge.base.application.service;
/**
* @author Luke.ye
* @date 2025/6/16 19:13
*/
public interface DocAppService {
}

View File

@ -4,9 +4,16 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration @Configuration
@ConfigurationProperties(prefix = "object.storage") @ConfigurationProperties(prefix = "object.storage")
@Data @Data
public class ObjectStorageProperties { public class ObjectStorageProperties {
private boolean useUuidPrefix = true; private boolean useUuidPrefix = true;
/**
* 配置哪些 buckets 是公开的不使用预签名链接
*/
private List<String> publicBuckets;
} }

View File

@ -162,7 +162,8 @@ public class FileWriteController {
// 使用原始文件名作为 keyMinIO 对象路径作为 value // 使用原始文件名作为 keyMinIO 对象路径作为 value
String fileKey = Paths.get(originalName).getFileName().toString(); String fileKey = Paths.get(originalName).getFileName().toString();
filePathMap.put(fileKey, String.format("http://s3.wisdompulse.cn/%s/%s", bucket, safeName)); String url = objectStorageGateway.generateUrl(bucket, safeName);
filePathMap.put(fileKey, url);
} }
return ResponseEntity.ok(Map.of( return ResponseEntity.ok(Map.of(

View File

@ -1,11 +1,14 @@
package com.knowledge.base.infrastructure.south; package com.knowledge.base.infrastructure.south;
import com.knowledge.base.infrastructure.config.ObjectStorageProperties;
import io.minio.*; import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Component @Component
@ -14,15 +17,20 @@ public class MinioOSGatewayImpl implements ObjectStorageGateway {
private final MinioClient minioClient; private final MinioClient minioClient;
private final ObjectStorageProperties objectStorageProperties;
public MinioOSGatewayImpl( public MinioOSGatewayImpl(
@Value("${minio.endpoint:http://s3.wisdompulse.cn}") String endpoint, @Value("${minio.endpoint:http://s3.wisdompulse.cn}") String endpoint,
@Value("${minio.access-key:lukeye}") String accessKey, @Value("${minio.access-key:lukeye}") String accessKey,
@Value("${minio.secret-key:lukeye@6}") String secretKey @Value("${minio.secret-key:lukeye@6}") String secretKey,
ObjectStorageProperties objectStorageProperties
) { ) {
this.minioClient = MinioClient.builder() this.minioClient = MinioClient.builder()
.endpoint(endpoint) .endpoint(endpoint)
.credentials(accessKey, secretKey) .credentials(accessKey, secretKey)
.build(); .build();
this.objectStorageProperties = objectStorageProperties;
} }
@Override @Override
@ -46,4 +54,34 @@ public class MinioOSGatewayImpl implements ObjectStorageGateway {
.build()); .build());
log.info("上传成功: {}/{}", bucket, objectPath); log.info("上传成功: {}/{}", bucket, objectPath);
} }
@Override
public String generateUrl(String bucket, String objectPath) throws Exception {
// 判断 bucket 是否公开通过策略判断或提前配置
boolean isPublic = isPublicBucket(bucket);
if (isPublic) {
// 直接拼接公网 URL你自己的 CDN 域名
return String.format("http://s3.wisdompulse.cn/%s/%s", bucket, objectPath);
} else {
// 生成预签名链接最大 7
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(objectPath)
.expiry(60 * 60 * 24 * 7) // 7
.build()
);
}
}
/**
* 判断 bucket 是否公开默认策略通过配置控制
*/
private boolean isPublicBucket(String bucket) {
List<String> publicBuckets = objectStorageProperties.getPublicBuckets();
return publicBuckets != null && publicBuckets.contains(bucket);
}
} }

View File

@ -13,4 +13,12 @@ public interface ObjectStorageGateway {
* 上传文件到指定 bucket 的路径 * 上传文件到指定 bucket 的路径
*/ */
void uploadFile(String bucket, String objectPath, MultipartFile file) throws Exception; void uploadFile(String bucket, String objectPath, MultipartFile file) throws Exception;
/**
* 根据 bucket object 路径生成可访问链接
* - 公开 bucket返回拼接的固定访问路径
* - 私有 bucket生成预签名 URL最长 7
*/
String generateUrl(String bucket, String objectPath) throws Exception;
} }

View File

@ -27,5 +27,7 @@ import.schedule.cron=0 0 * * * *
knowledge.base.redis.enable=false knowledge.base.redis.enable=false
object.storage.use-uuid-prefix=true object.storage.use-uuid-prefix=true
object.storage.public-buckets[0]=kbase
object.storage.public-buckets[1]=public-media
spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-file-size=200MB
spring.servlet.multipart.max-request-size=500MB spring.servlet.multipart.max-request-size=500MB