ai-engineering-init 1.1.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 (313) hide show
  1. package/.claude/agents/code-reviewer.md +139 -0
  2. package/.claude/agents/project-manager.md +159 -0
  3. package/.claude/audio/completed.wav +0 -0
  4. package/.claude/commands/add-todo.md +255 -0
  5. package/.claude/commands/check.md +210 -0
  6. package/.claude/commands/crud.md +454 -0
  7. package/.claude/commands/dev.md +503 -0
  8. package/.claude/commands/init-docs.md +681 -0
  9. package/.claude/commands/next.md +251 -0
  10. package/.claude/commands/progress.md +242 -0
  11. package/.claude/commands/start.md +199 -0
  12. package/.claude/commands/sync.md +307 -0
  13. package/.claude/commands/update-status.md +428 -0
  14. package/.claude/docs/Mixin/344/275/277/347/224/250/346/214/207/345/215/227.md +299 -0
  15. package/.claude/docs/README.md +167 -0
  16. package/.claude/docs//345/211/215/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +599 -0
  17. package/.claude/docs//345/220/216/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +726 -0
  18. package/.claude/docs//345/267/245/344/275/234/346/265/201/345/274/200/345/217/221/346/214/207/345/215/227.md +714 -0
  19. package/.claude/docs//345/267/245/345/205/267/347/261/273/344/275/277/347/224/250/346/214/207/345/215/227.md +463 -0
  20. package/.claude/docs//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/350/247/204/350/214/203.md +390 -0
  21. package/.claude/docs//346/226/260/345/212/237/350/203/275/345/274/200/345/217/221/346/265/201/347/250/213/350/247/204/350/214/203.md +688 -0
  22. package/.claude/docs//346/226/260/351/241/271/347/233/256/345/274/200/345/217/221/346/265/201/347/250/213.md +365 -0
  23. package/.claude/docs//346/241/206/346/236/266/350/257/264/346/230/216.md +393 -0
  24. package/.claude/docs//350/267/257/347/224/261/351/205/215/347/275/256/346/214/207/345/215/227.md +246 -0
  25. package/.claude/framework-config.json +73 -0
  26. package/.claude/hooks/pre-tool-use.js +117 -0
  27. package/.claude/hooks/skill-forced-eval.js +167 -0
  28. package/.claude/hooks/stop.js +58 -0
  29. package/.claude/settings.json +41 -0
  30. package/.claude/skills/add-skill/SKILL.md +352 -0
  31. package/.claude/skills/api-development/SKILL.md +560 -0
  32. package/.claude/skills/architecture-design/SKILL.md +756 -0
  33. package/.claude/skills/backend-annotations/SKILL.md +674 -0
  34. package/.claude/skills/banana-image/CHANGELOG.md +37 -0
  35. package/.claude/skills/banana-image/README.md +146 -0
  36. package/.claude/skills/banana-image/SKILL.md +164 -0
  37. package/.claude/skills/banana-image/assets/logo.png +0 -0
  38. package/.claude/skills/banana-image/references/advanced-usage.md +189 -0
  39. package/.claude/skills/banana-image/scripts/apply_template.py +125 -0
  40. package/.claude/skills/banana-image/scripts/banana_image_exec.ts +412 -0
  41. package/.claude/skills/banana-image/scripts/batch_prep.py +82 -0
  42. package/.claude/skills/banana-image/scripts/package-lock.json +1437 -0
  43. package/.claude/skills/banana-image/scripts/package.json +18 -0
  44. package/.claude/skills/banana-image/scripts/requirements.txt +10 -0
  45. package/.claude/skills/banana-image/templates/poster.json +22 -0
  46. package/.claude/skills/banana-image/templates/product.json +17 -0
  47. package/.claude/skills/banana-image/templates/social.json +22 -0
  48. package/.claude/skills/banana-image/templates/thumbnail.json +17 -0
  49. package/.claude/skills/brainstorm/SKILL.md +648 -0
  50. package/.claude/skills/bug-detective/SKILL.md +1206 -0
  51. package/.claude/skills/code-patterns/SKILL.md +590 -0
  52. package/.claude/skills/collaborating-with-codex/SKILL.md +174 -0
  53. package/.claude/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
  54. package/.claude/skills/collaborating-with-gemini/SKILL.md +194 -0
  55. package/.claude/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
  56. package/.claude/skills/crud-development/SKILL.md +649 -0
  57. package/.claude/skills/data-permission/SKILL.md +599 -0
  58. package/.claude/skills/database-ops/SKILL.md +407 -0
  59. package/.claude/skills/error-handler/SKILL.md +371 -0
  60. package/.claude/skills/file-oss-management/SKILL.md +863 -0
  61. package/.claude/skills/git-workflow/SKILL.md +375 -0
  62. package/.claude/skills/json-serialization/SKILL.md +357 -0
  63. package/.claude/skills/leniu-api-development/SKILL.md +803 -0
  64. package/.claude/skills/leniu-architecture-design/SKILL.md +598 -0
  65. package/.claude/skills/leniu-backend-annotations/SKILL.md +664 -0
  66. package/.claude/skills/leniu-code-patterns/SKILL.md +365 -0
  67. package/.claude/skills/leniu-crud-development/SKILL.md +1110 -0
  68. package/.claude/skills/leniu-data-permission/SKILL.md +256 -0
  69. package/.claude/skills/leniu-database-ops/SKILL.md +426 -0
  70. package/.claude/skills/leniu-error-handler/SKILL.md +462 -0
  71. package/.claude/skills/leniu-java-amount-handling/SKILL.md +461 -0
  72. package/.claude/skills/leniu-java-code-style/SKILL.md +510 -0
  73. package/.claude/skills/leniu-java-concurrent/SKILL.md +400 -0
  74. package/.claude/skills/leniu-java-entity/SKILL.md +751 -0
  75. package/.claude/skills/leniu-java-export/SKILL.md +560 -0
  76. package/.claude/skills/leniu-java-logging/SKILL.md +832 -0
  77. package/.claude/skills/leniu-java-mq/SKILL.md +338 -0
  78. package/.claude/skills/leniu-java-mybatis/SKILL.md +640 -0
  79. package/.claude/skills/leniu-java-report-query-param/SKILL.md +291 -0
  80. package/.claude/skills/leniu-java-task/SKILL.md +367 -0
  81. package/.claude/skills/leniu-java-total-line/SKILL.md +195 -0
  82. package/.claude/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  83. package/.claude/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  84. package/.claude/skills/leniu-mealtime/SKILL.md +215 -0
  85. package/.claude/skills/leniu-redis-cache/SKILL.md +316 -0
  86. package/.claude/skills/leniu-security-guard/SKILL.md +520 -0
  87. package/.claude/skills/leniu-utils-toolkit/SKILL.md +380 -0
  88. package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
  89. package/.claude/skills/openspec-archive-change/SKILL.md +114 -0
  90. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  91. package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
  92. package/.claude/skills/openspec-explore/SKILL.md +290 -0
  93. package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
  94. package/.claude/skills/openspec-new-change/SKILL.md +74 -0
  95. package/.claude/skills/openspec-onboard/SKILL.md +529 -0
  96. package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
  97. package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
  98. package/.claude/skills/performance-doctor/SKILL.md +627 -0
  99. package/.claude/skills/project-navigator/SKILL.md +305 -0
  100. package/.claude/skills/redis-cache/SKILL.md +839 -0
  101. package/.claude/skills/scheduled-jobs/SKILL.md +633 -0
  102. package/.claude/skills/security-guard/SKILL.md +748 -0
  103. package/.claude/skills/sms-mail/SKILL.md +766 -0
  104. package/.claude/skills/social-login/SKILL.md +668 -0
  105. package/.claude/skills/store-pc/SKILL.md +366 -0
  106. package/.claude/skills/task-tracker/SKILL.md +307 -0
  107. package/.claude/skills/tech-decision/SKILL.md +393 -0
  108. package/.claude/skills/tenant-management/SKILL.md +603 -0
  109. package/.claude/skills/test-development/SKILL.md +755 -0
  110. package/.claude/skills/ui-pc/SKILL.md +438 -0
  111. package/.claude/skills/utils-toolkit/SKILL.md +615 -0
  112. package/.claude/skills/websocket-sse/SKILL.md +716 -0
  113. package/.claude/skills/workflow-engine/SKILL.md +676 -0
  114. package/.claude/templates//345/276/205/345/212/236/346/270/205/345/215/225/346/250/241/346/235/277.md +56 -0
  115. package/.claude/templates//351/234/200/346/261/202/346/226/207/346/241/243/346/250/241/346/235/277.md +85 -0
  116. package/.claude/templates//351/241/271/347/233/256/347/212/266/346/200/201/346/250/241/346/235/277.md +43 -0
  117. package/.codex/skills/add-skill/SKILL.md +352 -0
  118. package/.codex/skills/add-todo/SKILL.md +269 -0
  119. package/.codex/skills/api-development/SKILL.md +693 -0
  120. package/.codex/skills/architecture-design/SKILL.md +628 -0
  121. package/.codex/skills/backend-annotations/SKILL.md +664 -0
  122. package/.codex/skills/banana-image/CHANGELOG.md +37 -0
  123. package/.codex/skills/banana-image/README.md +146 -0
  124. package/.codex/skills/banana-image/SKILL.md +164 -0
  125. package/.codex/skills/banana-image/assets/logo.png +0 -0
  126. package/.codex/skills/banana-image/references/advanced-usage.md +189 -0
  127. package/.codex/skills/banana-image/scripts/apply_template.py +125 -0
  128. package/.codex/skills/banana-image/scripts/banana_image_exec.ts +412 -0
  129. package/.codex/skills/banana-image/scripts/batch_prep.py +82 -0
  130. package/.codex/skills/banana-image/scripts/package-lock.json +1437 -0
  131. package/.codex/skills/banana-image/scripts/package.json +18 -0
  132. package/.codex/skills/banana-image/scripts/requirements.txt +10 -0
  133. package/.codex/skills/banana-image/templates/poster.json +22 -0
  134. package/.codex/skills/banana-image/templates/product.json +17 -0
  135. package/.codex/skills/banana-image/templates/social.json +22 -0
  136. package/.codex/skills/banana-image/templates/thumbnail.json +17 -0
  137. package/.codex/skills/brainstorm/SKILL.md +648 -0
  138. package/.codex/skills/bug-detective/SKILL.md +1206 -0
  139. package/.codex/skills/check/SKILL.md +367 -0
  140. package/.codex/skills/code-patterns/SKILL.md +442 -0
  141. package/.codex/skills/collaborating-with-codex/SKILL.md +174 -0
  142. package/.codex/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
  143. package/.codex/skills/collaborating-with-gemini/SKILL.md +194 -0
  144. package/.codex/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
  145. package/.codex/skills/crud/SKILL.md +265 -0
  146. package/.codex/skills/crud-development/SKILL.md +637 -0
  147. package/.codex/skills/data-permission/SKILL.md +591 -0
  148. package/.codex/skills/database-ops/SKILL.md +553 -0
  149. package/.codex/skills/dev/SKILL.md +187 -0
  150. package/.codex/skills/error-handler/SKILL.md +361 -0
  151. package/.codex/skills/file-oss-management/SKILL.md +863 -0
  152. package/.codex/skills/git-workflow/SKILL.md +375 -0
  153. package/.codex/skills/init-docs/SKILL.md +194 -0
  154. package/.codex/skills/json-serialization/SKILL.md +357 -0
  155. package/.codex/skills/leniu-api-development/SKILL.md +803 -0
  156. package/.codex/skills/leniu-architecture-design/SKILL.md +594 -0
  157. package/.codex/skills/leniu-backend-annotations/SKILL.md +662 -0
  158. package/.codex/skills/leniu-code-patterns/SKILL.md +365 -0
  159. package/.codex/skills/leniu-crud-development/SKILL.md +1110 -0
  160. package/.codex/skills/leniu-data-permission/SKILL.md +256 -0
  161. package/.codex/skills/leniu-database-ops/SKILL.md +426 -0
  162. package/.codex/skills/leniu-error-handler/SKILL.md +462 -0
  163. package/.codex/skills/leniu-java-amount-handling/SKILL.md +461 -0
  164. package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
  165. package/.codex/skills/leniu-java-concurrent/SKILL.md +400 -0
  166. package/.codex/skills/leniu-java-entity/SKILL.md +751 -0
  167. package/.codex/skills/leniu-java-export/SKILL.md +560 -0
  168. package/.codex/skills/leniu-java-logging/SKILL.md +832 -0
  169. package/.codex/skills/leniu-java-mq/SKILL.md +338 -0
  170. package/.codex/skills/leniu-java-mybatis/SKILL.md +640 -0
  171. package/.codex/skills/leniu-java-report-query-param/SKILL.md +291 -0
  172. package/.codex/skills/leniu-java-task/SKILL.md +367 -0
  173. package/.codex/skills/leniu-java-total-line/SKILL.md +195 -0
  174. package/.codex/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  175. package/.codex/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  176. package/.codex/skills/leniu-mealtime/SKILL.md +215 -0
  177. package/.codex/skills/leniu-redis-cache/SKILL.md +316 -0
  178. package/.codex/skills/leniu-security-guard/SKILL.md +520 -0
  179. package/.codex/skills/leniu-utils-toolkit/SKILL.md +378 -0
  180. package/.codex/skills/next/SKILL.md +137 -0
  181. package/.codex/skills/openspec-apply-change/SKILL.md +156 -0
  182. package/.codex/skills/openspec-archive-change/SKILL.md +114 -0
  183. package/.codex/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  184. package/.codex/skills/openspec-continue-change/SKILL.md +118 -0
  185. package/.codex/skills/openspec-explore/SKILL.md +290 -0
  186. package/.codex/skills/openspec-ff-change/SKILL.md +101 -0
  187. package/.codex/skills/openspec-new-change/SKILL.md +74 -0
  188. package/.codex/skills/openspec-onboard/SKILL.md +529 -0
  189. package/.codex/skills/openspec-sync-specs/SKILL.md +138 -0
  190. package/.codex/skills/openspec-verify-change/SKILL.md +168 -0
  191. package/.codex/skills/performance-doctor/SKILL.md +627 -0
  192. package/.codex/skills/progress/SKILL.md +193 -0
  193. package/.codex/skills/project-navigator/SKILL.md +286 -0
  194. package/.codex/skills/redis-cache/SKILL.md +829 -0
  195. package/.codex/skills/scheduled-jobs/SKILL.md +633 -0
  196. package/.codex/skills/security-guard/SKILL.md +739 -0
  197. package/.codex/skills/sms-mail/SKILL.md +766 -0
  198. package/.codex/skills/social-login/SKILL.md +668 -0
  199. package/.codex/skills/start/SKILL.md +154 -0
  200. package/.codex/skills/store-pc/SKILL.md +491 -0
  201. package/.codex/skills/sync/SKILL.md +149 -0
  202. package/.codex/skills/task-tracker/SKILL.md +307 -0
  203. package/.codex/skills/tech-decision/SKILL.md +393 -0
  204. package/.codex/skills/tenant-management/SKILL.md +603 -0
  205. package/.codex/skills/test-development/SKILL.md +755 -0
  206. package/.codex/skills/ui-pc/SKILL.md +475 -0
  207. package/.codex/skills/update-status/SKILL.md +159 -0
  208. package/.codex/skills/utils-toolkit/SKILL.md +593 -0
  209. package/.codex/skills/websocket-sse/SKILL.md +716 -0
  210. package/.codex/skills/workflow-engine/SKILL.md +676 -0
  211. package/.cursor/agents/code-reviewer.md +139 -0
  212. package/.cursor/agents/project-manager.md +159 -0
  213. package/.cursor/commands/opsx-apply.md +152 -0
  214. package/.cursor/commands/opsx-archive.md +157 -0
  215. package/.cursor/commands/opsx-bulk-archive.md +242 -0
  216. package/.cursor/commands/opsx-continue.md +114 -0
  217. package/.cursor/commands/opsx-explore.md +174 -0
  218. package/.cursor/commands/opsx-ff.md +94 -0
  219. package/.cursor/commands/opsx-new.md +69 -0
  220. package/.cursor/commands/opsx-onboard.md +525 -0
  221. package/.cursor/commands/opsx-sync.md +134 -0
  222. package/.cursor/commands/opsx-verify.md +164 -0
  223. package/.cursor/mcp.json +22 -0
  224. package/.cursor/skills/add-skill/SKILL.md +352 -0
  225. package/.cursor/skills/api-development/SKILL.md +560 -0
  226. package/.cursor/skills/architecture-design/SKILL.md +756 -0
  227. package/.cursor/skills/backend-annotations/SKILL.md +674 -0
  228. package/.cursor/skills/banana-image/CHANGELOG.md +37 -0
  229. package/.cursor/skills/banana-image/README.md +146 -0
  230. package/.cursor/skills/banana-image/SKILL.md +164 -0
  231. package/.cursor/skills/banana-image/assets/logo.png +0 -0
  232. package/.cursor/skills/banana-image/references/advanced-usage.md +189 -0
  233. package/.cursor/skills/banana-image/scripts/apply_template.py +125 -0
  234. package/.cursor/skills/banana-image/scripts/banana_image_exec.ts +412 -0
  235. package/.cursor/skills/banana-image/scripts/batch_prep.py +82 -0
  236. package/.cursor/skills/banana-image/scripts/package-lock.json +1437 -0
  237. package/.cursor/skills/banana-image/scripts/package.json +18 -0
  238. package/.cursor/skills/banana-image/scripts/requirements.txt +10 -0
  239. package/.cursor/skills/banana-image/templates/poster.json +22 -0
  240. package/.cursor/skills/banana-image/templates/product.json +17 -0
  241. package/.cursor/skills/banana-image/templates/social.json +22 -0
  242. package/.cursor/skills/banana-image/templates/thumbnail.json +17 -0
  243. package/.cursor/skills/brainstorm/SKILL.md +648 -0
  244. package/.cursor/skills/bug-detective/SKILL.md +1206 -0
  245. package/.cursor/skills/code-patterns/SKILL.md +590 -0
  246. package/.cursor/skills/collaborating-with-codex/SKILL.md +174 -0
  247. package/.cursor/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
  248. package/.cursor/skills/collaborating-with-gemini/SKILL.md +194 -0
  249. package/.cursor/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
  250. package/.cursor/skills/crud-development/SKILL.md +649 -0
  251. package/.cursor/skills/data-permission/SKILL.md +599 -0
  252. package/.cursor/skills/database-ops/SKILL.md +407 -0
  253. package/.cursor/skills/error-handler/SKILL.md +371 -0
  254. package/.cursor/skills/file-oss-management/SKILL.md +863 -0
  255. package/.cursor/skills/git-workflow/SKILL.md +375 -0
  256. package/.cursor/skills/json-serialization/SKILL.md +357 -0
  257. package/.cursor/skills/leniu-api-development/SKILL.md +803 -0
  258. package/.cursor/skills/leniu-architecture-design/SKILL.md +598 -0
  259. package/.cursor/skills/leniu-backend-annotations/SKILL.md +664 -0
  260. package/.cursor/skills/leniu-code-patterns/SKILL.md +365 -0
  261. package/.cursor/skills/leniu-crud-development/SKILL.md +1110 -0
  262. package/.cursor/skills/leniu-data-permission/SKILL.md +256 -0
  263. package/.cursor/skills/leniu-database-ops/SKILL.md +426 -0
  264. package/.cursor/skills/leniu-error-handler/SKILL.md +462 -0
  265. package/.cursor/skills/leniu-java-amount-handling/SKILL.md +461 -0
  266. package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
  267. package/.cursor/skills/leniu-java-concurrent/SKILL.md +400 -0
  268. package/.cursor/skills/leniu-java-entity/SKILL.md +751 -0
  269. package/.cursor/skills/leniu-java-export/SKILL.md +560 -0
  270. package/.cursor/skills/leniu-java-logging/SKILL.md +832 -0
  271. package/.cursor/skills/leniu-java-mq/SKILL.md +338 -0
  272. package/.cursor/skills/leniu-java-mybatis/SKILL.md +640 -0
  273. package/.cursor/skills/leniu-java-report-query-param/SKILL.md +291 -0
  274. package/.cursor/skills/leniu-java-task/SKILL.md +367 -0
  275. package/.cursor/skills/leniu-java-total-line/SKILL.md +195 -0
  276. package/.cursor/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  277. package/.cursor/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  278. package/.cursor/skills/leniu-mealtime/SKILL.md +215 -0
  279. package/.cursor/skills/leniu-redis-cache/SKILL.md +316 -0
  280. package/.cursor/skills/leniu-security-guard/SKILL.md +520 -0
  281. package/.cursor/skills/leniu-utils-toolkit/SKILL.md +380 -0
  282. package/.cursor/skills/openspec-apply-change/SKILL.md +156 -0
  283. package/.cursor/skills/openspec-archive-change/SKILL.md +114 -0
  284. package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  285. package/.cursor/skills/openspec-continue-change/SKILL.md +118 -0
  286. package/.cursor/skills/openspec-explore/SKILL.md +290 -0
  287. package/.cursor/skills/openspec-ff-change/SKILL.md +101 -0
  288. package/.cursor/skills/openspec-new-change/SKILL.md +74 -0
  289. package/.cursor/skills/openspec-onboard/SKILL.md +529 -0
  290. package/.cursor/skills/openspec-sync-specs/SKILL.md +138 -0
  291. package/.cursor/skills/openspec-verify-change/SKILL.md +168 -0
  292. package/.cursor/skills/performance-doctor/SKILL.md +627 -0
  293. package/.cursor/skills/project-navigator/SKILL.md +305 -0
  294. package/.cursor/skills/redis-cache/SKILL.md +839 -0
  295. package/.cursor/skills/scheduled-jobs/SKILL.md +633 -0
  296. package/.cursor/skills/security-guard/SKILL.md +748 -0
  297. package/.cursor/skills/sms-mail/SKILL.md +766 -0
  298. package/.cursor/skills/social-login/SKILL.md +668 -0
  299. package/.cursor/skills/store-pc/SKILL.md +366 -0
  300. package/.cursor/skills/task-tracker/SKILL.md +307 -0
  301. package/.cursor/skills/tech-decision/SKILL.md +393 -0
  302. package/.cursor/skills/tenant-management/SKILL.md +603 -0
  303. package/.cursor/skills/test-development/SKILL.md +755 -0
  304. package/.cursor/skills/ui-pc/SKILL.md +438 -0
  305. package/.cursor/skills/utils-toolkit/SKILL.md +615 -0
  306. package/.cursor/skills/websocket-sse/SKILL.md +716 -0
  307. package/.cursor/skills/workflow-engine/SKILL.md +676 -0
  308. package/AGENTS.md +669 -0
  309. package/CLAUDE.md +205 -0
  310. package/README.md +205 -0
  311. package/bin/index.js +179 -0
  312. package/init.sh +178 -0
  313. package/package.json +27 -0
