From 31599928a9404906b684003b9c52f129e2a6b0b2 Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Sun, 14 Jun 2026 13:06:38 +0800 Subject: [PATCH] feat: add upload file api --- .../app/controller/AppAuthController.java | 2 +- .../app/controller/AppFileController.java | 57 ++++++++++++ .../youlai/boot/app/model/vo/AppFileInfo.java | 23 +++++ .../boot/app/service/AppFileService.java | 30 ++++++ .../app/service/impl/LocalAppFileService.java | 93 +++++++++++++++++++ .../security/config/SecurityConfig.java | 2 +- 6 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/youlai/boot/app/controller/AppFileController.java create mode 100644 src/main/java/com/youlai/boot/app/model/vo/AppFileInfo.java create mode 100644 src/main/java/com/youlai/boot/app/service/AppFileService.java create mode 100644 src/main/java/com/youlai/boot/app/service/impl/LocalAppFileService.java diff --git a/src/main/java/com/youlai/boot/app/controller/AppAuthController.java b/src/main/java/com/youlai/boot/app/controller/AppAuthController.java index 0461ba1d..317a8aa0 100644 --- a/src/main/java/com/youlai/boot/app/controller/AppAuthController.java +++ b/src/main/java/com/youlai/boot/app/controller/AppAuthController.java @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.*; */ @Tag(name = "01.认证中心") @RestController -@RequestMapping("/api/v1/auth/app") +@RequestMapping("/api/v1/app/auth/") @RequiredArgsConstructor @Slf4j public class AppAuthController { diff --git a/src/main/java/com/youlai/boot/app/controller/AppFileController.java b/src/main/java/com/youlai/boot/app/controller/AppFileController.java new file mode 100644 index 00000000..7170d2d1 --- /dev/null +++ b/src/main/java/com/youlai/boot/app/controller/AppFileController.java @@ -0,0 +1,57 @@ +package com.youlai.boot.app.controller; + +import com.youlai.boot.app.model.vo.AppFileInfo; +import com.youlai.boot.app.service.AppFileService; +import com.youlai.boot.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件控制层 + * + * @author Ray.Hao + * @since 2022/10/16 + */ +@Tag(name = "10.文件接口") +@RestController +@RequestMapping("/api/v1/app/files") +@RequiredArgsConstructor +public class AppFileController { + + private final AppFileService fileService; + + @PostMapping + @Operation(summary = "文件上传") + public Result uploadFile( + @Parameter( + name = "file", + description = "表单文件对象", + required = true, + in = ParameterIn.DEFAULT, + schema = @Schema(name = "file", format = "binary") + ) + @RequestPart(value = "file") MultipartFile file + ) { + Assert.isTrue(!file.isEmpty(), "上传文件不能为空文件"); + AppFileInfo fileInfo = fileService.uploadFile(file); + return Result.success(fileInfo); + } + + @DeleteMapping + @Operation(summary = "文件删除") + @SneakyThrows + public Result deleteFile( + @Parameter(description = "文件路径") @RequestParam String filePath + ) { + boolean result = fileService.deleteFile(filePath); + return Result.judge(result); + } +} diff --git a/src/main/java/com/youlai/boot/app/model/vo/AppFileInfo.java b/src/main/java/com/youlai/boot/app/model/vo/AppFileInfo.java new file mode 100644 index 00000000..b0114d15 --- /dev/null +++ b/src/main/java/com/youlai/boot/app/model/vo/AppFileInfo.java @@ -0,0 +1,23 @@ +package com.youlai.boot.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + + +/** + * 文件信息对象 + * + * @author Ray.Hao + * @since 1.0.0 + */ +@Schema(description = "文件对象") +@Data +public class AppFileInfo { + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "文件URL") + private String url; + +} diff --git a/src/main/java/com/youlai/boot/app/service/AppFileService.java b/src/main/java/com/youlai/boot/app/service/AppFileService.java new file mode 100644 index 00000000..221b9b8e --- /dev/null +++ b/src/main/java/com/youlai/boot/app/service/AppFileService.java @@ -0,0 +1,30 @@ +package com.youlai.boot.app.service; + +import com.youlai.boot.app.model.vo.AppFileInfo; +import org.springframework.web.multipart.MultipartFile; + +/** + * 对象存储服务接口层 + * + * @author haoxr + * @since 2022/11/19 + */ +public interface AppFileService { + + /** + * 上传文件 + * @param file 表单文件对象 + * @return 文件信息 + */ + AppFileInfo uploadFile(MultipartFile file); + + /** + * 删除文件 + * + * @param filePath 文件完整URL + * @return 删除结果 + */ + boolean deleteFile(String filePath); + + +} diff --git a/src/main/java/com/youlai/boot/app/service/impl/LocalAppFileService.java b/src/main/java/com/youlai/boot/app/service/impl/LocalAppFileService.java new file mode 100644 index 00000000..9d51f739 --- /dev/null +++ b/src/main/java/com/youlai/boot/app/service/impl/LocalAppFileService.java @@ -0,0 +1,93 @@ +package com.youlai.boot.app.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import com.youlai.boot.app.model.vo.AppFileInfo; +import com.youlai.boot.app.service.AppFileService; +import com.youlai.boot.file.service.FileService; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.InputStream; +import java.time.LocalDateTime; + +/** + * 本地存储服务类 + * + * @author Theo + * @since 2024-12-09 17:11 + */ +@Data +@Slf4j +@Component +//@ConditionalOnProperty(value = "oss.type", havingValue = "local") +//@ConfigurationProperties(prefix = "oss.local") +@RequiredArgsConstructor +public class LocalAppFileService implements AppFileService { + + @Value("${oss.local.storage-path}") + private String storagePath; + + /** + * 上传文件方法 + * + * @param file 表单文件对象 + * @return 文件信息 + */ + @Override + public AppFileInfo uploadFile(MultipartFile file) { + // 获取文件名 + String originalFilename = file.getOriginalFilename(); + // 获取文件后缀 + String suffix = FileUtil.getSuffix(originalFilename); + // 生成uuid + String fileName = IdUtil.simpleUUID()+ "." + suffix;; + // 生成文件名(日期文件夹) + String folder = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN); + String filePrefix = storagePath.endsWith(File.separator) ? storagePath : storagePath + File.separator; + // try-with-resource 语法糖自动释放流 + try (InputStream inputStream = file.getInputStream()) { + // 上传文件 + FileUtil.writeFromStream(inputStream, filePrefix + folder + File.separator + fileName); + } catch (Exception e) { + log.error("文件上传失败", e); + throw new RuntimeException("文件上传失败"); + } + // 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀 + String fileUrl = File.separator + folder + File.separator + fileName; + AppFileInfo fileInfo = new AppFileInfo(); + fileInfo.setName(originalFilename); + fileInfo.setUrl(fileUrl); + return fileInfo; + } + + + /** + * 删除文件 + * @param filePath 文件完整URL + * @return 是否删除成功 + */ + @Override + public boolean deleteFile(String filePath) { + //判断文件是否为空 + if (filePath == null || filePath.isEmpty()) { + return false; + } + // 判断filepath是否为文件夹 + if (FileUtil.isDirectory(storagePath + filePath)) { + // 禁止删除文件夹 + return false; + } + // 删除文件 + return FileUtil.del(storagePath + filePath); + } +} diff --git a/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java b/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java index 20d5704d..5df0d6ad 100644 --- a/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java +++ b/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java @@ -71,7 +71,7 @@ public class SecurityConfig { // 移动设备专用接口路径(需要设备签名验证,但不需要用户登录) requestMatcherRegistry.requestMatchers("/api/v1/sn/**").permitAll(); - requestMatcherRegistry.requestMatchers("/api/v1/auth/app/**").permitAll(); + requestMatcherRegistry.requestMatchers("/api/v1/app/**").permitAll(); // 其他所有请求需登录后访问 requestMatcherRegistry.anyRequest().authenticated(); }