ai-engineering-init 1.3.4 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +79 -32
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +79 -32
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks/cursor-skill-eval.js +53 -1
- package/.cursor/hooks.json +3 -3
- package/.cursor/skills/add-skill/SKILL.md +79 -32
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- package/package.json +1 -1
|
@@ -8,856 +8,248 @@ description: |
|
|
|
8
8
|
- 云存储配置
|
|
9
9
|
- 预签名URL生成
|
|
10
10
|
- 文件元数据管理
|
|
11
|
-
-
|
|
11
|
+
- OSS服务商切换
|
|
12
12
|
|
|
13
|
-
触发词:文件上传、OSS、云存储、MinIO、阿里云、腾讯云、七牛、图片上传、文件下载、预签名、presigned
|
|
13
|
+
触发词:文件上传、OSS、云存储、MinIO、阿里云、腾讯云、七牛、图片上传、文件下载、预签名、presigned、OssClient、OssFactory
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# 文件与云存储指南
|
|
17
17
|
|
|
18
|
-
>
|
|
18
|
+
> 模块位置:`ruoyi-common/ruoyi-common-oss`
|
|
19
|
+
> 统一协议:基于 AWS S3 SDK v2,兼容所有 S3 协议云服务
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## 支持的存储类型
|
|
23
|
-
|
|
24
|
-
> **统一协议**:基于 AWS S3 SDK v2,支持所有兼容 S3 协议的云服务
|
|
21
|
+
## 核心类
|
|
25
22
|
|
|
26
|
-
|
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
| MinIO | `minio` | 开源对象存储 |
|
|
33
|
-
| 华为云OBS | `obs` | 华为云对象存储 |
|
|
23
|
+
| 类 | 说明 |
|
|
24
|
+
|----|------|
|
|
25
|
+
| `OssFactory` | 获取 OssClient 实例(只有2个方法) |
|
|
26
|
+
| `OssClient` | 统一操作入口(基于 AWS S3 SDK v2) |
|
|
27
|
+
| `UploadResult` | 上传结果(url, filename, eTag) |
|
|
28
|
+
| `ISysOssService` | OSS 文件管理服务接口 |
|
|
34
29
|
|
|
35
30
|
---
|
|
36
31
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
| 类 | 位置 | 说明 |
|
|
40
|
-
|----|------|------|
|
|
41
|
-
| `OssFactory` | `ruoyi-common-oss/.../factory/OssFactory.java` | 获取 OssClient 实例(只有2个方法) |
|
|
42
|
-
| `OssClient` | `ruoyi-common-oss/.../core/OssClient.java` | 统一操作入口(基于 AWS S3 SDK v2) |
|
|
43
|
-
| `UploadResult` | `ruoyi-common-oss/.../entity/UploadResult.java` | 上传结果(url, filename, eTag) |
|
|
44
|
-
| `ISysOssService` | `ruoyi-system/.../service/ISysOssService.java` | OSS 文件管理服务接口 |
|
|
45
|
-
| `SysOssServiceImpl` | `ruoyi-system/.../service/impl/SysOssServiceImpl.java` | OSS 文件管理服务实现 |
|
|
46
|
-
| `SysOssController` | `ruoyi-system/.../controller/system/SysOssController.java` | OSS 文件上传下载接口 |
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 🔴 基础使用(本项目规范)
|
|
51
|
-
|
|
52
|
-
### 获取 OssClient
|
|
53
|
-
|
|
54
|
-
> **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java`
|
|
55
|
-
|
|
56
|
-
> **重要**:OssFactory 只有 2 个方法,配置键是字符串(如 "aliyun", "minio"),不是枚举。
|
|
32
|
+
## 一、获取 OssClient
|
|
57
33
|
|
|
58
34
|
```java
|
|
59
35
|
import org.dromara.common.oss.factory.OssFactory;
|
|
60
36
|
import org.dromara.common.oss.core.OssClient;
|
|
61
37
|
|
|
62
|
-
|
|
63
|
-
OssClient client = OssFactory.instance();
|
|
64
|
-
|
|
65
|
-
// ✅ 根据配置键获取(配置键是字符串)
|
|
66
|
-
OssClient client = OssFactory.instance("aliyun");
|
|
38
|
+
OssClient client = OssFactory.instance(); // 默认配置
|
|
39
|
+
OssClient client = OssFactory.instance("aliyun"); // 指定配置(字符串,非枚举)
|
|
67
40
|
OssClient client = OssFactory.instance("minio");
|
|
68
41
|
|
|
69
|
-
// ❌
|
|
42
|
+
// ❌ 不存在 OssType 枚举参数
|
|
70
43
|
OssClient client = OssFactory.instance(OssType.ALIYUN); // 编译错误!
|
|
71
44
|
```
|
|
72
45
|
|
|
73
|
-
|
|
74
|
-
- 使用 `ConcurrentHashMap` 缓存 OssClient 实例
|
|
75
|
-
- 使用 `ReentrantLock` 双检锁模式确保线程安全
|
|
76
|
-
- 支持多租户隔离(对每个租户的 configKey 分别缓存)
|
|
77
|
-
- 配置信息从 Redis 中读取(`CacheNames.SYS_OSS_CONFIG`)
|
|
46
|
+
> 内部使用 ConcurrentHashMap + ReentrantLock 双检锁缓存实例,支持多租户隔离。
|
|
78
47
|
|
|
79
48
|
---
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
> **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java:140-227`
|
|
50
|
+
## 二、文件上传
|
|
84
51
|
|
|
85
52
|
```java
|
|
86
53
|
import org.dromara.common.oss.entity.UploadResult;
|
|
87
|
-
import java.nio.file.Path;
|
|
88
54
|
|
|
89
|
-
//
|
|
90
|
-
byte[] data = multipartFile.getBytes();
|
|
55
|
+
// 1. 上传字节数组,自动生成路径(推荐)
|
|
91
56
|
UploadResult result = client.uploadSuffix(data, ".jpg", "image/jpeg");
|
|
92
|
-
// 结果:prefix/2024/12/01/uuid.jpg
|
|
93
57
|
|
|
94
|
-
//
|
|
95
|
-
InputStream is = multipartFile.getInputStream();
|
|
96
|
-
Long fileSize = multipartFile.getSize();
|
|
58
|
+
// 2. 上传输入流,自动生成路径
|
|
97
59
|
UploadResult result = client.uploadSuffix(is, ".jpg", fileSize, "image/jpeg");
|
|
98
60
|
|
|
99
|
-
//
|
|
100
|
-
File file = new File("/path/to/file.jpg");
|
|
61
|
+
// 3. 上传 File 对象,自动生成路径
|
|
101
62
|
UploadResult result = client.uploadSuffix(file, ".jpg");
|
|
102
63
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
UploadResult result = client.upload(filePath, "avatar/user123.jpg", null, "image/jpeg");
|
|
64
|
+
// 4. 上传到指定路径
|
|
65
|
+
UploadResult result = client.upload(file.toPath(), "avatar/user123.jpg", null, "image/jpeg");
|
|
106
66
|
|
|
107
|
-
//
|
|
67
|
+
// 5. 上传流到指定路径
|
|
108
68
|
UploadResult result = client.upload(is, "images/photo.jpg", fileSize, "image/jpeg");
|
|
109
69
|
```
|
|
110
70
|
|
|
111
|
-
|
|
71
|
+
**方法签名:**
|
|
112
72
|
```java
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// 上传字节数组(带后缀自动拼接路径)
|
|
120
|
-
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType)
|
|
121
|
-
|
|
122
|
-
// 上传流(带后缀自动拼接路径)
|
|
123
|
-
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType)
|
|
124
|
-
|
|
125
|
-
// 上传文件对象(带后缀自动拼接路径)
|
|
126
|
-
public UploadResult uploadSuffix(File file, String suffix)
|
|
73
|
+
UploadResult upload(Path filePath, String key, String md5Digest, String contentType)
|
|
74
|
+
UploadResult upload(InputStream inputStream, String key, Long length, String contentType)
|
|
75
|
+
UploadResult uploadSuffix(byte[] data, String suffix, String contentType)
|
|
76
|
+
UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType)
|
|
77
|
+
UploadResult uploadSuffix(File file, String suffix)
|
|
127
78
|
```
|
|
128
79
|
|
|
129
80
|
---
|
|
130
81
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
> **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java`
|
|
82
|
+
## 三、UploadResult 字段
|
|
134
83
|
|
|
135
|
-
>
|
|
84
|
+
> 只有 3 个字段,使用 Lombok `@Builder`。
|
|
136
85
|
|
|
137
86
|
| 字段 | 类型 | 说明 |
|
|
138
87
|
|------|------|------|
|
|
139
|
-
| `url` | String | 文件访问URL
|
|
140
|
-
| `filename` | String |
|
|
141
|
-
| `eTag` | String |
|
|
88
|
+
| `url` | String | 文件访问URL |
|
|
89
|
+
| `filename` | String | 文件名/对象键(**小写 n**) |
|
|
90
|
+
| `eTag` | String | 文件校验标记 |
|
|
142
91
|
|
|
143
92
|
```java
|
|
144
|
-
// ✅ 正确
|
|
145
93
|
String url = result.getUrl();
|
|
146
|
-
String filename = result.getFilename();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// ❌ 错误:不存在这些字段
|
|
150
|
-
String fileName = result.getFileName(); // 编译错误!
|
|
151
|
-
Long fileSize = result.getFileSize(); // 编译错误!
|
|
152
|
-
String contentType = result.getContentType(); // 编译错误!
|
|
94
|
+
String filename = result.getFilename(); // ✅ 小写 'n'
|
|
95
|
+
// ❌ result.getFileName() / result.getFileSize() / result.getContentType() 不存在
|
|
153
96
|
```
|
|
154
97
|
|
|
155
98
|
---
|
|
156
99
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
> **实际代码位置**:`OssClient.java:236-319`
|
|
100
|
+
## 四、文件下载
|
|
160
101
|
|
|
161
102
|
```java
|
|
162
|
-
|
|
163
|
-
import java.io.OutputStream;
|
|
164
|
-
|
|
165
|
-
// ✅ 1. 下载到临时文件
|
|
103
|
+
// 下载到临时文件
|
|
166
104
|
Path tempFile = client.fileDownload("images/photo.jpg");
|
|
167
|
-
// 返回临时文件路径
|
|
168
105
|
|
|
169
|
-
//
|
|
170
|
-
OutputStream out = response.getOutputStream();
|
|
106
|
+
// 下载到输出流(推荐用于HTTP响应)
|
|
171
107
|
client.download("images/photo.jpg", out, contentLength -> {
|
|
172
|
-
// 可选的文件大小回调(用于设置响应头)
|
|
173
108
|
response.setContentLengthLong(contentLength);
|
|
174
109
|
});
|
|
175
110
|
|
|
176
|
-
//
|
|
111
|
+
// 获取文件输入流(内部创建临时文件,使用后自动删除)
|
|
177
112
|
InputStream is = client.getObjectContent("images/photo.jpg");
|
|
178
|
-
// 注意:此方法会创建临时文件,使用后会自动删除
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**方法签名(已验证)**:
|
|
182
|
-
```java
|
|
183
|
-
// 下载文件到临时目录
|
|
184
|
-
public Path fileDownload(String path)
|
|
185
|
-
|
|
186
|
-
// 下载文件到输出流(含文件大小回调)
|
|
187
|
-
public void download(String key, OutputStream out, Consumer<Long> consumer)
|
|
188
|
-
|
|
189
|
-
// 获取文件输入流
|
|
190
|
-
public InputStream getObjectContent(String path) throws IOException
|
|
191
113
|
```
|
|
192
114
|
|
|
193
115
|
---
|
|
194
116
|
|
|
195
|
-
##
|
|
196
|
-
|
|
197
|
-
> **实际代码位置**:`OssClient.java`
|
|
117
|
+
## 五、文件删除与预签名URL
|
|
198
118
|
|
|
199
119
|
```java
|
|
200
|
-
//
|
|
120
|
+
// 删除
|
|
201
121
|
client.delete("images/photo.jpg");
|
|
122
|
+
// ❌ client.copyFile() / client.getFileMetadata() / client.listFiles() 不存在
|
|
202
123
|
|
|
203
|
-
//
|
|
204
|
-
client.
|
|
205
|
-
client.getFileMetadata(...); // 不存在
|
|
206
|
-
client.listFiles(...); // 不存在
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## 预签名URL
|
|
212
|
-
|
|
213
|
-
> **实际代码位置**:`OssClient.java:343-375`
|
|
214
|
-
|
|
215
|
-
### 下载预签名URL
|
|
216
|
-
|
|
217
|
-
```java
|
|
218
|
-
import java.time.Duration;
|
|
219
|
-
|
|
220
|
-
// ✅ 生成60分钟有效的预签名下载URL
|
|
221
|
-
String presignedUrl = client.createPresignedGetUrl("images/photo.jpg", Duration.ofMinutes(60));
|
|
222
|
-
|
|
223
|
-
// ❌ 以下方法不存在
|
|
224
|
-
String url = client.generatePresignedUrl(...); // 不存在
|
|
225
|
-
String url = client.generatePublicUrl(...); // 不存在
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 上传预签名URL(前端直传)
|
|
229
|
-
|
|
230
|
-
```java
|
|
231
|
-
import java.util.Map;
|
|
232
|
-
|
|
233
|
-
// ✅ 生成预签名上传URL
|
|
234
|
-
Map<String, String> metadata = Map.of("user-id", "123");
|
|
235
|
-
String uploadUrl = client.createPresignedPutUrl(
|
|
236
|
-
"images/upload.jpg", // 对象键
|
|
237
|
-
Duration.ofHours(1), // 有效期
|
|
238
|
-
metadata // 元数据(可为 null)
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
// 前端使用此 URL 直接 PUT 上传
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**方法签名(已验证)**:
|
|
245
|
-
```java
|
|
246
|
-
// 创建下载预签名URL(GET请求)
|
|
247
|
-
public String createPresignedGetUrl(String objectKey, Duration expiredTime)
|
|
124
|
+
// 下载预签名URL
|
|
125
|
+
String url = client.createPresignedGetUrl("images/photo.jpg", Duration.ofMinutes(60));
|
|
248
126
|
|
|
249
|
-
//
|
|
250
|
-
|
|
127
|
+
// 上传预签名URL(前端直传)
|
|
128
|
+
String url = client.createPresignedPutUrl("images/upload.jpg", Duration.ofHours(1), metadata);
|
|
129
|
+
// ❌ client.generatePresignedUrl() / client.generatePublicUrl() 不存在
|
|
251
130
|
```
|
|
252
131
|
|
|
253
132
|
---
|
|
254
133
|
|
|
255
|
-
## OssClient
|
|
256
|
-
|
|
257
|
-
> **实际代码位置**:`OssClient.java`
|
|
134
|
+
## 六、OssClient 工具方法
|
|
258
135
|
|
|
259
136
|
```java
|
|
260
|
-
//
|
|
261
|
-
String
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// 获取服务商配置键
|
|
270
|
-
String configKey = client.getConfigKey();
|
|
271
|
-
|
|
272
|
-
// 获取桶权限类型(PUBLIC 或 PRIVATE)
|
|
273
|
-
AccessPolicyType accessPolicy = client.getAccessPolicy();
|
|
274
|
-
|
|
275
|
-
// 生成对象键路径(prefix + dateTime + uuid + suffix)
|
|
276
|
-
String path = client.getPath("", ".jpg");
|
|
277
|
-
|
|
278
|
-
// 移除基础URL获取相对路径
|
|
279
|
-
String relativePath = client.removeBaseUrl(fullUrl);
|
|
280
|
-
|
|
281
|
-
// 检查配置是否相同
|
|
282
|
-
boolean isSame = client.checkPropertiesSame(newProperties);
|
|
137
|
+
String baseUrl = client.getUrl(); // 基础URL
|
|
138
|
+
String endpoint = client.getEndpoint(); // 终端点URL
|
|
139
|
+
String domain = client.getDomain(); // 自定义域名
|
|
140
|
+
String configKey = client.getConfigKey(); // 配置键
|
|
141
|
+
AccessPolicyType policy = client.getAccessPolicy(); // 桶权限(PUBLIC/PRIVATE)
|
|
142
|
+
String path = client.getPath("", ".jpg"); // 生成对象键路径
|
|
143
|
+
String relative = client.removeBaseUrl(fullUrl); // 获取相对路径
|
|
144
|
+
boolean same = client.checkPropertiesSame(props); // 配置是否相同
|
|
283
145
|
```
|
|
284
146
|
|
|
285
147
|
---
|
|
286
148
|
|
|
287
|
-
## Controller
|
|
288
|
-
|
|
289
|
-
> **实际代码位置**:`ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java`
|
|
290
|
-
|
|
291
|
-
### API 接口清单
|
|
149
|
+
## 七、Controller 接口(SysOssController)
|
|
292
150
|
|
|
293
|
-
| 操作 | HTTP
|
|
294
|
-
|
|
151
|
+
| 操作 | HTTP | 路径 | 权限 |
|
|
152
|
+
|------|------|------|------|
|
|
295
153
|
| 查询列表 | GET | `/resource/oss/list` | `system:oss:list` |
|
|
296
154
|
| 批量查询 | GET | `/resource/oss/listByIds/{ossIds}` | `system:oss:query` |
|
|
297
155
|
| 上传文件 | POST | `/resource/oss/upload` | `system:oss:upload` |
|
|
298
156
|
| 下载文件 | GET | `/resource/oss/download/{ossId}` | `system:oss:download` |
|
|
299
157
|
| 删除文件 | DELETE | `/resource/oss/{ossIds}` | `system:oss:remove` |
|
|
300
158
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
@
|
|
305
|
-
@
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
* 上传文件
|
|
313
|
-
*
|
|
314
|
-
* 🔴 注意:
|
|
315
|
-
* 1. 必须使用 @RequestPart("file")
|
|
316
|
-
* 2. 必须指定 consumes = MediaType.MULTIPART_FORM_DATA_VALUE
|
|
317
|
-
* 3. 参数名必须是 "file"(前端约定)
|
|
318
|
-
*/
|
|
319
|
-
@SaCheckPermission("system:oss:upload")
|
|
320
|
-
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
|
|
321
|
-
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
322
|
-
public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
|
|
323
|
-
// 调用 Service 上传(会保存到数据库)
|
|
324
|
-
SysOssVo oss = ossService.upload(file);
|
|
325
|
-
|
|
326
|
-
// 构建返回对象
|
|
327
|
-
SysOssUploadVo uploadVo = new SysOssUploadVo();
|
|
328
|
-
uploadVo.setUrl(oss.getUrl());
|
|
329
|
-
uploadVo.setFileName(oss.getOriginalName());
|
|
330
|
-
uploadVo.setOssId(oss.getOssId().toString());
|
|
331
|
-
|
|
332
|
-
return R.ok(uploadVo);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 下载文件
|
|
337
|
-
*/
|
|
338
|
-
@SaCheckPermission("system:oss:download")
|
|
339
|
-
@GetMapping("/download/{ossId}")
|
|
340
|
-
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
|
|
341
|
-
ossService.download(ossId, response);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* 删除文件
|
|
346
|
-
*/
|
|
347
|
-
@SaCheckPermission("system:oss:remove")
|
|
348
|
-
@Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
|
|
349
|
-
@DeleteMapping("/{ossIds}")
|
|
350
|
-
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ossIds) {
|
|
351
|
-
return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true));
|
|
352
|
-
}
|
|
159
|
+
**上传接口规范:**
|
|
160
|
+
```java
|
|
161
|
+
// 必须使用 @RequestPart("file"),必须指定 consumes
|
|
162
|
+
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
163
|
+
public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
|
|
164
|
+
SysOssVo oss = ossService.upload(file);
|
|
165
|
+
SysOssUploadVo uploadVo = new SysOssUploadVo();
|
|
166
|
+
uploadVo.setUrl(oss.getUrl());
|
|
167
|
+
uploadVo.setFileName(oss.getOriginalName());
|
|
168
|
+
uploadVo.setOssId(oss.getOssId().toString());
|
|
169
|
+
return R.ok(uploadVo);
|
|
353
170
|
}
|
|
354
171
|
```
|
|
355
172
|
|
|
356
|
-
|
|
357
|
-
```java
|
|
358
|
-
@Data
|
|
359
|
-
public class SysOssUploadVo {
|
|
360
|
-
private String url; // 文件URL
|
|
361
|
-
private String fileName; // 原始文件名
|
|
362
|
-
private String ossId; // OSS对象ID(String类型)
|
|
363
|
-
}
|
|
364
|
-
```
|
|
173
|
+
**SysOssUploadVo**:`url`(String) / `fileName`(String) / `ossId`(String)
|
|
365
174
|
|
|
366
175
|
---
|
|
367
176
|
|
|
368
|
-
## Service
|
|
369
|
-
|
|
370
|
-
> **实际代码位置**:`ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java`
|
|
371
|
-
|
|
372
|
-
### 服务接口方法(ISysOssService)
|
|
177
|
+
## 八、Service 层接口(ISysOssService)
|
|
373
178
|
|
|
374
179
|
```java
|
|
375
|
-
// 分页查询OSS对象
|
|
376
180
|
TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
|
|
377
|
-
|
|
378
|
-
// 根据ossId列表查询
|
|
379
181
|
List<SysOssVo> listByIds(Collection<Long> ossIds);
|
|
380
|
-
|
|
381
|
-
// 单个ID查询(带缓存)
|
|
382
182
|
@Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
|
|
383
183
|
SysOssVo getById(Long ossId);
|
|
384
|
-
|
|
385
|
-
// MultipartFile上传
|
|
386
184
|
SysOssVo upload(MultipartFile file);
|
|
387
|
-
|
|
388
|
-
// File对象上传
|
|
389
185
|
SysOssVo upload(File file);
|
|
390
|
-
|
|
391
|
-
// 文件下载
|
|
392
186
|
void download(Long ossId, HttpServletResponse response) throws IOException;
|
|
393
|
-
|
|
394
|
-
// 删除文件
|
|
395
187
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
|
396
188
|
```
|
|
397
189
|
|
|
398
|
-
|
|
190
|
+
> 推荐通过 `ISysOssService.upload()` 上传,会自动保存数据库记录。
|
|
191
|
+
> 直接使用 `OssClient` 上传不会有数据库记录。
|
|
399
192
|
|
|
400
|
-
|
|
401
|
-
@RequiredArgsConstructor
|
|
402
|
-
@Service
|
|
403
|
-
public class SysOssServiceImpl implements ISysOssService {
|
|
404
|
-
|
|
405
|
-
private final SysOssMapper baseMapper;
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* 上传文件(保存到 OSS 并记录到数据库)
|
|
409
|
-
*
|
|
410
|
-
* 🔴 重要:
|
|
411
|
-
* 1. 使用 new ServiceException() 抛出异常
|
|
412
|
-
* 2. UploadResult.getFilename() 是小写 'n'
|
|
413
|
-
* 3. 扩展信息存储在 ext1 字段(JSON格式)
|
|
414
|
-
*/
|
|
415
|
-
@Override
|
|
416
|
-
public SysOssVo upload(MultipartFile file) {
|
|
417
|
-
if (ObjectUtil.isNull(file) || file.isEmpty()) {
|
|
418
|
-
throw new ServiceException("上传文件不能为空");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// 1. 提取文件后缀
|
|
422
|
-
String originalfileName = file.getOriginalFilename();
|
|
423
|
-
String suffix = StringUtils.substring(originalfileName,
|
|
424
|
-
originalfileName.lastIndexOf("."),
|
|
425
|
-
originalfileName.length());
|
|
426
|
-
|
|
427
|
-
// 2. 获取 OSS 客户端(默认配置)
|
|
428
|
-
OssClient storage = OssFactory.instance();
|
|
429
|
-
|
|
430
|
-
// 3. 上传文件
|
|
431
|
-
UploadResult uploadResult;
|
|
432
|
-
try {
|
|
433
|
-
uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
|
|
434
|
-
} catch (IOException e) {
|
|
435
|
-
throw new ServiceException(e.getMessage());
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// 4. 构建扩展信息
|
|
439
|
-
SysOssExt ext1 = new SysOssExt();
|
|
440
|
-
ext1.setFileSize(file.getSize());
|
|
441
|
-
ext1.setContentType(file.getContentType());
|
|
442
|
-
|
|
443
|
-
// 5. 保存到数据库
|
|
444
|
-
return buildResultEntity(originalfileName, suffix,
|
|
445
|
-
storage.getConfigKey(), uploadResult, ext1);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* 构建结果实体并保存
|
|
450
|
-
*/
|
|
451
|
-
private SysOssVo buildResultEntity(String originalfileName, String suffix,
|
|
452
|
-
String configKey, UploadResult uploadResult, SysOssExt ext1) {
|
|
453
|
-
|
|
454
|
-
SysOss oss = new SysOss();
|
|
455
|
-
oss.setUrl(uploadResult.getUrl());
|
|
456
|
-
oss.setFileSuffix(suffix);
|
|
457
|
-
oss.setFileName(uploadResult.getFilename()); // 对象键(小写 'n')
|
|
458
|
-
oss.setOriginalName(originalfileName); // 原始文件名
|
|
459
|
-
oss.setService(configKey); // 服务商标识
|
|
460
|
-
oss.setExt1(JsonUtils.toJsonString(ext1)); // 扩展信息(JSON)
|
|
461
|
-
|
|
462
|
-
baseMapper.insert(oss);
|
|
463
|
-
SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
|
|
464
|
-
return this.matchingUrl(sysOssVo);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* 文件下载
|
|
469
|
-
*/
|
|
470
|
-
@Override
|
|
471
|
-
public void download(Long ossId, HttpServletResponse response) throws IOException {
|
|
472
|
-
SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
|
|
473
|
-
if (ObjectUtil.isNull(sysOss)) {
|
|
474
|
-
throw new ServiceException("文件数据不存在!");
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// 设置响应头
|
|
478
|
-
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
|
|
479
|
-
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
|
|
480
|
-
|
|
481
|
-
// 下载文件
|
|
482
|
-
OssClient storage = OssFactory.instance(sysOss.getService());
|
|
483
|
-
storage.download(sysOss.getFileName(),
|
|
484
|
-
response.getOutputStream(),
|
|
485
|
-
response::setContentLengthLong); // 设置响应头Content-Length
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* 删除文件
|
|
490
|
-
*/
|
|
491
|
-
@Override
|
|
492
|
-
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
|
493
|
-
if (isValid) {
|
|
494
|
-
// 做一些业务上的校验,判断是否需要校验
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
List<SysOss> list = baseMapper.selectByIds(ids);
|
|
498
|
-
for (SysOss sysOss : list) {
|
|
499
|
-
OssClient storage = OssFactory.instance(sysOss.getService());
|
|
500
|
-
storage.delete(sysOss.getUrl()); // 删除OSS中的文件
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return baseMapper.deleteByIds(ids) > 0; // 删除数据库记录
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* 为私有文件生成预签名 URL
|
|
508
|
-
*
|
|
509
|
-
* 🔴 重要:私有桶的文件需要生成临时访问 URL(120秒有效)
|
|
510
|
-
*/
|
|
511
|
-
private SysOssVo matchingUrl(SysOssVo oss) {
|
|
512
|
-
OssClient storage = OssFactory.instance(oss.getService());
|
|
513
|
-
// 仅修改桶类型为 private 的URL,临时URL时长为120s
|
|
514
|
-
if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
|
|
515
|
-
oss.setUrl(storage.createPresignedGetUrl(oss.getFileName(), Duration.ofSeconds(120)));
|
|
516
|
-
}
|
|
517
|
-
return oss;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
```
|
|
193
|
+
> 完整 Service 实现代码详见 [references/service-impl.md](references/service-impl.md)
|
|
521
194
|
|
|
522
195
|
---
|
|
523
196
|
|
|
524
|
-
##
|
|
525
|
-
|
|
526
|
-
### SysOss 实体
|
|
527
|
-
|
|
528
|
-
```java
|
|
529
|
-
@Data
|
|
530
|
-
@EqualsAndHashCode(callSuper = true)
|
|
531
|
-
@TableName("sys_oss")
|
|
532
|
-
public class SysOss extends TenantEntity {
|
|
533
|
-
|
|
534
|
-
@TableId(value = "oss_id")
|
|
535
|
-
private Long ossId; // 对象存储主键
|
|
536
|
-
|
|
537
|
-
private String fileName; // 文件名(OSS对象键)
|
|
538
|
-
private String originalName; // 原始文件名
|
|
539
|
-
private String fileSuffix; // 文件后缀名(如 .jpg)
|
|
540
|
-
private String url; // URL地址
|
|
541
|
-
private String ext1; // 扩展字段(JSON格式)
|
|
542
|
-
private String service; // 服务商标识
|
|
543
|
-
|
|
544
|
-
// 继承自 TenantEntity 的字段
|
|
545
|
-
// tenantId, createTime, createBy, updateTime, updateBy, delFlag 等
|
|
546
|
-
}
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
### SysOssVo 视图对象
|
|
197
|
+
## 九、数据库实体
|
|
550
198
|
|
|
551
|
-
|
|
552
|
-
@Data
|
|
553
|
-
@AutoMapper(target = SysOss.class)
|
|
554
|
-
public class SysOssVo implements Serializable {
|
|
555
|
-
|
|
556
|
-
private Long ossId;
|
|
557
|
-
private String fileName;
|
|
558
|
-
private String originalName;
|
|
559
|
-
private String fileSuffix;
|
|
560
|
-
private String url;
|
|
561
|
-
private String ext1;
|
|
562
|
-
private Date createTime;
|
|
563
|
-
private Long createBy;
|
|
564
|
-
|
|
565
|
-
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
|
|
566
|
-
private String createByName; // 上传人名称(自动翻译)
|
|
567
|
-
|
|
568
|
-
private String service;
|
|
569
|
-
}
|
|
570
|
-
```
|
|
199
|
+
> 完整实体/VO/BO 定义详见 [references/entities.md](references/entities.md)
|
|
571
200
|
|
|
572
|
-
|
|
201
|
+
**SysOss 关键字段:**
|
|
573
202
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
private String fileSuffix;
|
|
584
|
-
private String url;
|
|
585
|
-
private String ext1;
|
|
586
|
-
private String service;
|
|
587
|
-
|
|
588
|
-
// 继承自 BaseEntity 的 params 字段(时间范围查询用)
|
|
589
|
-
}
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### SysOssExt 扩展信息对象
|
|
593
|
-
|
|
594
|
-
> **存储在 ext1 字段中**:以 JSON 字符串形式存储
|
|
595
|
-
|
|
596
|
-
```java
|
|
597
|
-
@Data
|
|
598
|
-
public class SysOssExt implements Serializable {
|
|
599
|
-
|
|
600
|
-
private String bizType; // 业务类型(avatar、report等)
|
|
601
|
-
private Long fileSize; // 文件大小(字节)
|
|
602
|
-
private String contentType; // MIME类型
|
|
603
|
-
private String source; // 来源标识
|
|
604
|
-
private String uploadIp; // 上传IP
|
|
605
|
-
private String remark; // 备注
|
|
606
|
-
private List<String> tags; // 标签
|
|
607
|
-
private String refId; // 绑定业务ID
|
|
608
|
-
private String refType; // 绑定业务类型
|
|
609
|
-
private Boolean isTemp; // 是否临时文件
|
|
610
|
-
private String md5; // 文件MD5值
|
|
611
|
-
}
|
|
612
|
-
```
|
|
203
|
+
| 字段 | 说明 |
|
|
204
|
+
|------|------|
|
|
205
|
+
| `ossId` | 主键 |
|
|
206
|
+
| `fileName` | OSS对象键 |
|
|
207
|
+
| `originalName` | 原始文件名 |
|
|
208
|
+
| `fileSuffix` | 后缀名 |
|
|
209
|
+
| `url` | 访问URL |
|
|
210
|
+
| `ext1` | 扩展字段(JSON,存储 SysOssExt) |
|
|
211
|
+
| `service` | 服务商标识 |
|
|
613
212
|
|
|
614
213
|
---
|
|
615
214
|
|
|
616
|
-
##
|
|
617
|
-
|
|
618
|
-
### 数据库配置表(sys_oss_config)
|
|
619
|
-
|
|
620
|
-
> **配置存储位置**:Redis(`CacheNames.SYS_OSS_CONFIG`)
|
|
215
|
+
## 十、配置(sys_oss_config 表)
|
|
621
216
|
|
|
622
217
|
| 字段 | 说明 |
|
|
623
218
|
|------|------|
|
|
624
|
-
| `config_key` |
|
|
625
|
-
| `access_key` |
|
|
626
|
-
| `
|
|
627
|
-
| `bucket_name` | 存储桶名称 |
|
|
219
|
+
| `config_key` | 配置标识(aliyun、minio等) |
|
|
220
|
+
| `access_key` / `secret_key` | 认证信息 |
|
|
221
|
+
| `bucket_name` | 存储桶 |
|
|
628
222
|
| `prefix` | 路径前缀 |
|
|
629
223
|
| `endpoint` | 服务端点 |
|
|
630
224
|
| `domain` | 自定义域名 |
|
|
631
225
|
| `is_https` | 是否HTTPS(Y/N) |
|
|
632
226
|
| `region` | 区域 |
|
|
633
|
-
| `access_policy` |
|
|
634
|
-
| `status` | 启用状态 |
|
|
635
|
-
|
|
636
|
-
### OssProperties 配置类
|
|
637
|
-
|
|
638
|
-
```java
|
|
639
|
-
@Data
|
|
640
|
-
public class OssProperties {
|
|
641
|
-
|
|
642
|
-
private String tenantId; // 租户ID
|
|
643
|
-
private String endpoint; // 访问站点(endpoint)
|
|
644
|
-
private String domain; // 自定义域名
|
|
645
|
-
private String prefix; // 文件前缀(对象键前缀)
|
|
646
|
-
private String accessKey; // ACCESS_KEY
|
|
647
|
-
private String secretKey; // SECRET_KEY
|
|
648
|
-
private String bucketName; // 存储空间名
|
|
649
|
-
private String region; // 存储区域
|
|
650
|
-
private String isHttps; // 是否HTTPS(Y/N)
|
|
651
|
-
private String accessPolicy; // 桶权限类型(0-private, 1-public, 2-custom)
|
|
652
|
-
}
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### 访问策略
|
|
656
|
-
|
|
657
|
-
| 策略 | 说明 |
|
|
658
|
-
|------|------|
|
|
659
|
-
| `PRIVATE` | 私有访问("0"),需要预签名URL(matchingUrl 自动处理) |
|
|
660
|
-
| `PUBLIC` | 公开访问("1"),URL 直接可访问 |
|
|
661
|
-
| `CUSTOM` | 自定义访问("2"),公开读取 |
|
|
662
|
-
|
|
663
|
-
---
|
|
664
|
-
|
|
665
|
-
## 最佳实践
|
|
666
|
-
|
|
667
|
-
### 1. 文件路径规范
|
|
668
|
-
|
|
669
|
-
```java
|
|
670
|
-
// ✅ 推荐:使用 uploadSuffix 自动生成路径
|
|
671
|
-
OssClient client = OssFactory.instance();
|
|
672
|
-
UploadResult result = client.uploadSuffix(file.getBytes(), ".jpg", contentType);
|
|
673
|
-
// 结果:prefix/2024/12/01/550e8400-e29b.jpg
|
|
674
|
-
|
|
675
|
-
// ❌ 避免:手动拼接路径容易出错
|
|
676
|
-
UploadResult result = client.upload(file.toPath(), "avatar/" + fileName, null, contentType);
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
### 2. 文件上传后保存数据库
|
|
227
|
+
| `access_policy` | 0-private, 1-public, 2-custom |
|
|
680
228
|
|
|
681
|
-
|
|
682
|
-
// ✅ 推荐:使用 SysOssService 上传(自动保存到数据库)
|
|
683
|
-
@Autowired
|
|
684
|
-
private ISysOssService ossService;
|
|
685
|
-
|
|
686
|
-
SysOssVo ossVo = ossService.upload(multipartFile);
|
|
687
|
-
Long ossId = ossVo.getOssId(); // 保存 ossId 到业务表
|
|
688
|
-
|
|
689
|
-
// ❌ 不推荐:直接使用 OssClient(无数据库记录,难以管理)
|
|
690
|
-
OssClient client = OssFactory.instance();
|
|
691
|
-
UploadResult result = client.uploadSuffix(file.getBytes(), ".jpg", contentType);
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
### 3. 文件类型校验
|
|
695
|
-
|
|
696
|
-
```java
|
|
697
|
-
// 限制允许的文件类型
|
|
698
|
-
private static final Set<String> ALLOWED_TYPES = Set.of(
|
|
699
|
-
"image/jpeg", "image/png", "image/gif", "application/pdf"
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
public SysOssVo upload(MultipartFile file) {
|
|
703
|
-
if (!ALLOWED_TYPES.contains(file.getContentType())) {
|
|
704
|
-
throw new ServiceException("不支持的文件类型");
|
|
705
|
-
}
|
|
706
|
-
// 继续上传...
|
|
707
|
-
}
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### 4. 文件大小限制
|
|
711
|
-
|
|
712
|
-
```yaml
|
|
713
|
-
# application.yml
|
|
714
|
-
spring:
|
|
715
|
-
servlet:
|
|
716
|
-
multipart:
|
|
717
|
-
max-file-size: 10MB
|
|
718
|
-
max-request-size: 20MB
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### 5. 异常处理方式
|
|
722
|
-
|
|
723
|
-
```java
|
|
724
|
-
// ✅ 正确:使用 new ServiceException()
|
|
725
|
-
if (file.isEmpty()) {
|
|
726
|
-
throw new ServiceException("上传文件不能为空");
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// ❌ 错误:不存在 of() 静态方法
|
|
730
|
-
throw ServiceException.of("上传文件不能为空"); // 编译错误!
|
|
731
|
-
```
|
|
229
|
+
> 配置从 Redis 读取(`CacheNames.SYS_OSS_CONFIG`),私有桶文件自动生成 120 秒预签名URL。
|
|
732
230
|
|
|
733
231
|
---
|
|
734
232
|
|
|
735
|
-
##
|
|
736
|
-
|
|
737
|
-
### ⚠️ UploadResult.getFilename() vs getFileName()
|
|
233
|
+
## 十一、快速对照表
|
|
738
234
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
UploadResult result = client.uploadSuffix(file.getBytes(), ".jpg", contentType);
|
|
742
|
-
String filename = result.getFilename(); // 正确
|
|
743
|
-
|
|
744
|
-
// ❌ 错误:不是 fileName(驼峰)
|
|
745
|
-
String fileName = result.getFileName(); // 编译错误!
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
### ⚠️ ServiceException 构造方式
|
|
749
|
-
|
|
750
|
-
```java
|
|
751
|
-
// ✅ 正确:使用 new ServiceException()
|
|
752
|
-
if (file.isEmpty()) {
|
|
753
|
-
throw new ServiceException("上传文件不能为空");
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// ❌ 错误:不存在 of() 静态方法
|
|
757
|
-
throw ServiceException.of("上传文件不能为空"); // 编译错误!
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
### 上传失败:Access Denied
|
|
761
|
-
|
|
762
|
-
- 检查 Access Key 和 Secret Key
|
|
763
|
-
- 检查存储桶权限配置
|
|
764
|
-
- 检查 IP 白名单
|
|
765
|
-
|
|
766
|
-
### 预签名URL无效
|
|
767
|
-
|
|
768
|
-
- 检查服务器时间是否同步
|
|
769
|
-
- 检查 URL 有效期是否过期
|
|
770
|
-
- 私有桶必须使用预签名URL(matchingUrl 自动处理)
|
|
771
|
-
|
|
772
|
-
### 跨域问题
|
|
773
|
-
|
|
774
|
-
配置存储桶 CORS 规则:
|
|
775
|
-
- AllowedOrigins: `*` 或具体域名
|
|
776
|
-
- AllowedMethods: `GET, PUT, POST, DELETE`
|
|
777
|
-
- AllowedHeaders: `*`
|
|
778
|
-
|
|
779
|
-
---
|
|
780
|
-
|
|
781
|
-
## 快速对照表
|
|
782
|
-
|
|
783
|
-
| ❌ 错误 | ✅ 正确 |
|
|
784
|
-
|--------|--------|
|
|
235
|
+
| 错误 | 正确 |
|
|
236
|
+
|------|------|
|
|
785
237
|
| `OssFactory.instance(OssType.ALIYUN)` | `OssFactory.instance("aliyun")` |
|
|
786
|
-
| `client.uploadFile(file, key, type)` | `client.upload(file.toPath(), key, null, type)` |
|
|
787
|
-
| `client.uploadStream(is, key, size, type)` | `client.upload(is, key, size, type)` |
|
|
788
238
|
| `result.getFileName()` | `result.getFilename()` |
|
|
789
|
-
| `result.getFileSize()` |
|
|
239
|
+
| `result.getFileSize()` | 不存在 |
|
|
790
240
|
| `client.downloadToTempFile(path)` | `client.fileDownload(path)` |
|
|
791
|
-
| `client.generatePresignedUrl(
|
|
241
|
+
| `client.generatePresignedUrl(...)` | `client.createPresignedGetUrl(...)` |
|
|
792
242
|
| `throw ServiceException.of("msg")` | `throw new ServiceException("msg")` |
|
|
793
|
-
| `client.copyFile
|
|
794
|
-
| `client.listFiles(...)` | 不存在此方法 |
|
|
795
|
-
|
|
796
|
-
---
|
|
797
|
-
|
|
798
|
-
## 核心工作流程
|
|
799
|
-
|
|
800
|
-
### 上传流程
|
|
801
|
-
|
|
802
|
-
```
|
|
803
|
-
1. Controller 接收 MultipartFile
|
|
804
|
-
2. Service 验证文件非空
|
|
805
|
-
3. 从文件名提取后缀
|
|
806
|
-
4. 获取默认 OssClient(从 Redis 读取配置)
|
|
807
|
-
5. 上传到 OSS(自动生成对象键:prefix/date/uuid + suffix)
|
|
808
|
-
6. 保存文件信息和扩展信息到数据库(ext1字段存JSON)
|
|
809
|
-
7. 若是私有桶,生成预签名URL(120秒有效)
|
|
810
|
-
8. 返回上传结果
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
### 下载流程
|
|
814
|
-
|
|
815
|
-
```
|
|
816
|
-
1. Controller 接收 ossId
|
|
817
|
-
2. Service 从数据库查询文件信息(带缓存)
|
|
818
|
-
3. 检查私有桶,若是则生成预签名URL
|
|
819
|
-
4. 获取对应的 OssClient
|
|
820
|
-
5. 下载文件到输出流
|
|
821
|
-
6. 设置响应头(Content-Disposition、Content-Type、Content-Length)
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
### 删除流程
|
|
825
|
-
|
|
826
|
-
```
|
|
827
|
-
1. 查询数据库获取文件信息
|
|
828
|
-
2. 获取对应的 OssClient
|
|
829
|
-
3. 删除 OSS 中的文件
|
|
830
|
-
4. 删除数据库记录
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
---
|
|
834
|
-
|
|
835
|
-
## 架构特性
|
|
836
|
-
|
|
837
|
-
1. **客户端缓存**:OssFactory 使用 ConcurrentHashMap 缓存 OssClient,支持多租户隔离
|
|
838
|
-
|
|
839
|
-
2. **配置来源**:从 Redis 中读取(`CacheNames.SYS_OSS_CONFIG`),配置为 JSON 格式
|
|
840
|
-
|
|
841
|
-
3. **默认配置键**:从 Redis key `sys_oss:default_config` 获取
|
|
842
|
-
|
|
843
|
-
4. **S3 协议支持**:支持所有兼容 S3 协议的云服务(阿里云、腾讯云、七牛云、MinIO等)
|
|
844
|
-
|
|
845
|
-
5. **异步传输**:基于 AWS SDK v2 的异步客户端和 S3TransferManager
|
|
846
|
-
|
|
847
|
-
6. **路径风格**:MinIO 使用路径风格访问,云服务商使用虚拟主机风格
|
|
848
|
-
|
|
849
|
-
7. **私有桶处理**:私有桶的URL自动生成预签名URL(120秒过期)
|
|
850
|
-
|
|
851
|
-
8. **扩展字段**:使用 JSON 存储在 ext1 字段中,支持灵活扩展
|
|
243
|
+
| `client.copyFile/listFiles/getFileMetadata` | 不存在 |
|
|
852
244
|
|
|
853
245
|
---
|
|
854
246
|
|
|
855
|
-
##
|
|
247
|
+
## 核心文件位置
|
|
856
248
|
|
|
857
249
|
| 类型 | 位置 |
|
|
858
250
|
|------|------|
|
|
859
|
-
| OssFactory | `ruoyi-common/ruoyi-common-oss
|
|
860
|
-
| OssClient | `ruoyi-common/ruoyi-common-oss
|
|
861
|
-
| UploadResult | `ruoyi-common/ruoyi-common-oss
|
|
862
|
-
| SysOssController | `ruoyi-modules/ruoyi-system
|
|
863
|
-
| SysOssServiceImpl | `ruoyi-modules/ruoyi-system
|
|
251
|
+
| OssFactory | `ruoyi-common/ruoyi-common-oss/.../factory/OssFactory.java` |
|
|
252
|
+
| OssClient | `ruoyi-common/ruoyi-common-oss/.../core/OssClient.java` |
|
|
253
|
+
| UploadResult | `ruoyi-common/ruoyi-common-oss/.../entity/UploadResult.java` |
|
|
254
|
+
| SysOssController | `ruoyi-modules/ruoyi-system/.../controller/system/SysOssController.java` |
|
|
255
|
+
| SysOssServiceImpl | `ruoyi-modules/ruoyi-system/.../service/impl/SysOssServiceImpl.java` |
|