一、文件上传代码修正与完整实现
你贴的代码里有几个明显的问题:
originalFilename.substring(originalFilename.lastIndexOf("."))里的"."被 IDE 提示标红了,是因为代码里混入了非纯文本字符(比如中文引号或多余空格)。- 路径硬编码、文件不存在判断、返回 URL 都不规范。
下面是修正后的完整版本:
java
运行
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
public class FileUploadController {
// 从配置文件读取存储路径,避免硬编码
@Value("${upload.base-path}")
private String basePath;
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws IOException {
// 1. 校验文件是否为空
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
// 2. 获取原始文件名
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
return Result.error("文件名无效");
}
// 3. 提取文件扩展名(兼容无后缀文件)
String suffix = "";
int dotIndex = originalFilename.lastIndexOf(".");
if (dotIndex > 0) {
suffix = originalFilename.substring(dotIndex);
}
// 4. 生成唯一文件名(UUID + 扩展名)
String filename = UUID.randomUUID().toString() + suffix;
// 5. 确保目标目录存在
File destDir = new File(basePath);
if (!destDir.exists()) {
destDir.mkdirs(); // 不存在则创建目录
}
// 6. 保存文件到磁盘
File destFile = new File(basePath + filename);
file.transferTo(destFile);
// 7. 返回可访问的文件 URL(示例,需根据部署环境调整)
String url = "http://localhost:8080/files/" + filename;
return Result.success(url);
}
}
application.properties 配置:
properties
# 文件上传大小限制
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
# 自定义存储路径(末尾必须加 /)
upload.base-path=E:/java项目/files/
MultipartFile 核心方法说明
表格
| 方法 | 作用 |
|---|---|
getOriginalFilename() |
获取用户上传的原始文件名 |
transferTo(File dest) |
将文件写入指定磁盘路径 |
getSize() |
获取文件大小(单位:字节) |
getBytes() |
获取文件内容的字节数组 |
getInputStream() |
获取文件内容的输入流 |
二、文章列表(条件分页)实现
1. 分页返回对象 PageBean
java
运行
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean<T> {
private Long total; // 总记录数
private List<T> items; // 当前页数据列表
}
2. Controller 层接口
java
运行
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/article")
public class ArticleController {
private final ArticleService articleService;
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
@GetMapping
public Result<PageBean<Article>> list(
@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(required = false) String categoryId,
@RequestParam(required = false) String state) {
PageBean<Article> pageBean = articleService.list(pageNum, pageSize, categoryId, state);
return Result.success(pageBean);
}
}
3. Service 层实现(使用 PageHelper)
java
运行
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class ArticleServiceImpl implements ArticleService {
private final ArticleMapper articleMapper;
public ArticleServiceImpl(ArticleMapper articleMapper) {
this.articleMapper = articleMapper;
}
@Override
public PageBean<Article> list(Integer pageNum, Integer pageSize, String categoryId, String state) {
// 1. 开启分页查询
PageHelper.startPage(pageNum, pageSize);
// 2. 从 ThreadLocal 获取当前用户ID
Map<String, Object> claims = ThreadLocalUtil.get();
Integer userId = (Integer) claims.get("id");
// 3. 执行条件查询
List<Article> articleList = articleMapper.list(userId, categoryId, state);
// 4. 封装分页结果
PageInfo<Article> pageInfo = new PageInfo<>(articleList);
PageBean<Article> pageBean = new PageBean<>();
pageBean.setTotal(pageInfo.getTotal());
pageBean.setItems(pageInfo.getList());
return pageBean;
}
}
4. Mapper 层(XML 动态 SQL)
xml
<mapper namespace="com.example.springboot.mapper.ArticleMapper">
<select id="list" resultType="com.example.springboot.pojo.Article">
select * from article
<where>
create_user = #{userId}
<if test="categoryId != null and categoryId != ''">
and category_id = #{categoryId}
</if>
<if test="state != null and state != ''">
and state = #{state}
</if>
</where>
</select>
</mapper>
三、关键注意事项
文件上传
- 路径硬编码不利于部署,建议通过配置文件读取。
- 目标目录如果不存在,
transferTo会抛出异常,必须提前创建。 - 使用 UUID 生成唯一文件名,避免文件覆盖。
分页查询
PageHelper.startPage(pageNum, pageSize)必须紧跟在查询语句之前,否则分页失效。- 必须加上
create_user = #{userId}条件,确保用户只能查询自己的文章。 categoryId和state是可选参数,使用@RequestParam(required = false)声明。
java
运行
//获取原始文件名
String originalFilename = file.getOriginalFilename();
//构造唯一文件名(uuid + 文件后缀)
String filename = UUID.randomUUID().toString() +
originalFilename.substring(originalFilename.lastIndexOf("."));
//存储文件
file.transferTo(new File("E:\\java项目\\files\\" + filename));
逐行解释
1. 获取原始文件名
java
运行
String originalFilename = file.getOriginalFilename();
意思: 用户上传的文件叫什么,我就拿到什么。比如用户传了一张 cat.jpg,这里得到的就是 "cat.jpg"。
2. 生成唯一文件名(最重要!)
java
运行
String filename = UUID.randomUUID().toString()
+ originalFilename.substring(
originalFilename.lastIndexOf(".")
);
拆成 3 部分讲:
① UUID.randomUUID().toString()
作用:生成一串全球唯一的随机字符串比如:
plaintext
550e8400-e29b-41d4-a716-446655440000
为什么要用? 防止两个人上传 同名文件 导致覆盖!
② originalFilename.lastIndexOf(".")
作用:找到文件名里最后一个 . 的位置
比如:cat.jpg → . 在索引 3 的位置
③ substring( 位置 )
作用:从 . 开始截取到最后 → 拿到文件后缀
cat.jpg → 截取后 → .jpg
✅ 拼起来最终效果:
plaintext
唯一字符串 + .后缀
例如:
plaintext
550e8400-e29b-41d4-a716-446655440000.jpg
3. 把文件保存到电脑磁盘
java
运行
file.transferTo( new File("E:\\java项目\\files\\" + filename) );
作用: 把前端传过来的文件,真正写入到服务器硬盘里。
路径示例:
plaintext
E:\java项目\files\550e8400-e29b-41d4-a716-446655440000.jpg
✨ 总结(3 句话记住)
- 获取原文件名 → 知道用户传的是什么文件
- 生成唯一文件名 → 避免文件重名覆盖
- 保存到硬盘 → 文件真正存下来
🎯 最关键的 2 个知识点
1. 为什么要用 UUID?
如果不用:
- A 传
a.jpg→ 存成a.jpg - B 也传
a.jpg→ 覆盖掉 A 的文件
用了:
- 每个人的文件名永远不会重复
2. 为什么要截取后缀?
因为要保留文件类型:
.jpg图片.png图片.mp4视频.pdf文件
如果你愿意,我还能给你讲:
- 怎么把文件存到阿里云 OSS(企业真实用法)
- 怎么访问上传后的图片
- 怎么限制文件大小、类型
你要继续吗?
与上文合并
📂 文件上传存储 完整代码 + 逐行解析
java
运行
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
public class FileUploadController {
// 读取配置文件中的文件存储路径,避免硬编码
@Value("${upload.base-path}")
private String basePath;
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws IOException {
// 校验空文件
if (file.isEmpty()){
return Result.error("文件不能为空");
}
// 1.获取原始文件名,包含后缀格式,例:photo.jpg
String originalFilename = file.getOriginalFilename();
if (originalFilename == null){
return Result.error("文件名异常");
}
// 2.拆分文件名与后缀,兼容无后缀文件
String suffix = "";
int index = originalFilename.lastIndexOf(".");
if(index > 0){
// 截取最后一个点之后的内容,拿到文件后缀
suffix = originalFilename.substring(index);
}
// 3.生成全新唯一文件名
// UUID生成不重复随机字符串,拼接后缀,防止同名文件相互覆盖
String filename = UUID.randomUUID().toString() + suffix;
// 4.判断并创建存储文件夹
File folder = new File(basePath);
if(!folder.exists()){
folder.mkdirs();
}
// 5.拼接完整文件路径,将文件写入本地磁盘保存
File targetFile = new File(basePath + filename);
file.transferTo(targetFile);
// 返回文件访问地址
String url = "http://localhost:8080/files/" + filename;
return Result.success(url);
}
}
配套 yml/properties 配置
properties
# 限制上传文件大小
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
# 文件存放目录
upload.base-path=E:/java项目/files/
核心代码释义
-
**getOriginalFilename()**获取用户本地上传的原始文件全名,包含后缀名,用于后续拆分格式。
-
lastIndexOf + substring定位文件名最后一个小数点,截取后缀部分,保留文件格式属性。
-
**UUID.randomUUID()**生成全局唯一字符串,拼接后缀组成新文件名,彻底解决文件重名覆盖问题。
-
**mkdirs()**检测存储目录是否存在,不存在则自动创建目录,避免保存时报错。
-
**transferTo()**接收前端上传的文件流,写入指定磁盘路径,完成文件持久化存储。