黑马JAVAWeb-10 文件上传-文件存储到服务器本地磁盘-文件存储在阿里云OSS-@Value属性注入

1.文件上传

  • 前端 和 服务器端
  • 想要上传文件,前端要求

  • 代码使用示例(单文件上传接口)
java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    // 处理单文件上传
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        // 判断文件是否为空
        if (file.isEmpty()) {
            return "请选择要上传的文件";
        }

        try {
            // 获取原始文件名
            String fileName = file.getOriginalFilename();
            // 定义文件保存路径(例如:项目根目录下的 upload 文件夹)
            String filePath = "D:/upload/";
            File dest = new File(filePath + fileName);

            // 确保保存目录存在(不存在则创建)
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
            }

            // 保存文件到本地
            file.transferTo(dest);
            return "文件上传成功,保存路径:" + dest.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败:" + e.getMessage();
        }
    }
}

关键说明
接口参数:通过 @RequestParam("file") 绑定前端上传的文件,其中 file 需与前端表单中 input 的 name 属性值一致(如 <input type="file" name="file">)。
  • 多文件上传:若需同时上传多个文件,可使用 List 接收
java 复制代码
@PostMapping("/upload-multiple")
public String uploadMultipleFiles(@RequestParam("files") List<MultipartFile> files) {
    // 遍历文件列表,逐个处理
    for (MultipartFile file : files) {
        // 处理逻辑同单文件上传...
    }
    return "多文件上传完成";
}

2.文件存储到本地

file.transferTo() 就是把用户上传的临时文件 "转移" 并永久保存到你指定的本地位置的关键操作。

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@RestController
public class UploadController {

    private static final Logger log = LoggerFactory.getLogger(UploadController.class);

    // 定义文件保存的根目录(实际项目中建议配置在application.properties中,方便修改)
    private static final String UPLOAD_BASE_DIR = "D:/images/";

    // 允许上传的文件类型(根据业务需求调整)
    private static final String[] ALLOWED_CONTENT_TYPES = {
            "image/jpeg", "image/png", "image/gif", "image/bmp"
    };

    // 单个文件最大大小限制(5MB,1MB=1024*1024字节)
    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024;

    @PostMapping("/upload")
    public ResponseEntity<String> upload(
            @RequestParam String name,
            @RequestParam Integer age,
            @RequestParam MultipartFile file) {

        // 1. 校验文件是否为空
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("上传失败:请选择文件");
        }

        try {
            // 2. 校验文件大小
            if (file.getSize() > MAX_FILE_SIZE) {
                return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
                        .body("上传失败:文件大小不能超过5MB");
            }

            // 3. 校验文件类型(通过MIME类型判断)
            String contentType = file.getContentType();
            boolean isAllowed = false;
            for (String allowedType : ALLOWED_CONTENT_TYPES) {
                if (allowedType.equals(contentType)) {
                    isAllowed = true;
                    break;
                }
            }
            if (!isAllowed) {
                return ResponseEntity.badRequest()
                        .body("上传失败:仅支持JPG、PNG、GIF、BMP格式的图片");
            }

            // 4. 确保保存目录存在(不存在则创建,包括多级目录)
            File baseDir = new File(UPLOAD_BASE_DIR);
            if (!baseDir.exists()) {
                boolean mkdirsSuccess = baseDir.mkdirs();
                if (!mkdirsSuccess) {
                    log.error("创建上传目录失败:{}", UPLOAD_BASE_DIR);
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                            .body("上传失败:服务器存储目录创建失败");
                }
            }

            // 5. 生成唯一文件名(避免重名覆盖)
            // 5.1 获取原始文件名的后缀(如.jpg)
            String originalFilename = file.getOriginalFilename();
            String fileSuffix = "";
            if (originalFilename.contains(".")) {
                fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            }

            // 5.2 生成唯一文件名:UUID + 时间戳 + 后缀(确保唯一性)
            String uniqueFileName = UUID.randomUUID().toString()
                    + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
                    + fileSuffix;

            // 6. 构建目标文件路径
            File destFile = new File(baseDir, uniqueFileName);

            // 7. 保存文件到本地
            file.transferTo(destFile);
            log.info("文件上传成功:{} -> {}", originalFilename, destFile.getAbsolutePath());

            // 8. 返回成功响应(包含实际保存的文件名)
            return ResponseEntity.ok("文件上传成功,保存路径:" + destFile.getAbsolutePath());

        } catch (IOException e) {
            log.error("文件上传失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("上传失败:" + e.getMessage());
        }
    }
}

2.文件存储在阿里云OSS

  • 存在服务器的本地磁盘,无法无限增大存储容量,磁盘容量终有限
  • 生产环境中常采用云服务


  • 使用阿里云OSS -少量改动,直接默认选项后确认
  • 创建Bucket桶

  • 更改权限

  • 获取秘钥




java 复制代码
set OSS_ACCESS_KEY_ID=xxxxxxxxxx
set OSS_ACCESS_KEY_SECRET=xxxxxxxxxxxxxxx

setx OSS_ACCESS_KEY_ID "%OSS_ACCESS_KEY_ID%"
setx OSS_ACCESS_KEY_SECRET "%OSS_ACCESS_KEY_SECRET%"

echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%
  • 泄露OSS_ACCESS_KEY之后,直接禁用重新创建即可

    3.参照官方SDK编写入门程序


  • 引入依赖
java 复制代码
<!--        引入阿里云OSS依赖-->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>
  • 这个前面已经配置过了
  • 复制文件上次的demo进行配置
java 复制代码
package com.itheima;

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;

import java.io.File;

