提交代码

This commit is contained in:
zlwu committed 2025-08-03 01:10:31 +08:00
1 parent 0ed820d7ce
commit 4a2761cb0d
8 files changed
+1481 -159

No files matched your search

@@ -398,6 +398,122 @@ def run_all_tests(self):
3. **缓存策略**:对频繁查询的字典数据使用 Redis 缓存
4. **接口响应优化**:减少不必要的字段传输
## 📁 文件下载最佳实践 (2025-08-02 新增)
### 核心问题解决
基于OSS文件下载功能的修复经验,总结文件下载相关的最佳实践。
#### 1. 响应头设置规范
**🔴 关键点:二进制文件下载不要设置charset**
```java
// ❌ 错误方式 - 会导致下载失败
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
// ✅ 正确方式 - 纯二进制类型
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
// ✅ 必需的缓存控制头
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
```
#### 2. Content-Length 正确设置
```java
// ❌ 可能失效的方法引用
storage.download(fileName, response.getOutputStream(), response::setContentLengthLong);
// ✅ 确保安全的设置方式
storage.download(fileName, response.getOutputStream(), contentLength -> {
if (contentLength != null && contentLength > 0) {
response.setContentLengthLong(contentLength);
}
});
```
#### 3. 输出流处理
```java
try {
// 文件下载逻辑
storage.download(fileName, response.getOutputStream(), contentLengthConsumer);
// 🔴 关键:必须手动刷新输出流
response.getOutputStream().flush();
} catch (Exception e) {
throw new ServiceException("文件下载失败: " + e.getMessage());
}
```
#### 4. 完整的文件下载模板
```java
@GetMapping("/download/{fileId}")
public void downloadFile(@PathVariable Long fileId, HttpServletResponse response) {
try {
// 1. 权限验证
if (!hasDownloadPermission(fileId)) {
throw new ServiceException("无权限下载该文件");
}
// 2. 获取文件信息
FileVo fileInfo = fileService.getById(fileId);
if (fileInfo == null) {
throw new ServiceException("文件不存在");
}
// 3. 设置响应头
FileUtils.setAttachmentResponseHeader(response, fileInfo.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// 4. 执行下载
OssClient storage = OssFactory.instance(fileInfo.getService());
storage.download(fileInfo.getFileName(), response.getOutputStream(), contentLength -> {
if (contentLength != null && contentLength > 0) {
response.setContentLengthLong(contentLength);
}
});
// 5. 刷新输出流
response.getOutputStream().flush();
} catch (Exception e) {
log.error("文件下载失败: {}", e.getMessage(), e);
throw new ServiceException("文件下载失败: " + e.getMessage());
}
}
```
### 常见问题排查
#### 问题1:文件传输完成但客户端未收到
- **原因**:Content-Type包含charset或缺少flush()
- **解决**:移除charset,添加手动flush
#### 问题2:下载的文件损坏
- **原因**:Content-Length设置错误
- **解决**:使用lambda表达式确保Content-Length正确设置
#### 问题3:浏览器缓存导致下载异常
- **原因**:缺少缓存控制头
- **解决**:设置no-cache相关响应头
### 测试验证标准
1. **Postman测试**:能正确下载文件
2. **浏览器测试**:点击下载链接正常
3. **文件完整性**:下载文件与原文件MD5一致
4. **不同文件类型**:图片、文档、压缩包都能正常下载
5. **权限控制**:无权限用户被正确拒绝
## 🔄 持续改进
### 规范迭代
@@ -438,5 +554,32 @@ def run_all_tests(self):
**总结**:本最佳实践基于场地预约管理系统的成功开发经验,形成了一套完整的 API 开发标准流程,为后续 API 模块开发提供了可复制的标准模板和质量保证体系。
**维护人员**:开发团队
**最后更新**:2025-07-01
**文档版本**:v1.0
**最后更新**:2025-08-02
**文档版本**:v1.1 (新增文件下载最佳实践)
## 前端开发最佳实践
### 🎯 组件开发规范
#### ImagePreview图片预览组件
- **认证图片加载**: 通过axios获取blob数据,确保认证信息传递
- **内存管理**: 使用`onBeforeUnmount`清理blob URLs,防止内存泄漏
- **缓存优化**: 实现blob URL缓存机制,提升性能
**核心实现**:
```javascript
// 认证图片加载
const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
const response = await request({
url: imageUrl,
method: 'get',
responseType: 'blob'
});
return URL.createObjectURL(response.data);
};
// 生命周期管理
onBeforeUnmount(() => {
blobUrls.value.forEach(url => URL.revokeObjectURL(url));
});
```