spring boot集成FastDFS
一、首先,maven工程添加依赖
<!--fastdfs--> <dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-RELEASE</version> </dependency>
如果报错请参考maven仓库有jar包还是报错怎么办?
二、resources目录下添加fdfs_client.conf文件
前面使用nginx支持http方式访问文件,但所有人都能直接访问这个文件服务器了,所以做一下权限控制。
从来都是我能借用别人的图片,而别人借用我的图片不好意思不行!
FastDFS的权限控制是在服务端开启token验证,客户端根据文件名、当前unix时间戳、秘钥获取token,在地址中带上token参数即可通过http方式访问文件。
#################### FastDFS-Client Start ####################
#默认值为30s
connect_timeout = 10
#默认值为30s
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
# token 防盗链功能
http.anti_steal_token = true
http.secret_key = FastDFS1234567890
tracker_server = 192.168.1.118:22122
#tracker_server = 192.168.0.119:22122
## Tracker Server, if more than one, separate with ","
# fastdfs.tracker_servers=10.0.11.201:22122,10.0.11.202:22122,10.0.11.203:22122
#fastdfs.tracker_servers=${tracker_server_addr}:22122
#################### FastDFS-Client End ####################
对应文件服务器上/etc/fdfs下的http.conf
修改http.conf # vim /etc/fdfs/http.conf 设置为true表示开启token验证 http.anti_steal.check_token=true 设置token失效的时间单位为秒(s) http.anti_steal.token_ttl=1800 密钥,跟客户端配置文件的fastdfs.http_secret_key保持一致 http.anti_steal.secret_key=FASTDFS1234567890 如果token检查失败,返回的页面 http.anti_steal.token_check_fail=/ljzsg/fastdfs/page/403.html
记得重启服务
三、客户端生成token
访问文件需要带上生成的token以及unix时间戳,所以返回的token是token和时间戳的拼接。
之后,将token拼接在地址后即可访问:file.ljzsg.com/group1/M00/00/00/wKgzgFnkaXqAIfXyAAEoRmXZPp878.jpeg?token=078d370098b03e9020b82c829c205e1f&ts=1508141521
/** * 获取访问服务器的token,拼接到地址后面 * * @param filepath 文件路径 group1/M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg * @param httpSecretKey 密钥 * @return 返回token,如: token=078d370098b03e9020b82c829c205e1f&ts=1508141521 */ public static String getToken(String filepath, String httpSecretKey){ // unix seconds int ts = (int) Instant.now().getEpochSecond(); // token String token = "null"; try { token = ProtoCommon.getToken(getFilename(filepath), ts, httpSecretKey); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (MyException e) { e.printStackTrace(); } StringBuilder sb = new StringBuilder(); sb.append("token=").append(token); sb.append("&ts=").append(ts); return sb.toString(); }
注意事项
如果生成的token验证无法通过,请进行如下两项检查:
A. 确认调用token生成函数(ProtoCommon.getToken),传递的文件ID中没有包含group name。传递的文件ID格式形如:M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg
B. 确认服务器时间基本是一致的,注意服务器时间不能相差太多,不要相差到分钟级别。
对比下发现,如果系统文件隐私性较高,可以直接通过fastdfs-client提供的API去访问即可,不用再配置Nginx走http访问。配置Nginx的主要目的是为了快速访问服务器的文件(如图片),如果还要加权限验证,则需要客户端生成token,其实已经没有多大意义。关键是,这里我没找到FastDFS如何对部分资源加token验证,部分开放。有知道的还请留言。
四、封装FastDFS上传工具类
import org.csource.common.NameValuePair; import org.csource.fastdfs.*; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * @Auther: lanhaifeng * @Date: 2019/4/25 0025 09:11 * @Description:文件上传工具类 */ public class FastDFSClient { private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class); static { try { String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();; ClientGlobal.init(filePath); } catch (Exception e) { logger.error("FastDFS Client Init Fail!",e); } } /** * 文件上传 * @param file 自定义文件上传类 * @return */ public static String[] upload(FastDFSFile file) { logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length); NameValuePair[] meta_list = new NameValuePair[1]; meta_list[0] = new NameValuePair("author", file.getAuthor()); long startTime = System.currentTimeMillis(); String[] uploadResults = null; StorageClient storageClient=null; try { storageClient = getTrackerClient(); //upload_file()三个参数:@param fileContent ①:文件的内容,字节数组 ②:文件扩展名 ③文件扩展信息 数组 uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list); } catch (IOException e) { logger.error("IO Exception when uploadind the file:" + file.getName(), e); } catch (Exception e) { logger.error("Non IO Exception when uploadind the file:" + file.getName(), e); } logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms"); if (uploadResults == null && storageClient!=null) { logger.error("upload file fail, error code:" + storageClient.getErrorCode()); } String groupName = uploadResults[0]; String remoteFileName = uploadResults[1]; logger.info("upload file successfully!!!" + "group_name:" + groupName + ", remoteFileName:" + " " + remoteFileName); return uploadResults; } /** * 查询文件信息 * @param groupName * @param remoteFileName * @return */ public static FileInfo getFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getTrackerClient(); return storageClient.get_file_info(groupName, remoteFileName); } catch (IOException e) { logger.error("IO Exception: Get File from Fast DFS failed", e); } catch (Exception e) { logger.error("Non IO Exception: Get File from Fast DFS failed", e); } return null; } /** * 下载文件 * @param groupName 文件路径 * @param remoteFileName 输出流 中包含要输出到磁盘的路径 * @return */ public static InputStream downFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getTrackerClient(); byte[] fileByte = storageClient.download_file(groupName, remoteFileName); InputStream ins = new ByteArrayInputStream(fileByte); return ins; } catch (IOException e) { logger.error("IO Exception: Get File from Fast DFS failed", e); } catch (Exception e) { logger.error("Non IO Exception: Get File from Fast DFS failed", e); } return null; } /** * 删除文件 * ==0表示成功 * @param groupName 组名 如:group1 * @param remoteFileName 不带组名的路径名称 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg * @throws Exception */ public static int deleteFile(String groupName, String remoteFileName) throws Exception { StorageClient storageClient = getTrackerClient(); return storageClient.delete_file(groupName, remoteFileName); } /** * 获取storage * @param groupName 组名 * @return * @throws IOException */ public static StorageServer[] getStoreStorages(String groupName) throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerClient.getStoreStorages(trackerServer, groupName); } /** * * @param groupName * @param remoteFileName * @return * @throws IOException */ public static ServerInfo[] getFetchStorages(String groupName,String remoteFileName) throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName); } /** * 获取reacker地址 * @return * @throws IOException */ public static String getTrackerUrl() throws IOException { return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":"+ ClientGlobal.getG_tracker_http_port()+"/"; } /** * 获取tracker连接 * @return * @throws IOException */ private static StorageClient getTrackerClient() throws IOException { TrackerServer trackerServer = getTrackerServer(); StorageClient storageClient = new StorageClient(trackerServer, null); return storageClient; } /** * 获取tracker服务 * @return * @throws IOException */ private static TrackerServer getTrackerServer() throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerServer; } }
FastDFSFile
/** * @Auther: lanhaifeng * @Date: 2019/4/25 0025 09:15 * @Description:文件信息类 */ public class FastDFSFile { private String name;//文件名 private byte[] content; //文件的内容,字节数组 private String ext; //文件扩展名,不包含(.) private String md5; //加密 private String author; public FastDFSFile() { } public FastDFSFile(String name, byte[] content, String ext) { this.name = name; this.content = content; this.ext = ext; } public String getName() { return name; } public void setName(String name) { this.name = name; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public String getExt() { return ext; } public void setExt(String ext) { this.ext = ext; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } }
五、controller调用
@Controller public class UploadController { private static Logger logger = LoggerFactory.getLogger(UploadController.class); @GetMapping("/") public String index() { return "upload"; }
@PostMapping("/upload") public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "Please select a file to upload"); return "redirect:uploadStatus"; } try { // Get the file and save it somewhere String path=saveFile(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded '" + file.getOriginalFilename() + "'"); redirectAttributes.addFlashAttribute("path", "file path url '" + path + "'"); } catch (Exception e) { logger.error("upload file failed",e); } return "redirect:/uploadStatus"; } @GetMapping("/uploadStatus") public String uploadStatus() { return "uploadStatus"; } /** * @param multipartFile * @return * @throws IOException 这个方法后面可以自己封装一个util,这里只是测试 */ public String saveFile(MultipartFile multipartFile) throws IOException { String[] fileAbsolutePath={}; String fileName=multipartFile.getOriginalFilename(); String ext = fileName.substring(fileName.lastIndexOf(".") + 1); byte[] file_buff = null; InputStream inputStream=multipartFile.getInputStream(); if(inputStream!=null){ int len1 = inputStream.available(); file_buff = new byte[len1]; inputStream.read(file_buff); } inputStream.close(); FastDFSFile file = new FastDFSFile(fileName, file_buff, ext); try { fileAbsolutePath = FastDFSClient.upload(file); //upload to fastdfs } catch (Exception e) { logger.error("upload file Exception!",e); } if (fileAbsolutePath==null) { logger.error("upload file failed,please upload again!"); } String path=FastDFSClient.getTrackerUrl()+fileAbsolutePath[0]+ "/"+fileAbsolutePath[1]; return path; } }
参考:https://www.cnblogs.com/Leo_wl/p/7705158.html
https://blog.csdn.net/qq_26641781/article/details/80913482