直接上代码,controller层代码:
@RestController
@RequestMapping("/common")
public class CommonController {private static final Logger log = LoggerFactory.getLogger(CommonController.class);@Resourceprivate ServerConfig serverConfig;private static final String FILE_DELIMETER = ",";/*** 通用上传请求(单个)*/@ApiOperation(value= "通用本地上传请求(单个)")@PostMapping("/upload")@ResponseBodypublic CommonResponse uploadFile(@RequestPart(name = "file") MultipartFile file) {try {// 上传文件路径String filePath = serverConfig.getProjectPath();// 上传并返回新文件名称String fileName = FileUploadUtils.upload(filePath, file);String url = serverConfig.getUrl() + fileName;JSONObject object = JSONUtil.createObj();object.putOpt("url", url);object.putOpt("fileName", fileName);object.putOpt("newFileName", FileUtils.getName(fileName));object.putOpt("originalFilename", file.getOriginalFilename());return CommonResponse.ok(object);} catch (Exception e) {return CommonResponse.fail(e.getMessage());}}/*** 通用上传请求(多个)*/@ApiOperation(value= "通用本地上传请求(多个)")@PostMapping("/uploads")@ResponseBodypublic CommonResponse uploadFiles(@RequestPart(name = "files") List<MultipartFile> files) {try {// 上传文件路径String filePath = serverConfig.getProjectPath();List<String> urls = new ArrayList<String>();List<String> fileNames = new ArrayList<String>();List<String> newFileNames = new ArrayList<String>();List<String> originalFilenames = new ArrayList<String>();for (MultipartFile file : files) {// 上传并返回新文件名称String fileName = FileUploadUtils.upload(filePath, file);String url = serverConfig.getUrl() + fileName;urls.add(url);fileNames.add(fileName);newFileNames.add(FileUtils.getName(fileName));originalFilenames.add(file.getOriginalFilename());}JSONObject object = JSONUtil.createObj();object.putOpt("urls", StringUtils.join(urls, FILE_DELIMETER));object.putOpt("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));object.putOpt("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));object.putOpt("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));return CommonResponse.ok(object);} catch (Exception e) {return CommonResponse.fail(e.getMessage());}}}
然后配置和工具类:
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/*** 服务相关配置**/
@Component
public class ServerConfig {/*** 获取完整的请求路径,包括:域名,端口,上下文访问路径** @return 服务地址*/public String getUrl() {HttpServletRequest request = ServletUtils.getRequest();return getDomain(request);}public static String getDomain(HttpServletRequest request) {StringBuffer url = request.getRequestURL();String contextPath = request.getServletContext().getContextPath();return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();}/*** 获取项目根路径** @return 路径地址*/public String getProjectPath() {try {Resource resource = new ClassPathResource("");String path = resource.getFile().getAbsolutePath();path = path.substring(0, path.indexOf("target") - 1);return path + Constants.RESOURCE_PREFIX;} catch (Exception e) {String path = Constants.RESOURCE_PREFIX;if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {path = "D:" + Constants.RESOURCE_PREFIX;}return path;}}}
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;/*** <p>* WebMvc配置类* </p>***/
@Import({GlobalExceptionHandler.class})
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Resourceprivate ServerConfig serverConfig;@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController(BaseConstant.WEBSOCKET_HTML).setViewName(BaseConstant.WEBSOCKET);registry.addViewController(BaseConstant.MAIL_HTML).setViewName(BaseConstant.MAIL);}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler(BaseConstant.DOC_HTML).addResourceLocations(BaseConstant.META_INF_RESOURCES);registry.addResourceHandler(BaseConstant.WEBJARS).addResourceLocations(BaseConstant.META_INF_RESOURCES_WEBJARS);String filePath = "file:"+serverConfig.getProjectPath();if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {registry.addResourceHandler("/upload/**").addResourceLocations(filePath);}else {registry.addResourceHandler("/upload/**").addResourceLocations(filePath);}}@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {// 添加参数解析器argumentResolvers.add(new SingleRequestBodyResolver());}/*** @param registry* @author quzhaodong* @date 2020/11/3**/@Overridepublic void addInterceptors(InterceptorRegistry registry) {}/*** 允许跨域请求*/@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration(BaseConstant.STAR_STAR, corsConfig());return new CorsFilter(source);}/*** <p>* Jackson全局转化long类型为String,解决jackson序列化时long类型缺失精度问题* </p>*/@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {Jackson2ObjectMapperBuilderCustomizer customizer = jacksonObjectMapperBuilder -> {jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
// jacksonObjectMapperBuilder.serializerByType(long.class, ToStringSerializer.instance);jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
// jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);//空值 null 进行序列化jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.ALWAYS);// 指定日期格式// 序列化jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 反序列化jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));};return customizer;}/*** 跨域配置*/private CorsConfiguration corsConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();// 请求常用的三种配置,*代表允许所有,当时你也可以自定义属性(比如header只能带什么,只能是post方式等等)corsConfiguration.addAllowedOriginPattern(BaseConstant.STAR);corsConfiguration.addAllowedHeader(BaseConstant.STAR);corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);corsConfiguration.addAllowedMethod(HttpMethod.GET);corsConfiguration.addAllowedMethod(HttpMethod.POST);corsConfiguration.addAllowedMethod(HttpMethod.PATCH);corsConfiguration.addAllowedMethod(HttpMethod.PUT);corsConfiguration.addAllowedMethod(HttpMethod.DELETE);corsConfiguration.setAllowCredentials(true);corsConfiguration.setMaxAge(3600L);return corsConfiguration;}@Beanpublic ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {return new ByteArrayHttpMessageConverter();}/*@Beanpublic CustomizationBean getCustomizationBean() {return new CustomizationBean();}*/}
上传工具类:
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;/*** 文件上传工具类** @author ruoyi*/
public class FileUploadUtils {/*** 根据文件路径上传** @param baseDir 相对应用的基目录* @param file 上传的文件* @return 文件名称* @throws IOException*/public static final String upload(String baseDir, MultipartFile file) throws IOException {try {return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);} catch (Exception e) {throw new IOException(e.getMessage(), e);}}/*** 文件上传** @param baseDir 相对应用的基目录* @param file 上传的文件* @param allowedExtension 上传文件类型* @return 返回上传成功的文件名* @throws IOException 比如读写文件出错时*/public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws IOException {String fileName = extractFilename(file);assertAllowed(file, allowedExtension);String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();file.transferTo(Paths.get(absPath));return getPathFileName(fileName);}/*** 编码文件名*/public static final String extractFilename(MultipartFile file) {return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),FilenameUtils.getBaseName(file.getOriginalFilename()), SnowFlakeManager.getSnowFlake().nextId(), getExtension(file));}public static final File getAbsoluteFile(String uploadDir, String fileName) {File desc = new File(uploadDir + File.separator + fileName);if (!desc.exists()) {if (!desc.getParentFile().exists()) {desc.getParentFile().mkdirs();}}return desc;}public static final String getPathFileName(String fileName) {return Constants.RESOURCE_PREFIX + fileName;
// int dirLastIndex = getDefaultBaseDir().length() + 1;
// String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
// if (StringUtils.isNotBlank(currentDir)) {
// return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
// }
// return Constants.RESOURCE_PREFIX + "/" + fileName;}/*** 文件校验** @param file 上传的文件* @return* @throws ServiceException*/public static final void assertAllowed(MultipartFile file, String[] allowedExtension)throws IOException {long size = file.getSize();String fileName = file.getOriginalFilename();String extension = getExtension(file);if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {throw new IOException("不支持文件:" + fileName + "的文件类型!");}}/*** 判断MIME类型是否是允许的MIME类型** @param extension* @param allowedExtension* @return*/public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {for (String str : allowedExtension) {if (str.equalsIgnoreCase(extension)) {return true;}}return false;}/*** 获取文件名的后缀** @param file 表单文件* @return 后缀名*/public static final String getExtension(MultipartFile file) {String extension = FilenameUtils.getExtension(file.getOriginalFilename());if (StringUtils.isEmpty(extension)) {extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));}return extension;}
常量类
/*** 通用常量信息**/
public class Constants
{/*** 资源映射路径 前缀*/public static final String RESOURCE_PREFIX = "/upload/";}
接下来讲一下思路:
1、首先我们是要把文件上传到项目的目录中,获取项目路径的方法是这个:
/*** 获取项目根路径** @return 路径地址*/public String getProjectPath() {try {Resource resource = new ClassPathResource("");String path = resource.getFile().getAbsolutePath();path = path.substring(0, path.indexOf("target") - 1);return path + Constants.RESOURCE_PREFIX;} catch (Exception e) {String path = Constants.RESOURCE_PREFIX;if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {path = "D:" + Constants.RESOURCE_PREFIX;}return path;}}
假如我们项目的路径是:D:/project/crm/admin,我们这里返回的路径就是D:/project/crm/admin/upload
2、文件上传,用到的方法是这个:
/*** 文件上传** @param baseDir 相对应用的基目录* @param file 上传的文件* @param allowedExtension 上传文件类型* @return 返回上传成功的文件名* @throws IOException 比如读写文件出错时*/public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws IOException {String fileName = extractFilename(file);assertAllowed(file, allowedExtension);String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();file.transferTo(Paths.get(absPath));return getPathFileName(fileName);}
上传方法就是 file.transferTo(Paths.get(absPath));
3、然后就是返回一个可以访问的路径,方法是:
//这里只返回图片的地址,不带项目的url,String url = serverConfig.getUrl() + fileName;public static final String getPathFileName(String fileName) {return Constants.RESOURCE_PREFIX + fileName;}
4。经过拼接之后,我们会发现,图片上传的位置是:
D:/project/crm/admin/upload/2025/05/05/1.jpg
返回的url是:http://127.0.0.1:8080/upload/2025/05/05/1.jpg
这样就可以访问了。为什么可以访问呢,是因为我们配置了静态资源的映射,具体配置为:
@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler(BaseConstant.DOC_HTML).addResourceLocations(BaseConstant.META_INF_RESOURCES);registry.addResourceHandler(BaseConstant.WEBJARS).addResourceLocations(BaseConstant.META_INF_RESOURCES_WEBJARS);String filePath = "file:"+serverConfig.getProjectPath();if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {registry.addResourceHandler("/upload/**").addResourceLocations(filePath);}else {registry.addResourceHandler("/upload/**").addResourceLocations(filePath);}}
这就是将文件上传到本地,并且返回一个url,可以正常访问图片。
如果你要匿名访问,需要在token的配置文件中设置/upload/**不需要token