在当前数字化时代,文件上传功能是各类Web应用中不可或缺的一部分。本文将为您展示如何利用Spring Boot框架及其优秀生态构建一个优雅且安全的文件上传服务——`UploadFileServiceImp`。此服务不仅实现了批量处理上传请求、确保文件安全存储,还通过JWT令牌提取用户ID,并将文件访问地址妥善保存至数据库中。
首先,我们关注`UploadFileServiceImp`的核心实现逻辑。该类作为服务接口`UploadFileService`的实现,注入了HttpServletRequest对象以捕获客户端请求中的重要信息,如JWT令牌。通过Hutool库解析令牌,获取并转换用户的唯一标识(ID),确保文件上传与用户身份紧密关联,提高了系统安全性。
代码中使用了 请导包使用此代码
hutool包
lombok包
uploadFilesCollectionImp Collection实现类
@Slf4j
@RestController
@RequestMapping("/upload")
public class uploadFilesCollectionImp {
private final uploadFileServiceImp uploadFileServiceImp;
public uploadFilesCollectionImp(uploadFileServiceImp uploadFileServiceImp) {
this.uploadFileServiceImp = uploadFileServiceImp;
}
/**
* 处理POST方法的多文件上传请求,接收一个MultipartFile类型的数组参数。
*
* @param files 客户端提交的待上传文件集合
* @return Result 对象封装了上传结果信息。如果所有文件上传成功,则返回包含每个文件完整访问地址的Result.success对象;
* 若有文件上传失败,则返回Result.error对象并附带错误提示信息。
*/
@PostMapping
public R upload(@RequestParam("files") MultipartFile[] files) {
try {
List<String> uploadFileUrls = uploadFileServiceImp.uploadFile(files);
// 如果上传成功,则返回包含文件URL的R.success对象
if (!CollectionUtils.isEmpty(uploadFileUrls)) {
return R.success(uploadFileUrls);
}
// 如果上传失败且uploadFileUrls为空(假设这代表失败)
return R.error("文件上传失败");
} catch (RuntimeException e) {
// 捕获自定义异常,如文件上传过程中可能出现的问题
log.error("文件上传时发生错误:{}", e.getMessage());
return R.error("文件上传失败:" + e.getMessage());
} catch (Exception e) {
// 其他未知异常
log.error("文件上传时发生未知错误:{}", e.getMessage());
return R.error("文件上传过程中出现未知错误");
}
}
}
UploadFileService Service接口层
public interface UploadFileService {
/**
* 批量上传文件方法。该方法接收一个MultipartFile数组,代表用户要上传的多个文件,
* 并返回一个包含所有成功上传文件访问链接地址的字符串列表。
*
* @param files MultipartFile[] 用户选择并提交的多个待上传文件对象
* @return List<String> 成功上传文件的网络访问链接地址列表,每个地址对应一个上传成功的文件
*/
List<String> uploadFile(MultipartFile[] files);
}
UploadFileService Service实现类
@Slf4j
@Service
public class uploadFileServiceImp implements UploadFileService {
// Spring自动注入HttpServletRequest对象以获取请求信息
@Autowired
private HttpServletRequest request;
// 注入自定义工具类FileUtils实例,用于进行文件操作
@Autowired
private FileUtils fileUtils;
// 注入上传链接地址服务实现类,用于保存上传文件的访问链接
@Autowired
private uploadLinkAddressServiceImp uploadLinkAddressServiceImp;
// 配置文件中读取上传服务的基础URL
private String "8080";
// 配置读取HTTP协议头
private String "http://";
/**
* 批量处理文件上传请求,并返回上传成功文件的详细访问地址列表。
*
* @param files 用户上传的MultipartFile数组
* @return List<String> 包含已上传文件完整访问地址的列表
*/
@Override
public List<String> uploadFile(MultipartFile[] files) {
Object id;
Path path;
List<String> uploadedFileDetails = new ArrayList<>();
try {
// 解析请求头中的token,提取用户ID
String token = request.getHeader("token");
JWT jwt = JWTUtil.parseToken(token);
// 确保JWT类型正确及包含"chao"字段(此处未使用)
jwt.getHeader(JWTHeader.TYPE);
jwt.getPayload("chao");
// 提取用户ID并转换为字符串
id = jwt.getPayload("id");
if (id instanceof Number) {
id = String.valueOf(((Number) id).longValue()); // 根据实际数据类型选择合适的方法
} else if (id != null) {
id = id.toString();
}
} catch (Exception e) {
log.error("解析或获取token中的用户ID时出错: {}", e.getMessage());
uploadedFileDetails.add("上传文件获取token失败");
return uploadedFileDetails;
}
// 遍历所有待上传的文件
for (MultipartFile file : files) {
if (Objects.isNull(file) || file.isEmpty()) {
// 如果有空文件,则终止上传并返回错误提示
uploadedFileDetails.add("存在空文件,请重新上传所有文件");
return uploadedFileDetails;
}
try {
// 获取文件内容字节数组
byte[] bytes = file.getBytes();
// 获取当前服务器IP地址
String serverIp = GetServiceIp.getServerIp();
// 设置目标文件存储路径(基于用户ID)
path = Paths.get(fileUtils.getUploadFolder((String) id));
// 确保目录存在,如果不存在则创建
if (!Files.exists(path)) {
Files.createDirectories(path);
}
// 获取上传文件的扩展名
String extension = FileUtils.getFileExtension(file);
// 生成新的唯一文件名(UUID+扩展名)
String newFileName = IdUtil.simpleUUID() + "." + extension;
// 将文件保存到指定目录下
FileUtils.getFileByBytes(bytes, fileUtils.getUploadFolder((String) id), newFileName);
// 构建文件的内部访问路径
String fullFilePath = "/picture/" + id + "/" + newFileName;
// 添加上传后的文件访问地址到结果列表
uploadedFileDetails.add(fullFilePath);
// 将文件访问地址保存到数据库或其他持久化存储中
uploadLinkAddressServiceImp.addLinkAddress((String) id, fullFilePath);
} catch (Exception e) {
log.error("上传文件[{}]时发生错误: {}", file.getOriginalFilename(), e.getMessage());
uploadedFileDetails.add("部分文件上传失败,请检查后重试");
return uploadedFileDetails;
}
}
// 检查上传成功的文件数量与原始提交文件总数是否一致
if (uploadedFileDetails.size() == files.length) {
log.info("所有文件上传成功");
// 返回上传成功的文件访问地址列表
} else {
int failedCount = files.length - uploadedFileDetails.size();
log.error("{}个文件上传失败", failedCount);
uploadedFileDetails.add("部分文件上传失败");
}
return uploadedFileDetails;
}
}
UploadLinkAddressCollection Collection接口
public interface UploadLinkAddressCollection {
R linAddressAll();
R deleteById(Integer id);
}
UploadLinkAddressCollection Collection实现类
// 设置该类下所有方法的统一基础路径
@Slf4j
@RestController
public class uploadLinkAddressCollectionImp implements UploadLinkAddressCollection {
@Autowired
private uploadLinkAddressServiceImp uploadLinkAddressServiceImp; // 上传链接地址业务服务实例
/**
* 处理查询所有上传链接地址的POST请求
*
* @return R 对象,包含查询结果信息
*/
@PostMapping("/uploadList")
@Log // 使用自定义的日志注解记录该方法调用
public R linAddressAll() {
// 调用业务层方法获取所有文件链接地址信息
List<FileLinkAddress> fileLinkAddresses = uploadLinkAddressServiceImp.listByID();
// 判断查询结果是否为空
if (!CollectionUtils.isEmpty(fileLinkAddresses)) {
// 如果查询到数据,则返回成功状态并携带查询结果
log.info("查询当前用户照片成功");
return R.success(fileLinkAddresses);
}
// 若查询不到数据,则返回失败状态及提示信息
log.info("查询不到当前用户照片");
return R.error("查询不到当前用户的照片");
}
@DeleteMapping("/deleteId/{id}")
public R deleteById(@PathVariable Integer id) {
int i = uploadLinkAddressServiceImp.deleteById(id);
if (i != 0) {
return R.success();
}
return R.error("删除失败,没有此图片");
}
}
UploadLinkAddressService service接口
public interface UploadLinkAddressService {
/**
* 添加新的文件链接地址到数据库中。这个方法会将指定的用户(通过name字段标识)和其上传文件的访问链接关联起来。
*
* @param name 用户名或用户ID,用于标识文件所属的用户
* @param addressLink 文件的网络访问链接地址
*/
void addLinkAddress(String name, String addressLink);
/**
* 根据用户ID查询该用户的所有已上传文件链接地址列表。
*
* @return List<FileLinkAddress> 包含所有与给定用户ID相关联的文件链接地址信息的对象列表
*/
List<FileLinkAddress> listByID();
int deleteById(Integer id);
}
FileUtils 类
/**
* 此类提供文件操作相关的工具方法,包括:
* 1. 将本地文件转换成字节数组
* 2. 根据字节数组创建并保存文件
* 3. 获取MultipartFile对象的文件后缀名
*/
@Slf4j
@Component
public class FileUtils {
/**
* 配置默认上传文件的本地存储路径(示例为Windows环境下的路径)
* 实际应用中可能需要配置成服务器上的路径。
*/
public String "D:/picture";
public static byte[] getBytesByFile(String pathStr) {
// 获取文件对象
File file = new File(pathStr);
// 获取文件输入流
try (FileInputStream fis = new FileInputStream(file);
// 创建字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream(2048)) {
// 创建字节数组
byte[] buffer = new byte[2048];
int n;
while ((n = fis.read(buffer)) != -1) {
bos.write(buffer, 0, n);
}
return bos.toByteArray();
} catch (IOException e) {
log.error(String.valueOf(e));
return null;
}
}
/**
* 将字节数组写入到指定目录下,并以给定的文件名创建新文件
*
* @param bytes 要写入文件的字节数组数据
* @param filePath 目标文件所在目录的路径
* @param fileName 新建文件的名称(不含扩展名)
*/
public static void getFileByBytes(byte[] bytes, String filePath, String fileName) {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file;
try {
// 确保目标目录存在,不存在则创建
File dir = new File(filePath);
if (!dir.exists()) {
dir.mkdirs();
}
// 构建完整的文件路径
file = new File(filePath + "\\" + fileName);
// 创建输出流并将字节写入到文件
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bytes);
} catch (Exception e) {
log.error(String.valueOf(e));
} finally {
// 关闭输出流
try {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
log.error(String.valueOf(e));
}
}
}
/**
* 从MultipartFile对象中获取原始文件的扩展名
*
* @param file Spring框架提供的用于处理上传文件的对象
* @return 原始文件的扩展名(不包含点),例如 ".jpg" 或 ".txt"
*/
public static String getFileExtension(MultipartFile file) {
String originalFileName = file.getOriginalFilename();
// 使用Objects.requireNonNull防止空指针异常,并截取扩展名部分
return Objects.requireNonNull(originalFileName).substring(originalFileName.lastIndexOf("."));
}
public String getUploadFolder(String id) {
return UPLOAD_FOLDER + "/" + id;
}
}
UploadLinkAddressService service实现类
@Service
@Slf4j
public class uploadLinkAddressServiceImp implements UploadLinkAddressService {
// Spring自动注入HttpServletRequest对象以获取请求信息
@Autowired
private HttpServletRequest request;
// 注入UploadAddressLinkMapper实例,用于操作数据库
@Autowired
private UploadAddressLinkMapper uploadLinkAddressMapper;
/**
* 添加新的文件访问链接到数据库,并关联给定的用户名(或用户ID)。
*
* @param name 用户名或用户ID标识符
* @param addressLink 上传文件的访问链接
*/
@Override
@Log
public void addLinkAddress(String name, String addressLink) {
// 将链接、名称和当前时间戳保存到数据库
uploadLinkAddressMapper.addressLink(name, addressLink, LocalDateTime.now());
}
/**
* 根据用户ID查询该用户所有已上传文件的链接地址列表。
*
* @return List<FileLinkAddress> 包含用户上传文件链接地址信息的对象列表
*/
@Override
public List<FileLinkAddress> listByID() {
Object id;
try {
// 解析请求头中的JWT token,提取用户ID
JWT jwt = JWTUtil.parseToken(request.getHeader("token"));
jwt.getHeader(TYPE);
jwt.getPayload("chao");
// 提取并转换用户ID为字符串
id = jwt.getPayload("id");
if (id instanceof Number) {
id = String.valueOf(((Number) id).longValue()); // 根据实际数据类型选择合适的方法
} else if (id != null) {
id = id.toString();
}
} catch (Exception e) {
log.error("解析或获取token中的用户ID时出错: {}", e.getMessage());
return null; // 或者抛出异常,此处假设返回null表示处理失败
}
// 查询并返回该用户的所有文件链接地址记录
return uploadLinkAddressMapper.listById((String) id);
}
/**
* 根据id删除数据
*
* @param id 要删除的数据的id
* @return 删除的行数
*/
@Override
public int deleteById(Integer id) {
return uploadLinkAddressMapper.deleteById(id);
}
}
R 实现类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class R {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static @NotNull R success() {
return new R(1, "success" , null);
}
//查询 成功响应
public static @NotNull R success(Object data) {
return new R(1, "success" , data);
}
//失败响应
public static @NotNull R error(String msg) {
return new R(0, msg, null);
}
}
UploadAddressLinkMapper Mapper层
@Mapper
public interface UploadAddressLinkMapper {
@Insert("insert into fileLink (name, address, create_time)values (#{name},#{address},#{createTime})")
void addressLink(String name, String address, LocalDateTime createTime);
@Select("select * from fileLink where name=#{id};")
List<FileLinkAddress> listById(String id);
@Delete("delete from filelink where id =#{id};")
int deleteById(Integer id);
}
注意:在数据库建一个 fileLink 表来存储上传文件的路经用户姓名(用户名是JWT中的token获取到的),地址,上传时间
create table if not exists filelink
(
id int auto_increment
primary key,
name varchar(1000) not null,
address varchar(500) not null,
create_time datetime null
);
在批量处理文件上传的过程中,服务采取了细致入微的错误处理策略。当接收到MultipartFile数组时,会逐一检查每个文件的有效性。若发现空文件,则立即停止上传流程并反馈给调用方。对于每一个待上传的文件,服务执行以下关键步骤:
获取文件内容字节数组。
根据用户ID生成并确保目标存储路径存在,创建必要的目录结构。
为文件生成一个独特的名称,结合UUID和文件扩展名。
使用自定义工具类
FileUtils
将文件内容写入服务器指定位置。构建内部访问路径,添加至已上传文件的详细访问地址列表。
将生成的文件访问地址持久化存储至数据库中,便于后续检索和管理。
在完成所有文件上传后,服务会对上传成功的文件数量进行校验,如果与原始提交文件总数一致,则输出成功消息;反之则记录失败次数并提醒用户部分文件上传失败。 总结来说,本篇所探讨的`UploadFileServiceImp`实现在提供高效稳定的文件上传功能的同时,兼顾了安全性与数据一致性,展示了Spring Boot在实际开发场景下的强大威力。通过巧妙地整合JWT令牌验证、自定义工具类以及数据库操作,这一服务组件无疑为您的项目增添了独特魅力与实用价值。
版权归原作者 青山烟雨客:-) 所有, 如有侵权,请联系我们删除。