ai-engineering-init 1.3.3 → 1.4.0

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.
Files changed (228) hide show
  1. package/.claude/hooks/skill-forced-eval.js +4 -1
  2. package/.claude/settings.json +3 -3
  3. package/.claude/skills/add-skill/SKILL.md +252 -116
  4. package/.claude/skills/api-development/SKILL.md +83 -377
  5. package/.claude/skills/architecture-design/SKILL.md +138 -632
  6. package/.claude/skills/backend-annotations/SKILL.md +134 -506
  7. package/.claude/skills/banana-image/SKILL.md +10 -3
  8. package/.claude/skills/brainstorm/SKILL.md +103 -535
  9. package/.claude/skills/bug-detective/SKILL.md +147 -1097
  10. package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
  11. package/.claude/skills/code-patterns/SKILL.md +116 -426
  12. package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  13. package/.claude/skills/crud-development/SKILL.md +64 -304
  14. package/.claude/skills/data-permission/SKILL.md +105 -412
  15. package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
  16. package/.claude/skills/file-oss-management/SKILL.md +106 -714
  17. package/.claude/skills/file-oss-management/references/entities.md +105 -0
  18. package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
  19. package/.claude/skills/leniu-api-development/SKILL.md +142 -626
  20. package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
  21. package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
  22. package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
  23. package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
  24. package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  25. package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
  26. package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
  27. package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
  28. package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
  29. package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
  30. package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
  31. package/.claude/skills/leniu-java-export/SKILL.md +94 -379
  32. package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
  33. package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
  34. package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  35. package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
  36. package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  37. package/.claude/skills/leniu-report-customization/SKILL.md +111 -325
  38. package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
  39. package/.claude/skills/leniu-report-standard-customization/SKILL.md +328 -0
  40. package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  41. package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  42. package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
  43. package/.claude/skills/mysql-debug/SKILL.md +364 -0
  44. package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
  45. package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
  46. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  47. package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
  48. package/.claude/skills/openspec-explore/SKILL.md +10 -1
  49. package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
  50. package/.claude/skills/openspec-new-change/SKILL.md +9 -1
  51. package/.claude/skills/openspec-onboard/SKILL.md +15 -130
  52. package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
  53. package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
  54. package/.claude/skills/performance-doctor/SKILL.md +110 -434
  55. package/.claude/skills/redis-cache/SKILL.md +89 -595
  56. package/.claude/skills/redis-cache/references/listeners.md +23 -0
  57. package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
  58. package/.claude/skills/security-guard/SKILL.md +137 -532
  59. package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
  60. package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
  61. package/.claude/skills/sms-mail/SKILL.md +116 -574
  62. package/.claude/skills/sms-mail/references/mail-config.md +88 -0
  63. package/.claude/skills/sms-mail/references/sms-config.md +74 -0
  64. package/.claude/skills/social-login/SKILL.md +112 -514
  65. package/.claude/skills/social-login/references/provider-configs.md +118 -0
  66. package/.claude/skills/tenant-management/SKILL.md +129 -444
  67. package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
  68. package/.claude/skills/test-development/SKILL.md +86 -540
  69. package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
  70. package/.claude/skills/utils-toolkit/SKILL.md +52 -305
  71. package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  72. package/.claude/skills/websocket-sse/SKILL.md +105 -550
  73. package/.claude/skills/workflow-engine/SKILL.md +147 -502
  74. package/.codex/skills/add-skill/SKILL.md +252 -116
  75. package/.codex/skills/api-development/SKILL.md +172 -599
  76. package/.codex/skills/architecture-design/SKILL.md +138 -504
  77. package/.codex/skills/backend-annotations/SKILL.md +134 -496
  78. package/.codex/skills/banana-image/SKILL.md +10 -3
  79. package/.codex/skills/brainstorm/SKILL.md +103 -535
  80. package/.codex/skills/bug-detective/SKILL.md +147 -1097
  81. package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
  82. package/.codex/skills/code-patterns/SKILL.md +120 -282
  83. package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  84. package/.codex/skills/crud-development/SKILL.md +64 -292
  85. package/.codex/skills/data-permission/SKILL.md +108 -407
  86. package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
  87. package/.codex/skills/database-ops/SKILL.md +8 -154
  88. package/.codex/skills/error-handler/SKILL.md +10 -0
  89. package/.codex/skills/file-oss-management/SKILL.md +106 -714
  90. package/.codex/skills/file-oss-management/references/entities.md +105 -0
  91. package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
  92. package/.codex/skills/git-workflow/SKILL.md +27 -5
  93. package/.codex/skills/leniu-api-development/SKILL.md +142 -626
  94. package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
  95. package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
  96. package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
  97. package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
  98. package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  99. package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
  100. package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
  101. package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
  102. package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
  103. package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
  104. package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
  105. package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
  106. package/.codex/skills/leniu-java-export/SKILL.md +94 -379
  107. package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
  108. package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
  109. package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  110. package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
  111. package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  112. package/.codex/skills/leniu-report-customization/SKILL.md +111 -325
  113. package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
  114. package/.codex/skills/leniu-report-standard-customization/SKILL.md +328 -0
  115. package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  116. package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  117. package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
  118. package/.codex/skills/mysql-debug/SKILL.md +364 -0
  119. package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
  120. package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
  121. package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  122. package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
  123. package/.codex/skills/openspec-explore/SKILL.md +10 -1
  124. package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
  125. package/.codex/skills/openspec-new-change/SKILL.md +9 -1
  126. package/.codex/skills/openspec-onboard/SKILL.md +15 -130
  127. package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
  128. package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
  129. package/.codex/skills/performance-doctor/SKILL.md +110 -434
  130. package/.codex/skills/project-navigator/SKILL.md +20 -1
  131. package/.codex/skills/redis-cache/SKILL.md +93 -589
  132. package/.codex/skills/redis-cache/references/listeners.md +23 -0
  133. package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
  134. package/.codex/skills/security-guard/SKILL.md +141 -527
  135. package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
  136. package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
  137. package/.codex/skills/sms-mail/SKILL.md +116 -574
  138. package/.codex/skills/sms-mail/references/mail-config.md +88 -0
  139. package/.codex/skills/sms-mail/references/sms-config.md +74 -0
  140. package/.codex/skills/social-login/SKILL.md +112 -514
  141. package/.codex/skills/social-login/references/provider-configs.md +118 -0
  142. package/.codex/skills/store-pc/SKILL.md +258 -383
  143. package/.codex/skills/tenant-management/SKILL.md +129 -444
  144. package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
  145. package/.codex/skills/test-development/SKILL.md +86 -540
  146. package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
  147. package/.codex/skills/ui-pc/SKILL.md +350 -387
  148. package/.codex/skills/utils-toolkit/SKILL.md +52 -283
  149. package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  150. package/.codex/skills/websocket-sse/SKILL.md +105 -550
  151. package/.codex/skills/workflow-engine/SKILL.md +147 -502
  152. package/.cursor/hooks.json +3 -3
  153. package/.cursor/rules/skill-activation.mdc +2 -0
  154. package/.cursor/skills/add-skill/SKILL.md +252 -116
  155. package/.cursor/skills/api-development/SKILL.md +83 -377
  156. package/.cursor/skills/architecture-design/SKILL.md +138 -632
  157. package/.cursor/skills/backend-annotations/SKILL.md +134 -506
  158. package/.cursor/skills/banana-image/SKILL.md +10 -3
  159. package/.cursor/skills/brainstorm/SKILL.md +103 -535
  160. package/.cursor/skills/bug-detective/SKILL.md +147 -1097
  161. package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
  162. package/.cursor/skills/code-patterns/SKILL.md +116 -426
  163. package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  164. package/.cursor/skills/crud-development/SKILL.md +64 -304
  165. package/.cursor/skills/data-permission/SKILL.md +105 -412
  166. package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
  167. package/.cursor/skills/file-oss-management/SKILL.md +106 -714
  168. package/.cursor/skills/file-oss-management/references/entities.md +105 -0
  169. package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
  170. package/.cursor/skills/git-workflow/SKILL.md +27 -5
  171. package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
  172. package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
  173. package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
  174. package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
  175. package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
  176. package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  177. package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
  178. package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
  179. package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
  180. package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
  181. package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
  182. package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
  183. package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
  184. package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
  185. package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
  186. package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
  187. package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  188. package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
  189. package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  190. package/.cursor/skills/leniu-report-customization/SKILL.md +111 -325
  191. package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
  192. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +328 -0
  193. package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  194. package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  195. package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
  196. package/.cursor/skills/mysql-debug/SKILL.md +364 -0
  197. package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
  198. package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
  199. package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  200. package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
  201. package/.cursor/skills/openspec-explore/SKILL.md +10 -1
  202. package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
  203. package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
  204. package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
  205. package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
  206. package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
  207. package/.cursor/skills/performance-doctor/SKILL.md +110 -434
  208. package/.cursor/skills/redis-cache/SKILL.md +89 -595
  209. package/.cursor/skills/redis-cache/references/listeners.md +23 -0
  210. package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
  211. package/.cursor/skills/security-guard/SKILL.md +137 -532
  212. package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
  213. package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
  214. package/.cursor/skills/sms-mail/SKILL.md +116 -574
  215. package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
  216. package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
  217. package/.cursor/skills/social-login/SKILL.md +112 -514
  218. package/.cursor/skills/social-login/references/provider-configs.md +118 -0
  219. package/.cursor/skills/tenant-management/SKILL.md +129 -444
  220. package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
  221. package/.cursor/skills/test-development/SKILL.md +86 -540
  222. package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
  223. package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
  224. package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  225. package/.cursor/skills/websocket-sse/SKILL.md +105 -550
  226. package/.cursor/skills/workflow-engine/SKILL.md +147 -502
  227. package/AGENTS.md +1 -0
  228. 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
