【开发技巧】-- 什么你还在使用本地作为文件保存服务器?本文将带你了解,如何使用SpringBoot优雅的将文件上传至阿里云OSS、FastDFS(分布式文件系统)

Keelia ·
更新时间:2024-09-21
· 915 次阅读

1.1 业务背景

当今互联网项目,需求日渐增多,并且应用服务器的压力也日渐增大,这时就引入了分布式系统的概念,然后又有了动静分离,即动态资源与静态资源分开,使后端的应用服务器专注业务请求的处理,并降低因为请求静态资源而为应用服务器带来的压力。

1.2 文件上传的实现方式有哪些? 直接上传到应用服务器(缺点:增加应用服务器的压力)。 通过搭建私有云,比如通过FASTDFS搭建一个分布式文件系统。 使用第三方云存储(阿里云OSS、七牛云等)。 1.3 文件上传的实现 1.3.1 前置准备 1. 创建一个枚举类FileSourceEnum(用于后期实例化指定文件上传业务实现类) package com.qingyun.farm.enums; import lombok.Getter; @Getter public enum FileSourceEnum { LOCAL(1L,"LOCAL"), ALIYUN(2L,"ALIYUN"), FAST_DFS(3L,"FAST_DFS"); private Long code; private String desc; FileSourceEnum(Long code, String desc) { this.code=code; this.desc=desc; } } 2. 创建一个文件上传业务实现类创建实例工厂类FileServiceFactory package com.qingyun.farm.factory; import com.qingyun.farm.enums.FileSourceEnum; import com.qingyun.farm.service.FileService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; /** * Created with IntelliJ IDEA. * User: 李敷斌. * Date: 2020-04-02 * Time: 11:20 * Explain: 文件业务工厂类 */ @Component public class FileServiceFactory { private HashMap fileServiceMap=new HashMap(); @Autowired private FileService localFileServiceImpl; @Autowired private FileService aliyunFileServiceImpl; @Autowired private FileService fastdfsFileServiceImpl; @PostConstruct private void initFileService(){ fileServiceMap.put(FileSourceEnum.LOCAL,localFileServiceImpl); fileServiceMap.put(FileSourceEnum.ALIYUN,aliyunFileServiceImpl); fileServiceMap.put(FileSourceEnum.FAST_DFS,fastdfsFileServiceImpl); } public FileService getFileService(Long fileSourceCode) { if (fileSourceCode.equals(FileSourceEnum.FAST_DFS.getCode())){ return fileServiceMap.get(FileSourceEnum.FAST_DFS); }else if (fileSourceCode.equals(FileSourceEnum.ALIYUN.getCode())){ return fileServiceMap.get(FileSourceEnum.ALIYUN); } return fileServiceMap.get(FileSourceEnum.LOCAL); } } 3. 创建一个文件上传业务实现接口类FileService package com.qingyun.farm.service; import java.io.IOException; import com.qingyun.farm.model.FileInfo; import org.springframework.web.multipart.MultipartFile; public interface FileService { FileInfo upload(MultipartFile file) throws Exception; void delete(FileInfo fileInfo); } 4. 创建一个文件上传业务实现抽象父类AbstractFileService package com.qingyun.farm.service.impl; import com.qingyun.farm.dao.FileInfoDao; import com.qingyun.farm.enums.FileSourceEnum; import com.qingyun.farm.model.FileInfo; import com.qingyun.farm.service.FileService; import com.qingyun.farm.utils.FileUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.multipart.MultipartFile; @Slf4j public abstract class AbstractFileService implements FileService { protected abstract FileInfoDao getFileDao(); @Override public FileInfo upload(MultipartFile file) throws Exception { FileInfo fileInfo = FileUtil.getFileInfo(file); FileInfo oldFileInfo = getFileDao().getById(fileInfo.getId()); if (oldFileInfo != null) { return oldFileInfo; } if (!fileInfo.getName().contains(".")) { throw new IllegalArgumentException("缺少后缀名"); } uploadFile(file, fileInfo); fileInfo.setSource(fileSource().name());// 设置文件来源 getFileDao().save(fileInfo);// 将文件信息保存到数据库 log.info("上传文件:{}", fileInfo); return fileInfo; } /** * 文件来源 * * @return */ protected abstract FileSourceEnum fileSource(); /** * 上传文件 * * @param file * @param fileInfo */ protected abstract void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception; @Override public void delete(FileInfo fileInfo) { deleteFile(fileInfo); getFileDao().delete(fileInfo.getId()); log.info("删除文件:{}", fileInfo); } /** * 删除文件资源 * * @param fileInfo * @return */ protected abstract boolean deleteFile(FileInfo fileInfo); } 5 .创建一个文件上传请求控制器FileController package com.qingyun.farm.controller; import java.io.IOException; import java.util.List; import com.qingyun.farm.dao.FileInfoDao; import com.qingyun.farm.dto.LayuiFile; import com.qingyun.farm.factory.FileServiceFactory; import com.qingyun.farm.model.FileInfo; import com.qingyun.farm.page.table.PageTableHandler; import com.qingyun.farm.page.table.PageTableRequest; import com.qingyun.farm.page.table.PageTableResponse; import com.qingyun.farm.service.FileService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import com.qingyun.farm.annotation.LogAnnotation; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @Api(tags = "文件") @RestController @RequestMapping("/files") public class FileController { @Autowired private FileServiceFactory fileServiceFactory; @Autowired private FileInfoDao fileInfoDao; @LogAnnotation @PostMapping @ApiOperation(value = "文件上传") public FileInfo uploadFile(MultipartFile file,@RequestParam(value = "sourceCode",defaultValue = "1") Long sourceCode) throws IOException { try { return fileServiceFactory.getFileService(sourceCode).upload(file); } catch (Exception e) { e.printStackTrace(); } return null; } @LogAnnotation @DeleteMapping("/{id}") @ApiOperation(value = "文件删除") @RequiresPermissions("sys:file:del") public void delete(@PathVariable String id,Long sourceCode) { FileInfo fileInfo = fileInfoDao.getById(id); fileServiceFactory.getFileService(sourceCode).delete(fileInfo); } } 6. 创建一个文件上传工具类FileUtil,用于获取上传文件的信息,以及具体实现本地文件上传。 package com.qingyun.farm.utils; import com.qingyun.farm.model.FileInfo; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.time.LocalDate; import java.util.Date; public class FileUtil { public static FileInfo getFileInfo(MultipartFile file) throws Exception { String md5 = fileMd5(file.getInputStream()); FileInfo fileInfo = new FileInfo(); fileInfo.setId(md5);// 将文件的md5设置为文件表的id fileInfo.setName(file.getOriginalFilename()); fileInfo.setContentType(file.getContentType()); fileInfo.setIsImg(fileInfo.getContentType().startsWith("image/")?1L:0L); fileInfo.setSize(file.getSize()); fileInfo.setCreateTime(new Date()); return fileInfo; } /** * 文件的md5 * * @param inputStream * @return */ public static String fileMd5(InputStream inputStream) { try { return DigestUtils.md5Hex(inputStream); } catch (IOException e) { e.printStackTrace(); } return null; } public static String saveFile(MultipartFile file, String path) { try { File targetFile = new File(path); if (targetFile.exists()) { return path; } if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } file.transferTo(targetFile); return path; } catch (Exception e) { e.printStackTrace(); } return null; } public static boolean deleteFile(String pathname) { File file = new File(pathname); if (file.exists()) { boolean flag = file.delete(); if (flag) { File[] files = file.getParentFile().listFiles(); if (files == null || files.length == 0) { file.getParentFile().delete(); } } return flag; } return false; } public static String getPath() { return "/" + LocalDate.now().toString().replace("-", "/") + "/"; } /** * 将文本写入文件 * * @param value * @param path */ public static void saveTextFile(String value, String path) { FileWriter writer = null; try { File file = new File(path); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } writer = new FileWriter(file); writer.write(value); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static String getText(String path) { File file = new File(path); if (!file.exists()) { return null; } try { return getText(new FileInputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } public static String getText(InputStream inputStream) { InputStreamReader isr = null; BufferedReader bufferedReader = null; try { isr = new InputStreamReader(inputStream, "utf-8"); bufferedReader = new BufferedReader(isr); StringBuilder builder = new StringBuilder(); String string; while ((string = bufferedReader.readLine()) != null) { string = string + "\n"; builder.append(string); } return builder.toString(); } catch (IOException e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (isr != null) { try { isr.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } }

以上就是文件上传业务实现的准备工作,可以根据具体业务修改相关代码实现。

1.3.2 通过SpringMVC直接将文件上传至应用服务器 1. 创建本地文件上传业务实现类 package com.qingyun.farm.service.impl; import java.io.IOException; import java.time.LocalDate; import com.qingyun.farm.enums.FileSourceEnum; import com.qingyun.farm.model.FileInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import com.qingyun.farm.dao.FileInfoDao; import com.qingyun.farm.service.FileService; import com.qingyun.farm.utils.FileUtil; @Service public class LocalFileServiceImpl extends AbstractFileService { @Autowired private FileInfoDao fileInfoDao; @Override protected FileInfoDao getFileDao() { return fileInfoDao; } @Value("${file.local.urlPrefix}") private String urlPrefix; /** * 上传文件存储在本地的根路径 */ @Value("${file.local.path}") private String localFilePath; @Override protected FileSourceEnum fileSource() { return FileSourceEnum.LOCAL; } @Override protected void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception { int index = fileInfo.getName().lastIndexOf("."); // 文件扩展名 String fileSuffix = fileInfo.getName().substring(index); String suffix = "/" + LocalDate.now().toString().replace("-", "/") + "/" + fileInfo.getId() + fileSuffix; String path = localFilePath + suffix; String url = urlPrefix + suffix; fileInfo.setPath(path); fileInfo.setUrl(url); FileUtil.saveFile(file, path); } @Override protected boolean deleteFile(FileInfo fileInfo) { return FileUtil.deleteFile(fileInfo.getPath()); } } 2. 配置上传文件的保存路径 file: local: path: /Users/qingyun/IdeaProjects/SmartAgriculture/src/main/resources/static/upload prefix: /upload urlPrefix: http://localhost:8088/${file.local.prefix} 1.3.3 使用FASTDFS搭建私有云实现文件上传。 1. 下载fastdfs-client,并将它安装到maven仓库中【fastdfs-client-java:点击链接即可下载, 密码: ol7r】 2. 引入fastdfs-client所需maven依赖 org.csource fastdfs-client-java 1.29-SNAPSHOT 3. 创建一个fastdfs配置文件,tracker.conf

在这里插入图片描述

tracker_server=192.168.69.139:22122 # 连接超时时间,针对socket套接字函数connect,默认为30秒 connect_timeout=30000 # 网络通讯超时时间,默认是60秒 network_timeout=60000 4. 创建一个Fastdfs文件上传客户端配置类 package com.qingyun.farm.config; import org.csource.fastdfs.ClientGlobal; import org.csource.fastdfs.StorageClient; import org.csource.fastdfs.TrackerClient; import org.csource.fastdfs.TrackerServer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created with IntelliJ IDEA. * User: 李敷斌. * Date: 2020-02-26 * Time: 17:57 * Explain: 文件上传配置类 */ @Configuration @ConditionalOnProperty(prefix = "file.fastdfs.tracker.config",name = "path",havingValue = "/tracker.conf") public class FastDFSUploadConfig { @Value(value = "${file.fastdfs.tracker.config.path}") private String trackerConfigPath; @Bean public StorageClient storageClient(){ TrackerClient trackerClient=null; TrackerServer trackerServer=null; try { String tracker = FastDFSUploadConfig.class.getResource(trackerConfigPath).getPath(); //初始化 ClientGlobal.init(tracker); //创建trackerClient trackerClient=new TrackerClient(); //通过client获取service trackerServer=trackerClient.getTrackerServer(); }catch (Exception e){ } //以trackerservice为参数 构建storageclient return new StorageClient(trackerServer,null); } } 5. 创建fastdfs文件上传业务实现类 package com.qingyun.farm.service.impl; import com.qingyun.farm.dao.FileInfoDao; import com.qingyun.farm.enums.FileSourceEnum; import com.qingyun.farm.model.FileInfo; import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; import org.csource.fastdfs.StorageClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; /** * Created with IntelliJ IDEA. * User: 李敷斌. * Date: 2020-04-02 * Time: 11:58 * Explain: fastdfs文件上传 */ @Service @Slf4j public class FastdfsFileServiceImpl extends AbstractFileService { @Autowired private StorageClient storageClient; @Autowired private FileInfoDao fileInfoDao; @Value("${file.fastdfs.domain}") private String fastdfs_domain; @Override protected FileInfoDao getFileDao() { return fileInfoDao; } /** * 文件来源 * * @return */ @Override protected FileSourceEnum fileSource() { return FileSourceEnum.FAST_DFS; } /** * 上传文件 * * @param multipartFile * @param fileInfo */ @Override protected void uploadFile(MultipartFile multipartFile, FileInfo fileInfo) throws Exception { //获取文件后缀名 String sourceFileName = multipartFile.getOriginalFilename(); //获取后缀名 String suffixName = sourceFileName.substring(sourceFileName.lastIndexOf(".")+1); String[] uploadFileResult=null; try { //上传文件 uploadFileResult = storageClient.upload_file(multipartFile.getBytes(), suffixName, null); } catch (Exception e){ log.error("【文件上传】获取二进制数据失败,ex.msg={}",e.getMessage()); } String url="http://"+fastdfs_domain+getUrl(uploadFileResult); fileInfo.setUrl(url); log.debug("【文件上传】文件访问路径,imageUrl={}",url); } private String getUrl(String[] uploadFileResult){ StringBuffer sbf=new StringBuffer(); for (String item : uploadFileResult) { sbf.append("/"+item); } return sbf.toString(); } /** * 删除文件资源 * * @param fileInfo * @return */ @Override protected boolean deleteFile(FileInfo fileInfo) { try { return storageClient.delete_file("group1",fileInfo.getName())>0; } catch (IOException e) { e.printStackTrace(); } catch (MyException e) { e.printStackTrace(); } return false; } } 6. 在配置文件中,配置好fastdfs文件上传业务有关配置信息 file: fastdfs: tracker: config: path: /tracker.conf domain: 192.168.69.139

fastdfs文件上传至此以及完整地实现了。

1.3.4 使用阿里云OSS实现文件上传 1. 引入阿里云OSS实现文件上传所需maven依赖 com.aliyun.oss aliyun-sdk-oss ${aliyun-sdk-oss.version} com.aliyun aliyun-java-sdk-core ${aliyun-sdk-core.version} com.aliyun aliyun-java-sdk-dysmsapi ${aliyun-sdk-dysmsapi.version}

对应版本信息:

2.8.2 3.2.8 1.1.0 2. 在配置文件中,配置阿里云OSS文件上传相关配置 file: aliyun: endpoint: xxx accessKeyId: xxx accessKeySecret: xxx bucketName: xxx domain: xxx 3. 写一个阿里云OSS文件上传配置类 package com.qingyun.farm.config; import com.aliyun.oss.OSSClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.URL; import java.util.Date; @Configuration public class AliyunConfig { @Value("${file.aliyun.endpoint}") private String endpoint; @Value("${file.aliyun.accessKeyId}") private String accessKeyId; @Value("${file.aliyun.accessKeySecret}") private String accessKeySecret; /** * 阿里云文件存储client * */ @Bean public OSSClient ossClient() { OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret); return ossClient; } public static void main(String[] args) throws FileNotFoundException { OSSClient ossClient = new OSSClient("oss-cn-beijing.aliyuncs.com", "LTAI3jTQMjLamd0v", "aOR1ZFUoJCKmiSUUQopZcwZDu0uei6"); InputStream inputStream = new FileInputStream("D://ssfw.sql"); ossClient.putObject("topwulian", "upload/" + "ss11fw.sql", inputStream); Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10); // 生成URL URL url = ossClient.generatePresignedUrl("topwulian", "upload/" + "ss11fw.sql", expiration); System.out.println(url); } } 4. 创建阿里云OSS文件上传业务实现类 package com.qingyun.farm.service.impl; import com.aliyun.oss.OSSClient; import com.qingyun.farm.dao.FileInfoDao; import com.qingyun.farm.enums.FileSourceEnum; import com.qingyun.farm.model.FileInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; /** * 阿里云存储文件 * * @author 小威老师 * */ @Service("aliyunFileServiceImpl") public class AliyunFileServiceImpl extends AbstractFileService { @Autowired private FileInfoDao fileInfoDao; @Override protected FileInfoDao getFileDao() { return fileInfoDao; } @Override protected FileSourceEnum fileSource() { return FileSourceEnum.ALIYUN; } @Autowired private OSSClient ossClient; @Value("${file.aliyun.bucketName}") private String bucketName; @Value("${file.aliyun.domain}") private String domain; @Override protected void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception { ossClient.putObject(bucketName, fileInfo.getName(), file.getInputStream()); fileInfo.setUrl(domain + "/" + fileInfo.getName()); } @Override protected boolean deleteFile(FileInfo fileInfo) { ossClient.deleteObject(bucketName, fileInfo.getName()); return true; } }

至此三种文件上传方式整合完毕,如果文章对你有帮助的话请给我点个赞哦,也可以关注博主我将持续更新一些组件使用教程❤️


作者:DomainExpert



阿里云oss springboot 服务器 技巧 分布式 分布 系统 fastdfs oss 阿里 文件上传

需要 登录 后方可回复, 如果你还没有账号请 注册新账号