@@ -0,0 +1,863 @@
1
+ ---
2
+ name: file-oss-management
3
+ description: |
4
+ 当需要进行文件上传、下载、存储管理时自动使用此 Skill。支持本地存储、阿里云OSS、腾讯云COS、七牛云、MinIO等。
5
+
6
+ 触发场景:
7
+ - 文件上传下载
8
+ - 云存储配置
9
+ - 预签名URL生成
10
+ - 文件元数据管理
11
+ - 图片处理
12
+
13
+ 触发词:文件上传、OSS、云存储、MinIO、阿里云、腾讯云、七牛、图片上传、文件下载、预签名、presigned
14
+ ---
15
+
16
+ # 文件与云存储指南
17
+
18
+ > ⚠️ **本项目规范**:本文档基于项目实际代码编写,所有API和字段名均已验证。标记 `🔴 本项目规范` 的部分必须严格遵守。
19
+
20
+ ---
21
+
22
+ ## 支持的存储类型
23
+
24
+ > **统一协议**:基于 AWS S3 SDK v2,支持所有兼容 S3 协议的云服务
25
+
26
+ | 类型 | 配置键示例 | 说明 |
27
+ |------|-----------|------|
28
+ | 本地存储 | `local` | 存储到服务器本地目录 |
29
+ | 阿里云OSS | `aliyun` | 阿里云对象存储 |
30
+ | 腾讯云COS | `qcloud` | 腾讯云对象存储 |
31
+ | 七牛云 | `qiniu` | 七牛云存储 |
32
+ | MinIO | `minio` | 开源对象存储 |
33
+ | 华为云OBS | `obs` | 华为云对象存储 |
34
+
35
+ ---
36
+
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"),不是枚举。
57
+
58
+ ```java
59
+ import org.dromara.common.oss.factory.OssFactory;
60
+ import org.dromara.common.oss.core.OssClient;
61
+
62
+ // ✅ 获取默认配置的客户端(从 Redis 读取默认 configKey)
63
+ OssClient client = OssFactory.instance();
64
+
65
+ // ✅ 根据配置键获取(配置键是字符串)
66
+ OssClient client = OssFactory.instance("aliyun");
67
+ OssClient client = OssFactory.instance("minio");
68
+
69
+ // ❌ 错误:不存在 OssType 枚举参数
70
+ OssClient client = OssFactory.instance(OssType.ALIYUN); // 编译错误!
71
+ ```
72
+
73
+ **工作原理**:
74
+ - 使用 `ConcurrentHashMap` 缓存 OssClient 实例
75
+ - 使用 `ReentrantLock` 双检锁模式确保线程安全
76
+ - 支持多租户隔离(对每个租户的 configKey 分别缓存)
77
+ - 配置信息从 Redis 中读取(`CacheNames.SYS_OSS_CONFIG`)
78
+
79
+ ---
80
+
81
+ ### 文件上传
82
+
83
+ > **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java:140-227`
84
+
85
+ ```java
86
+ import org.dromara.common.oss.entity.UploadResult;
87
+ import java.nio.file.Path;
88
+
89
+ // ✅ 1. 上传字节数组,自动生成路径(推荐)
90
+ byte[] data = multipartFile.getBytes();
91
+ UploadResult result = client.uploadSuffix(data, ".jpg", "image/jpeg");
92
+ // 结果:prefix/2024/12/01/uuid.jpg
93
+
94
+ // ✅ 2. 上传输入流,自动生成路径
95
+ InputStream is = multipartFile.getInputStream();
96
+ Long fileSize = multipartFile.getSize();
97
+ UploadResult result = client.uploadSuffix(is, ".jpg", fileSize, "image/jpeg");
98
+
99
+ // ✅ 3. 上传 File 对象,自动生成路径
100
+ File file = new File("/path/to/file.jpg");
101
+ UploadResult result = client.uploadSuffix(file, ".jpg");
102
+
103
+ // ✅ 4. 上传到指定路径(手动指定完整 key)
104
+ Path filePath = file.toPath();
105
+ UploadResult result = client.upload(filePath, "avatar/user123.jpg", null, "image/jpeg");
106
+
107
+ // ✅ 5. 上传流到指定路径
108
+ UploadResult result = client.upload(is, "images/photo.jpg", fileSize, "image/jpeg");
109
+ ```
110
+
111
+ **方法签名(已验证)**:
112
+ ```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)
127
+ ```
128
+
129
+ ---
130
+
131
+ ### UploadResult 字段(已验证)
132
+
133
+ > **实际代码位置**:`ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java`
134
+
135
+ > **重要**:UploadResult 使用 Lombok `@Builder`,只有 3 个字段。
136
+
137
+ | 字段 | 类型 | 说明 |
138
+ |------|------|------|
139
+ | `url` | String | 文件访问URL(完整路径) |
140
+ | `filename` | String | 文件名/对象键(注意是小写 'n') |
141
+ | `eTag` | String | 文件校验标记(用来校验文件) |
142
+
143
+ ```java
144
+ // ✅ 正确
145
+ 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(); // 编译错误!
153
+ ```
154
+
155
+ ---
156
+
157
+ ## 文件下载
158
+
159
+ > **实际代码位置**:`OssClient.java:236-319`
160
+
161
+ ```java
162
+ import java.nio.file.Path;
163
+ import java.io.OutputStream;
164
+
165
+ // ✅ 1. 下载到临时文件
166
+ Path tempFile = client.fileDownload("images/photo.jpg");
167
+ // 返回临时文件路径
168
+
169
+ // ✅ 2. 下载到输出流(推荐用于响应流)
170
+ OutputStream out = response.getOutputStream();
171
+ client.download("images/photo.jpg", out, contentLength -> {
172
+ // 可选的文件大小回调(用于设置响应头)
173
+ response.setContentLengthLong(contentLength);
174
+ });
175
+
176
+ // ✅ 3. 获取文件输入流
177
+ 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
+ ```
192
+
193
+ ---
194
+
195
+ ## 文件操作
196
+
197
+ > **实际代码位置**:`OssClient.java`
198
+
199
+ ```java
200
+ // ✅ 删除文件
201
+ client.delete("images/photo.jpg");
202
+
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)
248
+
249
+ // 创建上传预签名URL(PUT请求)
250
+ public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata)
251
+ ```
252
+
253
+ ---
254
+
255
+ ## OssClient 其他工具方法
256
+
257
+ > **实际代码位置**:`OssClient.java`
258
+
259
+ ```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);
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Controller 示例(已验证)
288
+
289
+ > **实际代码位置**:`ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java`
290
+
291
+ ### API 接口清单
292
+
293
+ | 操作 | HTTP方法 | 路径 | 权限 |
294
+ |------|---------|------|------|
295
+ | 查询列表 | GET | `/resource/oss/list` | `system:oss:list` |
296
+ | 批量查询 | GET | `/resource/oss/listByIds/{ossIds}` | `system:oss:query` |
297
+ | 上传文件 | POST | `/resource/oss/upload` | `system:oss:upload` |
298
+ | 下载文件 | GET | `/resource/oss/download/{ossId}` | `system:oss:download` |
299
+ | 删除文件 | DELETE | `/resource/oss/{ossIds}` | `system:oss:remove` |
300
+
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
+ }
353
+ }
354
+ ```
355
+
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
+ ```
365
+
366
+ ---
367
+
368
+ ## Service 层示例(已验证)
369
+
370
+ > **实际代码位置**:`ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java`
371
+
372
+ ### 服务接口方法(ISysOssService)
373
+
374
+ ```java
375
+ // 分页查询OSS对象
376
+ TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
377
+
378
+ // 根据ossId列表查询
379
+ List<SysOssVo> listByIds(Collection<Long> ossIds);
380
+
381
+ // 单个ID查询(带缓存)
382
+ @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
383
+ SysOssVo getById(Long ossId);
384
+
385
+ // MultipartFile上传
386
+ SysOssVo upload(MultipartFile file);
387
+
388
+ // File对象上传
389
+ SysOssVo upload(File file);
390
+
391
+ // 文件下载
392
+ void download(Long ossId, HttpServletResponse response) throws IOException;
393
+
394
+ // 删除文件
395
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
396
+ ```
397
+
398
+ ### 文件上传完整流程
399
+
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
+ ```
521
+
522
+ ---
523
+
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 视图对象
550
+
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
+ ```
571
+
572
+ ### SysOssBo 业务对象
573
+
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
+ ```
613
+
614
+ ---
615
+
616
+ ## 配置说明
617
+
618
+ ### 数据库配置表(sys_oss_config)
619
+
620
+ > **配置存储位置**:Redis(`CacheNames.SYS_OSS_CONFIG`)
621
+
622
+ | 字段 | 说明 |
623
+ |------|------|
624
+ | `config_key` | 配置标识(如 aliyun、minio) |
625
+ | `access_key` | Access Key |
626
+ | `secret_key` | Secret Key |
627
+ | `bucket_name` | 存储桶名称 |
628
+ | `prefix` | 路径前缀 |
629
+ | `endpoint` | 服务端点 |
630
+ | `domain` | 自定义域名 |
631
+ | `is_https` | 是否HTTPS(Y/N) |
632
+ | `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. 文件上传后保存数据库
680
+
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
+ ```
732
+
733
+ ---
734
+
735
+ ## 常见问题
736
+
737
+ ### ⚠️ UploadResult.getFilename() vs getFileName()
738
+
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
+ |--------|--------|
785
+ | `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
+ | `result.getFileName()` | `result.getFilename()` |
789
+ | `result.getFileSize()` | 不存在此字段 |
790
+ | `client.downloadToTempFile(path)` | `client.fileDownload(path)` |
791
+ | `client.generatePresignedUrl(key, duration)` | `client.createPresignedGetUrl(key, duration)` |
792
+ | `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 字段中,支持灵活扩展
852
+
853
+ ---
854
+
855
+ ## 相关文件位置
856
+
857
+ | 类型 | 位置 |
858
+ |------|------|
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` |