阿里云 OSS 文件上传笔记

阿里云 OSS 文件上传笔记

商品主图、管理员/用户头像等文件不存应用服务器(多实例不共享、重新部署会丢),而是上传到阿里云 OSS 对象存储,数据库里只存图片访问 URL。上传接口在 mall-service,OSS 工具类在 mall-common,孤儿图片由定时任务清理(见《定时任务.md》)。


一、为什么用 OSS(对比本地上传)

方案 问题
存数据库 BLOB 撑大数据库、备份慢、读写都压在 DB
存应用服务器本地磁盘 微服务多实例不共享;重新部署/扩容文件丢失;占用应用带宽
OSS 对象存储 与应用解耦、可 CDN 加速、高可用、按量付费,DB 只存一个 URL

product.main_image、admin.avatar 字段存的就是 OSS 返回的 https 地址。


二、配置(密钥走环境变量,不留明文)

各服务 application.yml(OSS 配置定义在父/各服务里):

oss:
  endpoint: https://oss-cn-beijing.aliyuncs.com
  bucket-name: maall
  access-key-id: ${ACCESS_KEY_ID:}            # 冒号后默认空,强制由环境变量注入
  access-key-secret: ${ACCESS_KEY_SECRET:}

AliOSSUtil 用 @Value 读取这四项。密钥不写死、不提交到 Git,和支付宝私钥、DeepSeek api-key 的处理方式一致。


三、上传接口(mall-service)

mall-service/.../controller/UploadController.java:

@RestController
@RequestMapping("/service")
public class UploadController {

    @Autowired
    private AliOSSUtil aliOSSUtil;

    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file) {
        // 1.UUID 生成唯一文件名,避免同名覆盖
        String uuid = UUID.randomUUID().toString().replace("-", "");
        // 2.保留原始扩展名,如 .png
        String filename = file.getOriginalFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        String newFilename = uuid + extension;          // 例如 7c45...abe.png
        // 3.以流的形式上传到 OSS,返回公网访问 URL
        String url = aliOSSUtil.uploadFile(newFilename, file.getInputStream());
        return Result.ok("上传成功", url);
    }
}

要点:

  • MultipartFile 是 Spring 封装的上传文件对象,getInputStream() 拿文件流,getOriginalFilename() 拿原始文件名;
  • UUID 重命名:防止不同用户上传同名文件互相覆盖,也规避中文/特殊字符文件名问题;
  • 返回的 URL 由前端填进表单(商品主图字段),最后随表单一起入库。

四、OSS 工具类(mall-common)

mall-common/.../util/AliOSSUtil.java:

@Component
public class AliOSSUtil {
    @Value("${oss.endpoint}")        private String endpoint;
    @Value("${oss.bucket-name}")     private String bucketName;
    @Value("${oss.access-key-id}")   private String accessKeyId;
    @Value("${oss.access-key-secret}") private String accessKeySecret;

    public String uploadFile(String objectName, InputStream in) throws Exception {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        try {
            PutObjectRequest req = new PutObjectRequest(bucketName, objectName, in);
            ossClient.putObject(req);
            // URL 拼接:https://bucket名.endpoint/objectName
            return "https://" + bucketName + "."
                 + endpoint.substring(endpoint.lastIndexOf("/") + 1) + "/" + objectName;
        } finally {
            ossClient.shutdown();   // 用完释放连接
        }
    }

    public void deleteFile(String url) {                 // 按 objectName 删除
        ossClient.deleteObject(bucketName, url.substring(url.lastIndexOf("/") + 1));
    }

    public Set<String> listFile() {                      // 分页列举 bucket 内全部 objectName
        // marker 翻页循环直到 !isTruncated()
    }
}
  • SDK 依赖 com.aliyun.oss:aliyun-sdk-oss(父 pom 管理,3.15.1);
  • 三个方法分别对应:上传 putObject、删除 deleteObject、列举 listObjects(定时清理孤儿图用)。

五、前端怎么传(el-upload)

Element Plus 的 el-upload 自带文件选择和上传,关键是要带 JWT(请求经网关,网关要验 token)。el-upload 不走 axios 拦截器,所以用 :headers 手动加:

<el-upload
    action="/service/upload"
    name="file"
    :headers="{ Authorization: tokenStore.token }"
    :show-file-list="false"
    :on-success="handleSuccess">
  <el-button>上传图片</el-button>
</el-upload>
  • action 是上传接口(经 vite 代理/网关);name="file" 要和后端 MultipartFile file 参数名一致;
  • on-success 回调里把返回的图片 URL 赋给表单字段(如 form.mainImage = res.data)。

六、孤儿图片清理(与定时任务联动)

商品删除/换图后,旧图片成了 OSS 里”数据库不再引用”的孤儿文件,持续占空间。DeleteImageJob 每 2 分钟(cron 可配)做差集清理:

DB 在用图片名(product 主图 + admin 头像,经 Feign 聚合)
              ⊖
OSS 全部文件名(aliOSSUtil.listFile())
              = 孤儿文件 → aliOSSUtil.deleteFile(name)

详见《定时任务.md》。这是”上传”之外保证 OSS 不堆积垃圾的闭环。


七、生产环境改进点(面试可主动说)

  1. 不用主账号 AK/SK:当前 AccessKey 权限过大。生产用 STS 临时凭证 或只授权 OSS 的 RAM 子账号,泄露面最小。
  2. 前端直传(推荐):后端只生成签名/STS,浏览器直传 OSS,文件流量不经过应用服务器,大幅降低后端带宽与压力。当前是”浏览器→应用→OSS”的中转上传。
  3. OSSClient 复用:现在每次上传都 new 一个 client 再 shutdown,连接无法复用。生产把 OSS 做成单例 Bean(线程安全),容器关闭时统一 shutdown。
  4. 校验文件:限制类型(白名单扩展名/Content-Type)、大小,图片可做压缩;防止上传可执行文件。
  5. 私有 Bucket + 预签名 URL:敏感文件私有读,用限时签名 URL 访问;公开图片才用公共读。
  6. 可配合 CDN 加速、图片处理服务(缩略图/水印)。

八、一句话总结

前端用 el-upload 带 JWT 把文件 POST 到 mall-service 的 /service/upload;后端用 MultipartFile 接流、UUID 重命名防覆盖,调 AliOSSUtil.putObject 上传到阿里云 OSS 并拼出访问 URL 返回;密钥由环境变量注入不落明文;数据库只存 URL;定时任务再用”OSS 全集 − DB 在用集”清理孤儿图片。生产应升级为 STS 临时凭证 + 前端直传 + 单例 client + 文件类型校验。

上一篇
下一篇