public class Demo {

    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-shenzhen.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "tianwen-java-ai";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "Snipaste_2025-11-08_09-27-06.png";
        // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
        String region = "cn-shenzhen";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "C:\\Users\\59742\\Desktop\\Snipaste_2025-11-08_09-27-06.png";



        
        // 创建OSSClient实例。
        // 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        
        OSS ossClient = OSSClientBuilder.create()
        .endpoint(endpoint)
        .credentialsProvider(credentialsProvider)
        .clientConfiguration(clientBuilderConfiguration)
        .region(region)               
        .build();

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);
            
            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);           
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
  • Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。

    String endpoint = "https://oss-cn-shenzhen.aliyuncs.com";

  • 填写Bucket名称,例如examplebucket。

    String bucketName = "tianwen-java-ai";

  • 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。

    String objectName = "Snipaste_2025-11-08_09-27-06.png";

  • 填写本地文件的完整路径,例如D:\localpath\examplefile.txt。

    如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。

    String filePath= "C:\Users\59742\Desktop\Snipaste_2025-11-08_09-27-06.png";

3.运行代码

  • 代码配置完成之后,需要重启IDEA,否则可能IDEA可能读取不到刚刚CMD里配置的已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
  • 运行Demo程序,上传成功!

    4.阿里云OSS,案例集成

  • 代码案例
  • UploadController类
java 复制代码
package com.itheima.controller;
import com.itheima.pojo.Result;
import com.itheima.utils.AliyunOSS0perator;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


@Slf4j
@RestController
public class UploadController {

    private static final Logger log = LoggerFactory.getLogger(UploadController.class);

    @Autowired
    private AliyunOSS0perator aliyunOSS0perator;

    @RequestMapping("/upload")
    public Result upload(MultipartFile file) throws Exception {
        log.info("文件上传开始...{}", file.getOriginalFilename());
        // 将文件交给OSS存储管理
        String url = aliyunOSS0perator.upload(file.getBytes(), file.getOriginalFilename());
        log.info("文件的URL:{}", url);
        return Result.success(url);
    }

}
  • AliyunOSSOperator类
java 复制代码
package com.itheima.utils;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyuncs.exceptions.ClientException;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

@Component
public class AliyunOSS0perator {
    private String endpoint = "https://oss-cn-shenzhen.aliyuncs.com";
    private String bucketName = "tianwen-java-ai";
    private String region = "cn-shenzhen";

    public String upload(byte[] bytes, String originalFilename) throws ClientException {
        // 从环境变量中获取访问凭证,运行代码前,确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();

        // 构造文件存储路径(按年月分目录 + 随机UUID文件名)
        String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
        // 生成不会重复的新文件名
        String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = dir + "/" + newFileName;

        // 创建OSSClient实例
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        try {
            // 上传文件到OSS
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));

            // 生成可访问的URL(这里以公共读权限为例,实际可根据业务调整)
            return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName; } finally {
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

3.1 代码优化

  • 这三个信息需要变动,不建议硬编码,将需要变动的信息配置在yml文件中
java 复制代码
通过直接注入的方式,不用写死在代码中
public class AliyunOSS0perator {
    @Value("${aliyun.endpoint}")
    private String endpoint;
    @Value("${aliyun.bucketName}")
    private String bucketName;
    @Value("${aliyun.region}")
    private String region;
    ...
    ...
    ...
}

application.yml文件中↓
# aliyun_OSS相关配置
aliyun:
  endpoint: https://oss-cn-shenzhen.aliyuncs.com
  bucketName: tianwen-java-ai
  region: cn-shenzhen

4.2 @Value注入配置文件,过于繁琐,每次都要手动@Value每一个属性

  • 简化@Value注解带来的问题
  • 将配置项数据配置到实体类中,使用时,直接@Autowire注入Bean即可
java 复制代码
@Data
@Component
// 定义配置项映射 prefix是前缀
@ConfigurationProperties(prefix = "aliyun.oss") 
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
    private String region;
}

# aliyun_OSS相关配置
aliyun:
  oss:
    endpoint: https://oss-cn-shenzhen.aliyuncs.com
    bucketName: tianwen-java-ai
    region: cn-shenzhen


@Component
public class AliyunOSS0perator {

    @Autowired
    AliyunOSSProperties aliyunOSSProperties;

    public String upload(byte[] bytes, String originalFilename) throws ClientException {
        String endpoint = aliyunOSSProperties.getEndpoint();
        String bucketName = aliyunOSSProperties.getBucketName();
        String region = aliyunOSSProperties.getRegion();
        ...
        ...
        ...
}
相关推荐
大G的笔记本2 小时前
算法篇常见面试题清单
java·算法·排序算法
亚林瓜子2 小时前
Spring中的异步任务(CompletableFuture版)
java·spring boot·spring·async·future·异步
MuYiLuck2 小时前
redis持久化与集群
java·数据库·redis
一叶飘零_sweeeet2 小时前
Java 项目 HTTP+WebSocket 统一权限控制实战
java·websocket·http·权限控制
7澄12 小时前
深入解析 LeetCode 数组经典问题:删除每行中的最大值与找出峰值
java·开发语言·算法·leetcode·intellij idea
ysyxg3 小时前
设计模式-策略模式
java·开发语言
Felix_XXXXL3 小时前
Spring Security安全框架原理与实战
java·后端
一抓掉一大把3 小时前
秒杀-StackExchangeRedisHelper连接单例
java·开发语言·jvm
升鲜宝供应链及收银系统源代码服务3 小时前
升鲜宝生鲜配送供应链管理系统--- 《多语言商品查询优化方案(Redis + 翻译表 + 模糊匹配)》
java·数据库·redis·bootstrap·供应链系统·生鲜配送·生鲜配送源代码