架构设计 (Architecture)
本文档详细介绍文件模块的架构设计、设计模式和技术决策,帮助你深入理解模块的内部实现。
🏗️ 整体架构
分层架构
文件模块采用经典的三层架构 + 基础设施层,实现清晰的职责分离:
graph TB
subgraph "展示层 (Presentation)"
Controller[FileController]
end
subgraph "业务层 (Business)"
Service[FileServiceImpl]
Converter[FileConverter]
end
subgraph "数据层 (Data)"
Mapper[FileMapper]
Entity[FileFile Entity]
end
subgraph "基础设施层 (Infrastructure)"
Strategy[FileStorageStrategy]
Bitiful[BitifulStorage]
Config[BitifulConfig]
end
subgraph "外部服务"
S3[Bitiful S4]
DB[(MySQL)]
end
Controller --> Service
Service --> Converter
Service --> Mapper
Service --> Strategy
Strategy --> Bitiful
Bitiful --> Config
Bitiful --> S3
Mapper --> Entity
Entity --> DB
style Controller fill:#e1f5ff
style Service fill:#fff4e1
style Mapper fill:#f0f0f0
style Strategy fill:#e8f5e9
style Bitiful fill:#ffe8f0
层级职责
| 层 | 职责 | 关键组件 |
|---|---|---|
| 展示层 | 处理HTTP请求,参数验证,返回统一响应 | FileController |
| 业务层 | 实现业务逻辑(秒传检测、状态管理) | FileServiceImpl, FileConverter |
| 数据层 | 数据持久化,CRUD操作 | FileMapper, FileFile |
| 基础设施层 | 外部服务集成(S3、OSS) | FileStorageStrategy, BitifulStorage |
🎨 设计模式
1. 策略模式 (Strategy Pattern)
目的:支持多种存储服务,运行时动态切换。
实现:
classDiagram
class FileStorageStrategy {
<<interface>>
+upload(file, fileKey) String
+generatePresignedUrl(fileKey, minutes) String
+generateDownloadUrl(fileKey, minutes) String
+delete(fileKey) void
+getBucketName() String
}
class BitifulStorage {
-S3Client s3Client
-S3Presigner s3Presigner
-BitifulProperties properties
+upload() String
+generatePresignedUrl() String
+generateDownloadUrl() String
+delete() void
}
class AliyunOSSStorage {
-OSSClient ossClient
-OSSProperties properties
+upload() String
+generatePresignedUrl() String
...
}
class MinIOStorage {
-MinioClient minioClient
+upload() String
...
}
FileStorageStrategy <|.. BitifulStorage
FileStorageStrategy <|.. AliyunOSSStorage
FileStorageStrategy <|.. MinIOStorage
代码示例:
// 1. 定义策略接口
public interface FileStorageStrategy {
/**
* 生成预签名上传URL
* @param fileKey 文件键(如 uploads/2025/12/xxx.jpg)
* @param expireMinutes 过期分钟数
* @return 预签名URL
*/
String generatePresignedUrl(String fileKey, int expireMinutes);
// 其他方法...
}
// 2. 实现具体策略
@Service("BITIFUL") // Bean名称即为策略类型
public class BitifulStorage implements FileStorageStrategy {
private final S3Client s3Client;
private final S3Presigner s3Presigner;
@Override
public String generatePresignedUrl(String fileKey, int expireMinutes) {
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(properties.getBucket())
.key(fileKey)
.build();
PutObjectPresignRequest presignRequest =
PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(expireMinutes))
.putObjectRequest(putRequest)
.build();
return s3Presigner.presignPutObject(presignRequest)
.url().toString();
}
}
// 3. 使用策略
@Service
public class FileServiceImpl {
private final FileStorageStrategy storageStrategy;
public PreSignedUploadVO generateUploadUrl(PreSignedUrlRequest request) {
String fileKey = generateFileKey(request.getFileName());
// 调用策略生成预签名URL
String uploadUrl = storageStrategy.generatePresignedUrl(fileKey, 30);
return PreSignedUploadVO.builder()
.uploadUrl(uploadUrl)
.fileKey(fileKey)
.build();
}
}
优势:
- ✅ 业务代码与具体存储解耦
- ✅ 支持配置文件切换存储类型
- ✅ 易于扩展新的存储实现
- ✅ 符合开闭原则(OCP)
2. 工厂模式 (Factory Pattern)
目的:集中管理S3客户端创建的复杂逻辑。
实现:
@Configuration
@RequiredArgsConstructor
public class BitifulConfig {
private final BitifulProperties properties;
/**
* 工厂方法:创建S3Client
*/
@Bean
public S3Client bitifulS3Client() {
return S3Client.builder()
.region(Region.of(properties.getRegion()))
.endpointOverride(URI.create(properties.getEndpoint()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(
properties.getAccessKey(),
properties.getSecretKey()
)
))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true) // Bitiful必须使用path-style
.chunkedEncodingEnabled(true) // 支持大文件分片
.build())
.build();
}
/**
* 工厂方法:创建S3Presigner
*/
@Bean
public S3Presigner bitifulS3Presigner() {
return S3Presigner.builder()
.region(Region.of(properties.getRegion()))
.endpointOverride(URI.create(properties.getEndpoint()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(
properties.getAccessKey(),
properties.getSecretKey()
)
))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true)
.build())
.build();
}
}
优势:
- ✅ 复杂的SDK初始化逻辑集中管理
- ✅ Bean单例注入,线程安全
- ✅ 配置与使用分离
3. 适配器模式 (Adapter Pattern)
目的:将AWS SDK V2的复杂API适配为业务易用的接口。
实现:
classDiagram
class FileStorageStrategy {
<<Target Interface>>
+generatePresignedUrl() String
}
class S3Presigner {
<<Adaptee (AWS SDK)>>
+presignPutObject(request) PresignedPutObjectRequest
}
class BitifulStorage {
<<Adapter>>
-S3Presigner s3Presigner
+generatePresignedUrl() String
}
FileStorageStrategy <|.. BitifulStorage
BitifulStorage --> S3Presigner : 使用
代码示例:
// Adaptee - AWS SDK的复杂API
S3Presigner s3Presigner = ...;
PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(...);
String url = presignedRequest.url().toString();
// Adapter - 简化后的业务接口
FileStorageStrategy strategy = ...;
String url = strategy.generatePresignedUrl(fileKey, 30);
优势:
- ✅ 屏蔽第三方SDK的复杂性
- ✅ 便于替换底层实现(换SDK)
- ✅ 统一业务接口
4. 模板方法模式 (Template Method)
目的:BaseServiceImpl提供CRUD模板,子类专注业务逻辑。
实现:
// 模板类(blog-common)
public abstract class BaseServiceImpl<M, E, V, D, C> {
// 模板方法 - 定义算法骨架
public Long saveByDto(D dto) {
// 1. 前置钩子
preSave(dto);
// 2. DTO -> Entity转换
E entity = converter.dtoToEntity(dto);
// 3. 保存到数据库
baseMapper.insert(entity);
// 4. 后置钩子
afterSave(entity);
return getId(entity);
}
// 钩子方法 - 子类可覆盖
protected void preSave(D dto) {}
protected void afterSave(E entity) {}
}
// 具体实现类
@Service
public class FileServiceImpl extends BaseServiceImpl<...> {
@Override
protected void preSave(FileDTO dto) {
// 自定义前置逻辑
validateFile(dto);
}
@Override
protected void afterSave(FileFile entity) {
// 自定义后置逻辑(如发送事件)
eventPublisher.publishEvent(new FileUploadedEvent(entity));
}
}
优势:
- ✅ 减少重复代码
- ✅ 统一CRUD逻辑
- ✅ 子类专注业务扩展
🔄 数据流转
完整上传流程(带秒传)
sequenceDiagram
participant FE as 前端
participant CTL as FileController
participant SVC as FileServiceImpl
participant CONV as FileConverter
participant MAP as FileMapper
participant STR as BitifulStorage
participant S3 as Bitiful S4
participant DB as MySQL
FE->>CTL: 1. POST /presigned (fileName, size, md5)
CTL->>CTL: 2. @Valid 参数验证
CTL->>SVC: 3. generateUploadUrl(request)
SVC->>SVC: 4.
validateFile(扩展名、大小、类型)
SVC->>MAP: 5. SELECT WHERE md5=? AND size=?
MAP->>DB: 6. 查询数据库
DB-->>MAP: 7. 返回结果
alt 秒传命中
MAP-->>SVC: 8. 已存在文件记录
SVC->>SVC: 9. createInstantUploadResponse()
SVC-->>CTL: 10. instant=true, fileId=xxx, uploadUrl=null
CTL-->>FE: 11. ⚡ 秒传成功
else 需要上传
MAP-->>SVC: 12. 未找到
SVC->>SVC: 13. generateFileKey() 生成文件路径
SVC->>CONV: 14. dtoToEntity转换
CONV-->>SVC: 15. FileFile Entity
SVC->>MAP: 16. INSERT (status=PENDING)
MAP->>DB: 17. 保存记录
SVC->>STR: 18. generatePresignedUrl(fileKey, 30min)
STR->>STR: 19. PutObjectPresignRequest
STR->>S3: 20. 调用S3Presigner
S3-->>STR: 21. 预签名URL
STR-->>SVC: 22. url
SVC-->>CTL: 23. instant=false, uploadUrl=xxx, fileId=yyy
CTL-->>FE: 24. 返回预签名URL
FE->>S3: 25. PUT 上传文件
S3-->>FE: 26. 200 OK
FE->>CTL: 27. PATCH /files/{id}/confirm
CTL->>SVC: 28. confirmUpload(fileId)
SVC->>MAP: 29. UPDATE status=COMPLETED
MAP->>DB: 30. 更新记录
SVC-->>CTL: 31. 完成
CTL-->>FE: 32. 🎉 上传成功
end
🗂️ 模块结构
物理结构
blog-module-file/
├── blog-file-api/ # API层(接口定义)
│ └── src/main/java/com/blog/
│ ├── dto/ # 数据传输对象
│ │ ├── FileDTO.java
│ │ └── PreSignedUrlRequest.java
│ ├── vo/ # 视图对象
│ │ ├── FileVO.java
│ │ └── PreSignedUploadVO.java
│ └── service/
│ └── IFileService.java
│
└── blog-file-service/ # 实现层
└── src/main/java/com/blog/
├── file/
│ ├── controller/
│ │ └── FileController.java
│ ├── service/impl/
│ │ └── FileServiceImpl.java
│ ├── converter/
│ │ └── FileConverter.java
│ ├── entity/
│ │ └── FileFile.java
│ └── mapper/
│ └── FileMapper.java
│
└── infrastructure/ # 基础设施
├── config/
│ └── BitifulConfig.java
├── storage/
│ └── FileStorageStrategy.java
└── oss/
└── BitifulStorage.java
依赖关系
graph LR
APP[blog-application] --> SERVICE[blog-file-service]
SERVICE --> API[blog-file-api]
SERVICE --> COMMON[blog-common]
API --> COMMON
SERVICE --> AWS[AWS SDK V2]
SERVICE --> SPRING[Spring Boot]
SERVICE --> MYBATIS[MyBatis-Plus]
style APP fill:#e8f5e9
style SERVICE fill:#fff4e1
style API fill:#e1f5ff
style COMMON fill:#f0f0f0
🔐 安全设计
1. 预签名URL安全
时效性控制:
// 默认30分钟过期
String url = strategy.generatePresignedUrl(fileKey, 30);
// Bitiful限制最多60分钟
if (expireMinutes > 60) {
throw new BusinessException("过期时间不能超过60分钟");
}
权限隔离:
- 使用子账户Access Key,限制权限范围
- 不同Bucket对应不同子账户
- 开启Bucket版本控制和日志
2. 文件验证
扩展名白名单:
private boolean isValidExtension(String ext) {
return allowedExtensions.contains(ext.toLowerCase());
}
大小限制:
if (file.getSize() > maxFileSize) {
throw new BusinessException(FileErrorCode.FILE_EXCEED_MAX_SIZE);
}
Content-Type检查:
String fileType = file.getContentType();
if (!isValidContentType(fileType)) {
throw new BusinessException(FileErrorCode.FILE_INVALID_TYPE);
}
3. 接口认证
JWT保护:
// SecurityConfig.java
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/files/**").authenticated() // 需要JWT token
)
用户隔离:
// 记录上传者
Long currentUserId = SecurityUtils.getCurrentUserId();
file.setCreateBy(currentUserId);
📊 性能优化
1. 直传优化
传统方式(经过后端):
客户端 --文件--> 后端服务器 --文件--> 云存储
耗时: 网络延迟 * 2 + 服务器IO时间
预签名URL方式(直传):
客户端 --------------文件-------------> 云存储
耗时: 网络延迟 * 1
性能提升:
- 上传速度提升50%+
- 服务器带宽成本降低90%
2. 秒传优化
MD5索引:
CREATE INDEX idx_md5_size ON file_file(md5, file_size);
查询性能:
// O(1) 时间复杂度(索引)
SELECT * FROM file_file
WHERE md5 = ? AND file_size = ?
AND upload_status = 1
LIMIT 1;
3. 并发优化
Bean单例S3Client:
@Bean
@Scope("singleton") // 默认单例,线程安全
public S3Client bitifulS3Client() { ... }
连接池复用:AWS SDK V2内置连接池,自动管理。
🚀 可扩展性
1. 添加新存储
只需3步:
// Step 1: 实现策略接口
@Service("ALIYUN_OSS")
public class AliyunOSSStorage implements FileStorageStrategy {
// 实现接口方法
}
// Step 2: 创建配置类
@ConfigurationProperties("oss.aliyun")
public class AliyunOSSProperties { ... }
@Configuration
public class AliyunOSSConfig {
@Bean
public OSSClient ossClient() { ... }
}
// Step 3: 配置文件切换
oss:
type: ALIYUN_OSS # 切换到阿里云
2. 扩展文件处理
事件驱动:
// 定义事件
public class FileUploadedEvent extends ApplicationEvent {
private final Long fileId;
}
// 发布事件
applicationEventPublisher.publishEvent(
new FileUploadedEvent(fileId)
);
// 监听处理
@EventListener
public void handleFileUploaded(FileUploadedEvent event) {
if (isImage(event.getFileId())) {
// 生成缩略图
thumbnailService.generate(event.getFileId());
}
}
🎯 设计原则
遵循的SOLID原则
| 原则 | 体现 |
|---|---|
| 单一职责 (SRP) | 每个类只负责一项职责: |
- FileController只处理HTTP
- FileServiceImpl只处理业务逻辑
- BitifulStorage只负责S3交互 | | 开闭原则 (OCP) | 通过策略模式支持扩展,无需修改现有代码 | | 里氏替换 (LSP) | 所有FileStorageStrategy实现可互相替换 | | 接口隔离 (ISP) | 接口method精简,避免冗余 | | 依赖倒置 (DIP) | 业务层依赖抽象接口FileStorageStrategy,不依赖具体实现 |
📚 延伸阅读
🎓 学习建议:建议在理解架构后,继续学习 上传流程文档,掌握核心业务逻辑。