- > ⚠️ **本项目规范**:本文档基于项目实际代码编写,所有API和字段名均已验证。标记 `🔴 本项目规范` 的部分必须严格遵守。
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
- | 本地存储 | `local` | 存储到服务器本地目录 |
29
- | 阿里云OSS | `aliyun` | 阿里云对象存储 |
30
- | 腾讯云COS | `qcloud` | 腾讯云对象存储 |
31
- | 七牛云 | `qiniu` | 七牛云存储 |
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
- // 获取默认配置的客户端(从 Redis 读取默认 configKey)
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
- // ❌ 错误:不存在 OssType 枚举参数
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
- // 1. 上传字节数组,自动生成路径(推荐)
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
- // 2. 上传输入流,自动生成路径
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
- // 3. 上传 File 对象,自动生成路径
100
- File file = new File("/path/to/file.jpg");
61
+ // 3. 上传 File 对象,自动生成路径
101
62
  UploadResult result = client.uploadSuffix(file, ".jpg");
102
63
 
103
- // 4. 上传到指定路径(手动指定完整 key)
104
- Path filePath = file.toPath();
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
- // 5. 上传流到指定路径
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
- // 上传本地文件到S3
114
- public UploadResult upload(Path filePath, String key, String md5Digest, String contentType)
115
-
116
- // 上传输入流到S3
117
- public UploadResult upload(InputStream inputStream, String key, Long length, String contentType)
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
- ### UploadResult 字段(已验证)
132
-
133
- > **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java`
82
+ ## 三、UploadResult 字段
134
83
 
135
- > **重要**:UploadResult 使用 Lombok `@Builder`,只有 3 个字段。
84
+ > 只有 3 个字段,使用 Lombok `@Builder`。
136
85
 
137
86
  | 字段 | 类型 | 说明 |
138
87
  |------|------|------|
139
- | `url` | String | 文件访问URL(完整路径) |
140
- | `filename` | String | 文件名/对象键(注意是小写 'n') |
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(); // 小写 'n'
147
- String eTag = result.getETag();
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
- import java.nio.file.Path;
163
- import java.io.OutputStream;
164
-
165
- // ✅ 1. 下载到临时文件
103
+ // 下载到临时文件
166
104
  Path tempFile = client.fileDownload("images/photo.jpg");
167
- // 返回临时文件路径
168
105
 
169
- // ✅ 2. 下载到输出流(推荐用于响应流)
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
- // ✅ 3. 获取文件输入流
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
- // ❌ 以下方法不存在于 OssClient
204
- client.copyFile(...); // 不存在
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
- // 创建上传预签名URL(PUT请求)
250
- public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata)
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
- // 获取云存储服务的基础URL
261
- String baseUrl = client.getUrl();
262
-
263
- // 获取终端点URL (http:// 或 https://)
264
- String endpoint = client.getEndpoint();
265
-
266
- // 获取自定义域名或终端点
267
- String domain = client.getDomain();
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
- ```java
304
- @RestController
305
- @RequestMapping("/resource/oss")
306
- @RequiredArgsConstructor
307
- public class SysOssController extends BaseController {
308
-
309
- private final ISysOssService ossService;
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
- **返回对象 SysOssUploadVo**:
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
- ```java
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
- ```java
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
- ### SysOssBo 业务对象
201
+ **SysOss 关键字段:**
573
202
 
574
- ```java
575
- @Data
576
- @EqualsAndHashCode(callSuper = true)
577
- @AutoMapper(target = SysOss.class, reverseConvertGenerate = false)
578
- public class SysOssBo extends BaseEntity {
579
-
580
- private Long ossId;
581
- private String fileName;
582
- private String originalName;
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` | 配置标识(如 aliyun、minio |
625
- | `access_key` | Access Key |
626
- | `secret_key` | Secret Key |
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` | 访问策略(0-private, 1-public, 2-custom |
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
- ```java
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
- ```java
740
- // ✅ 正确:字段名是 filename(小写 'n')
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(key, duration)` | `client.createPresignedGetUrl(key, duration)` |
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/src/main/java/org/dromara/common/oss/factory/OssFactory.java` |
860
- | OssClient | `ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java` |
861
- | UploadResult | `ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java` |
862
- | SysOssController | `ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java` |
863
- | SysOssServiceImpl | `ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java` |
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` |