分布式文件存储系统FastDFS(入门)
目录
一、什么是分布式文件管理系统
1.1 解决的问题:
二、FastDFS分布式文件管理系统
2.1 FastDFS 简介
2.2 官方地址
三、FastDFS 架构
3.1 架构图
3.2 角色
3.2.1 Client
3.2.2 Tracker Server
3.2.3 Storage Server
3.3 架构解读
四、基于Docker安装FastDFS
4.1 拉取FastDFS镜像:
4.2 创建 fastDFS 的两个挂载目录:
4.3 创建跟踪器容器:
4.4 创建存储器容器(重连网络-ip地址变了,去4.6
4.4.1 如果 ip 地址没换,直接粘了这个代码 ,导致出错了 -- 删存储器容器
4.5 查看一下文件的存放位置:
4.6 解决问题:重启storage容器报错:
解决方案一:重启容器之前,删除对应的pid进程描述文件
解决方案二:重启容器之前,删除对应的pid进程描述文件
4.6.1 重启虚拟机了:
五、文件上传:
5.1 创建 maven 项目,添加启动器:
5.2 创建fdfs.properties文件:
5.3 创建fastDFS上传工具类:
5.4.1 文件上传
5.4.1-1 @Test后没有可以运行的三角:在 pom.xml 添加依赖,刷新Maven
5.4.2 查看文件元数据
六、文件下载
七、删除文件
八、在线预览:
九、查看上传文件列表
十、案例:花卉添加,查询,删除
10.1 建表
10.2 添加 application.yml 配置文件:
10.3 添加pojo实体类:
10.4 添加 FlowerMapper 接口:
10.5 添加FlowerService接口:
10.6 添加FlowServiceImpl接口实现类:
10.7 创建路径控制器(也可以直接在FlowerController里写):
10.8 创建添加页面 save.html:
10.9 创建FlowerController
10.10 编写成功页面 success.html
10.11 编写启动类 SpringBootMain
项目结构:
10.12 运行 启动类SpringBootMain
输入127.0.0.1:8866/save
用到的:Idea(2024版),Linux + SSH,Docker
要是有需要的话我会补一篇 Linux 和 Docker 怎么安装和使用的(先挖个坑)
一、什么是分布式文件管理系统
分布式文件系统(Distributed File System,DFS)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机)相连;或是若干不同的逻辑磁盘分区或卷标组合在一起而形成的完整的有层次的文件系统。DFS为分布在网络上任意位置的资源提供一个逻辑上的树形文件系统结构,从而使用户访问分布在网络上的共享文件更加简便。
分布式环境下,应用服务拆分成若干份,分别部署在不同的应用服务器中,同时对外提供一个完整的应用系统。
常见的分布式文件系统有:GlusterFS、GoogleFS、FastDFS、TFS等,各自适用的领域不同。
GlusterFS:主要应用在集群系统中,具有很好的可扩展性。软件的结构设计良好,易于扩展和配置,通过各个模块的灵活搭配以得到针对性的解决方案。对硬件和网络要求比较高点。
GoogleFS:性能十分好,可扩展性强,可靠性强。用于大型的、分布式的、对大数据进行访问的应用。运用在廉价的硬件上。
FastDFS:一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。文件大小一般都是在500MB以下的文件,使用fastDFS最合适了。
TFS:TFS(Taobao FileSystem)是一个高可扩展、高可用、高性能、面向互联网服务的分布式文件系统,主要针对海量的非结构化数据,它构筑在普通的Linux机器集群上,可为外部提供高可靠和高并发的存储访问。TFS为淘宝提供海量小文件存储,通常文件大小不超过1M,满足了淘宝对小文件存储的需求,被广泛地应用 在淘宝各项应用中
1.1 解决的问题:
解决文件上传时,图片没有地方存的问题
分布式架构之后,多个模块项目中的图片上传到同一个服务器上去;同样取图片的时候 多个模块也从同一个服务器上取拿。
二、FastDFS分布式文件管理系统
2.1 FastDFS 简介
FastDFS 是一个轻量级的开源分布式文件系统。2008年4月份开始启动。类似 google FS 的一个轻量级分布式文件系统,纯 C 实现,支持 Linux、FreeBSD、AIX 等 UNIX 系统。
主要解决了大容量的文件存储和高并发访问的问题,文件存取时实现了负载均衡。实现了软件方式的磁盘阵列(Redundant Arrays of Independent Drives,RAID),可以使用廉价的 IDE(Integrated Drive Electronics)硬盘进行存储。并且支持存储服务器在线扩容。支持相同内容的文件只保存一份,节约磁盘空间。
FastDFS 只能通过 Client API 访问,不支持 POSIX 访问方式。
FastDFS 特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)
2.2 官方地址
FastDFS 没有官网。但是作者余庆(happy_fish100)担任 chinaunix 中 FastDFS 板块版主。并且会不定期更新板块中内容。 http://bbs.chinaunix.net/
FastDFS软件可以在sourceforge中进行下载,最新版本为5.11:https://sourceforge.net/projects/fastdfs/files/https://sourceforge.net/projects/fastdfs/files/
三、FastDFS 架构
3.1 架构图
客户端:就是我们,使用 Java 语言编写的程序
Tracker Server 集群:跟踪服务器 做调度工作,连接客户端和 存储器。有负载均衡的作用,上传文件存储在 Storage Server 的哪个位置上,由 Tracker Server 决定。
Storage Server:存储服务器。一共有10组 分别是 group0-group9,组之间存储的文件内容不同,个数也不同;但是组里纵向看 存储器之间存储的文件内容是相同的。所以就是横向看是分布式,纵向看是集群。
3.2 角色
3.2.1 Client
客户端。使用 Java 语言编写的项目属于客户端。
3.2.2 Tracker Server
跟踪服务器,主要做调度工作,在访问商起负载均衡的作用。在内存中记录集群中 group 和 Storage Server 的状态信息,是连接 Client 和 Storage Server 的枢纽。
3.2.3 Storage Server
存储服务器,文件和文件属性(meta data)都保存到存储服务器上
3.3 架构解读
FastDFS 服务端只有两个角色,tracker server 和 storage server。
所有同角色服务器集群节点都是平等的,不存在主从关系(Master-Slave)。
存储服务器采用分组方式,同组内存储服务器上的文件完全相同(备份);不同分组的存储服务器管理不同的文件(扩容 RAID)。
不同组的 storage server 之间不会相互通信。
由 storage server 主动向 tracker server 报告状态信息,tracker server 之间不会相互通信。
四、基于Docker安装FastDFS
4.1 拉取FastDFS镜像:
进入docker 界面,输入下面的命令:shift + insert 是linux的粘贴快捷键
docker pull delron/fastdfs |
如果报错:
此时需要更换docker 的镜像源:
先把它拖到桌面,修改,保存,把原本的删了,再把桌面的拖进去
↓ 打开长这样,拿下面的代码把这个替换掉
{"registry-mirrors" : ["https://docker.registry.cyou","https://docker-cf.registry.cyou","https://dockercf.jsdelivr.fyi","https://docker.jsdelivr.fyi","https://dockertest.jsdelivr.fyi","https://mirror.aliyuncs.com","https://dockerproxy.com","https://mirror.baidubce.com","https://docker.m.daocloud.io","https://docker.nju.edu.cn","https://docker.mirrors.sjtug.sjtu.edu.cn","https://docker.mirrors.ustc.edu.cn","https://mirror.iscas.ac.cn","https://docker.rainbond.cc","https://do.nark.eu.org","https://dc.j8.work","https://dockerproxy.com","https://gst6rzl9.mirror.aliyuncs.com","https://registry.docker-cn.com","http://hub-mirror.c.163.com","http://mirrors.ustc.edu.cn/","https://mirrors.tuna.tsinghua.edu.cn/","http://mirrors.sohu.com/"],"insecure-registries" : ["registry.docker-cn.com","docker.mirrors.ustc.edu.cn"],"debug": true,"experimental": false
}
然后重启服务 -- 输入以下两个命令,之后再重新拉取fastdfs镜像:
systemctl daemon-reload systemctl restart docker |
拉取(有点慢):docker pull delron/fastdfs
查看 docker 镜像:docker images
↑ 有这个 delron/fastdfs 就是成功了(粘的时候不要把前后的空格也粘了;;)
4.2 创建 fastDFS 的两个挂载目录:
创建 /opt/fdfs/tracker 和 /opt/fdfs/tracker 两个目录:
mkdir -p /opt/fdfs/tracker mkdir -p /opt/fdfs/storage |
也可以手动创建
4.3 创建跟踪器容器:
跟踪器默认服务端口是:22122
使用如下命令 创建 并 启动 容器: docker run -d --network=host --name tracker -v /opt/fdfs/tracker:/var/fdfs delron/fastdfs tracker |
4.4 创建存储器容器(重连网络 -- ip地址变了,去4.6
↑ 输入ip addr查看端口号,↓ 这里的蓝色改成当前启动的 虚拟机 的 端口号
docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.1.109:22122 -v /opt/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage |
192.168.1.109 是 虚拟机 的端口号,22122 是 跟踪器 的默认端口,GROUP_NAME=group1 是指当前启动的 storage 容器属于 group1 这个存储组
-v /opt/fdfs/storage:/var/fdfs 是 Docker 的目录挂载(Bind Mount) 机制,将宿主机的 /opt/fdfs/storage 目录与容器内的 /var/fdfs 目录绑定,图片实际是存储在宿主机的 /opt/fdfs/storage 中,而非容器内部的临时文件系统. ==> 删除 storage 容器时,只是删除了容器本身的运行实例,宿主机的 /opt/fdfs/storage 目录及其中的文件会被保留。若需重新创建 storage 容器,只需使用相同的命令(确保 -v 参数不变),新容器会直接读取宿主机 /opt/fdfs/storage 中的数据,之前存储的文件会自动恢复。
4.4.1 如果 ip 地址没换,直接粘了这个代码 ,导致出错了 -- 删存储器容器
先停止容器: docker stop storage
再删除容器: docker rm storage
输入换ip后的命令:docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.1.109:22122 -v /opt/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
查看一下启动情况:docker ps
还是不行的话就试试下面4.6的命令
4.5 查看一下文件的存放位置:
cd /opt/fdfs/storage/data ls cd 00 ls |
一共 256*256 个文件夹 来存储文件。
4.6 解决问题:重启storage容器报错:
原因是:存储器启动时,需要重新创建日志文件和进程描述文件(xxx.pid)。因为容器目录映射到宿主机(/opt/fdfs/storage),容器关闭时无法正常删除进程描述文件,所以在重启时,发生启动错误。
解决方案一:重启容器之前,删除对应的pid进程描述文件
(多行代码一起粘贴并回车后,要运行最后一行命令得再按一次回车
(下面这些全部一起粘贴后这里的删除操作要确认,输入y并回车后会断,得再输入一遍后面的
docker stop storage docker start storage docker ps cd /opt/fdfs/storage/data rm fdfs_storaged.pid y docker start storage |
上面的 ↑ 只对 storage 进行了操作,如果这样也不行:
解决方案二:重启容器之前,删除对应的pid进程描述文件
docker stop storage |
4.6.1 重启虚拟机了:
得重新开那两个容器 + 关防火墙(如果没关)
如果输入命令docker ps后没有输出 storage 和 tracker,开启容器
docker start tracker |
五、文件上传:
5.1 创建 maven 项目,添加启动器:
在 pom.xml 添加:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.2</version></parent>
<dependencies><dependency><groupId>cn.bestwu</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27</version></dependency><dependency> <!-- 上传文件用的jar包 --><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> <!-- 仅在测试环境生效,不打包到生产 --></dependency></dependencies>
<build><!--添加tomcat插件 --><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><port>8080</port><path>/</path></configuration></plugin></plugins><!--资源拷贝的插件 --><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory><includes><include>**/*.yml</include><include>**/*.xml</include><include>**/*.html</include> <!-- 很重要 --><include>**/*.js</include> <!-- 很重要 --><include>**/*.png</include> <include>**/*.jpg</include> <include>**/*.properties</include></includes></resource></resources></build>
5.2 创建fdfs.properties文件:
在 src/main/resources 目录中创建 properties 类型配置文件,命名不限。如:fdfs.properties
5.3 创建fastDFS上传工具类:
直接粘贴过来,手写意义不大:
package com.jr.util;import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;import java.io.*;
import java.util.Properties;public final class FastDFSUtils {/*** 定义静态属性,Properties和StorageClient*/private final static Properties PROPERTIES;private final static StorageClient STORAGE_CLIENT;/*** 静态初始化代码块,初始化静态属性* 静态初始化代码块有异常如何处理?* 处理的时候,try。。catch。。 抛出一个Error,终止虚拟机。*/static{try {PROPERTIES = new Properties();// 读取配置文件PROPERTIES.load(FastDFSUtils.class.getClassLoader().getResourceAsStream("fdfs.properties"));// 使用ClientGlobal初始化FastDFS客户端配置ClientGlobal.initByProperties(PROPERTIES);// 创建Tracker客户端对象TrackerClient trackerClient = new TrackerClient();// 基于Tracker客户端对象,获取Tracker服务器对象TrackerServer trackerServer = trackerClient.getConnection();// 基于Tracker服务器和客户端对象,获取Storage服务器对象StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);// 创建Storage客户端对象STORAGE_CLIENT = new StorageClient(trackerServer, storageServer);}catch (Exception e){throw new ExceptionInInitializerError(e);}}/**** @param inputStream* 上传的文件输入流* @param fileName* 上传的文件原始名* @return* 可以保存元信息*/public static String[] uploadFile(InputStream inputStream, String fileName) {try {// 文件的元数据NameValuePair[] meta_list = new NameValuePair[2];// 第一组元数据,文件的原始名称meta_list[0] = new NameValuePair("file name", fileName);// 第二组元数据meta_list[1] = new NameValuePair("file length", inputStream.available()+"");// 准备字节数组byte[] file_buff = null;if (inputStream != null) {// 查看文件的长度int len = inputStream.available();// 创建对应长度的字节数组file_buff = new byte[len];// 将输入流中的字节内容,读到字节数组中。inputStream.read(file_buff);}// 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);return fileids;} catch (Exception ex) {ex.printStackTrace();return null;}}/**** @param file* 文件* @param fileName* 文件名* @return 返回Null则为失败* 不想保存元信息*/public static String[] uploadFile(File file, String fileName) {FileInputStream fis = null;try {NameValuePair[] meta_list = null; // new NameValuePair[0];fis = new FileInputStream(file);byte[] file_buff = null;if (fis != null) {int len = fis.available();file_buff = new byte[len];fis.read(file_buff);}String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);return fileids;} catch (Exception ex) {return null;}finally{if (fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 根据组名和远程文件名来删除一个文件** @param groupName* 例如 "group1" 如果不指定该值,默认为group1* @param remoteFileName* 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"* @return 0为成功,非0为失败,具体为错误代码*/public static int deleteFile(String groupName, String remoteFileName) {try {int result = STORAGE_CLIENT.delete_file(groupName == null ? "group1" : groupName, remoteFileName);return result;} catch (Exception ex) {return 0;}}/*** 修改一个已经存在的文件** @param oldGroupName* 旧的组名* @param oldFileName* 旧的文件名* @param file* 新文件* @param fileName* 新文件名* @return 返回空则为失败*/public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {String[] fileids = null;try {// 先上传fileids = uploadFile(file, fileName);if (fileids == null) {return null;}// 再删除int delResult = deleteFile(oldGroupName, oldFileName);if (delResult != 0) {return null;}} catch (Exception ex) {return null;}return fileids;}/*** 文件下载** @param groupName 卷名* @param remoteFileName 文件名* @return 返回一个流*/public static InputStream downloadFile(String groupName, String remoteFileName) {try {System.out.println("尝试下载文件 - groupName: " + groupName + ", remoteFileName: " + remoteFileName);byte[] bytes = STORAGE_CLIENT.download_file(groupName, remoteFileName);System.out.println("文件下载成功,字节数: " + (bytes != null ? bytes.length : 0));InputStream inputStream = new ByteArrayInputStream(bytes);return inputStream;} catch (Exception ex) {System.out.println("文件下载失败: " + ex.getMessage());ex.printStackTrace();return null;}}public static NameValuePair[] getMetaDate(String groupName, String remoteFileName){try{NameValuePair[] nvp = STORAGE_CLIENT.get_metadata(groupName, remoteFileName);return nvp;}catch(Exception ex){ex.printStackTrace();return null;}}/*** 获取文件后缀名(不带点).** @return 如:"jpg" or "".*/private static String getFileExt(String fileName) {if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {return "";} else {return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点}}/*** 提供获取Storage客户端对象的工具方法*/public static StorageClient getStorageClient(){return STORAGE_CLIENT;}private FastDFSUtils(){}
}
5.4 编写测试类:
在 test 的 jr 包下创建测试类 DemoTest:
5.4.1 文件上传
FastDFSUtils.uploadFile 里的 第一个参数 -- 要存的文件的路径;第二个参数 -- 文件名
// 1. 文件上传@Testpublic void test1() throws FileNotFoundException {//文件上传String[] strings1 = FastDFSUtils.uploadFile(new File("E:\\1224021553-1.jpg"),"1224021553-1.jpg");System.out.println(Arrays.toString(strings1));//文件上传,保存元数据【包括文件的原始名称和文件大小】String[] strings2 = FastDFSUtils.uploadFile(new FileInputStream(new File("C:\\Users\\de'l'l\\Desktop\\Penguin22.jpg")),"Penguin22.jpg");System.out.println(Arrays.toString(strings2));}
5.4.1-1 测试方法(test1)前面没有可以运行的三角:在 pom.xml 添加依赖,刷新Maven
(其实在上面已经加过了)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> <!-- 仅在测试环境生效,不打包到生产 --></dependency>
运行 ==> 控制台输出:
group1 是组名,M00/00/00/wKgkH2jf9sSAD2boAACcBMoTY1A086.jpg 是在容器里存的文件名
虚拟机:要是粘贴后cd后面有个问号,cd就手动敲,只粘空格后面的
cd /opt/fdfs/storage/data/00/00 ls |
上面两个是一个图片,有-m后缀的是元数据,是用 FileInputStream 上传的(上面代码的第二个)
有 -m 的才可以查看元数据
5.4.2 查看文件元数据
M00/00/00/wKgkYmjeCMyANOqrAABti-fouGU691.jpg 要换成自己的有元数据的文件名(有 -m 后缀的文件)
// 1-2. 查看文件元数据@Testpublic void test11(){//获取数据的元数据:NameValuePair[] group1s = FastDFSUtils.getMetaDate("group1", "
M00/00/00/wKgkH2jf9sSAOlYBAABm6FUu32E261.jpg");System.out.println(group1s[0].getName()+"----"+group1s[0].getValue());System.out.println(group1s[1].getName()+"----"+group1s[1].getValue());}
导包导第一个包 (of org.csource.common) 那个
运行结果:
六、文件下载
// 2. 文件下载@Testpublic void test2() throws IOException {//从fastDFS中下载的文件 转成 inputStreamInputStream inputStream = FastDFSUtils.downloadFile("group1", "M00/00/00/wKgkH2jf9sSAOlYBAABm6FUu32E261.jpg");//自己指定输入的目录:OutputStream outputStream = new FileOutputStream("E:\\aa.jpg");IOUtils.copy(inputStream, outputStream);}
输出的图片后缀看自己需求,.jpg,.png 都行 -- 如果写的是"aa.png",那下载的就是png形式的
控制台输出:
E盘多了个 aa.jpg文件↓↓↓
七、删除文件
// 3. 文件删除@Testpublic void test3(){//文件删除,返回值是0 代表删除成功;非0 代表删除失败!int i = FastDFSUtils.deleteFile("group1", "M00/00/00/wKgkH2jf9sSAOlYBAABm6FUu32E261.jpg");System.out.println(i);}
控制台返回0:成功
查看:还是那个文件夹,没有退出文件夹就不用运行:cd /opt/fdfs/storage/data/00/00
命令:ls
M00/00/00/wKgkH2jf9sSAOlYBAABm6FUu32E261.jpg 没了,连带着元数据也没了
↓ 也可以直接在 SSH 查看文件夹
八、在线预览:
FastDFS没有提供访问接口, 但是内置了nginx 是虚拟主机,具有访问能力。
复制刚刚删除文件后查到的文件名( linux 复制快捷键:ctrl + insert )
http://192.168.36.31:8888/group1/M00/00/00/wKgkH2jf9sSAD2boAACcBMoTY1A086.jpg |
8888是默认端口号
在浏览器搜索上面的网址:
九、查看上传文件列表
cd /opt/fdfs/storage/data/00/00
ls
十、案例:花卉添加,查询,下载 ( + 删除没写;;)
10.1 建表
DROP TABLE IF EXISTS `flower`;
CREATE TABLE `flower` (`id` int NOT NULL AUTO_INCREMENT COMMENT '花卉ID',`name` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '花卉名称',`price` double(3, 1) NULL DEFAULT NULL COMMENT '花卉价格',`production` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '花卉产地',`orname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '花卉图片原始名称',`groupname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '花卉图片组名称',`remotefilename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '花卉远程端名称',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
10.2 添加 application.yml 配置文件:
server:port: 8866
spring:servlet:multipart:max-file-size: 10MBmax-request-size: 10MBdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/jdbc2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis:type-aliases-package: com.jr.pojomapper-locations: classpath:com/jr/mapper/*.xmlconfiguration: ##控制台输出sql语句log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
10.3 添加pojo实体类:
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Flower implements Serializable {private Integer id;private String name;private Double price;private String production;private String orname;private String groupname;private String remotefilename;
}
10.4 添加 FlowerMapper 接口:
@Mapper
public interface FlowerMapper {@Insert("insert into flower values(default,#{name},#{price},#{production},#{orname},#{groupname},#{remotefilename})")int insert(Flower flower);@Select("select * from flower")List<Flower> selectAll();
}
10.5 添加FlowerService接口:
public interface FlowerService {/*** @param* @param photo MultipartFile 上传文件:可以是 图片,文件,视频,音频* @return* @throws IOException*/int save(Flower flower, MultipartFile photo)throws IOException;List<Flower> findAll();
}
10.6 添加FlowServiceImpl接口实现类:
@Service
public class FlowerServiceImpl implements FlowerService {@Autowiredprivate FlowerMapper flowerMapper;@Override@Transactionalpublic int save(Flower flower, MultipartFile photo)throws IOException {// 进行文件上传// 把文件转换成 InputStreamInputStream inputStream = photo.getInputStream();// 调用工具类进行文件上传String[] strings = FastDFSUtils.uploadFile(inputStream,photo.getOriginalFilename());//保存文件原始名称flower.setOrname(photo.getOriginalFilename());flower.setGroupname(strings[0]);flower.setRemotefilename(strings[1]);return flowerMapper.insert(flower);}@Overridepublic List<Flower> findAll() {return flowerMapper.selectAll();}
}
10.7 创建路径控制器(也可以直接在FlowerController里写):
@Controller
public class PathController {@RequestMapping("/{url}")public String path(@PathVariable String url){return url;}
}
10.8 创建添加页面 save.html:
在 resources 目录下创建 templates,在 templates 下创建 save.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>花卉添加</title>
</head>
<body>
<h2>花卉添加</h2>
<!-- 提交信息必须带这个:enctype="multipart/form-data" -- look:service层里save方法,有 multipart -->
<form action="/upfile" method="post" enctype="multipart/form-data"><p>花卉名称:<input type="text" name="name"/></p><p>花卉价格:<input type="text" name="price"/></p><p>花卉产地:<input type="text" name="production"/></p><p>花卉文件:<input type="file" name="photo"/></p><p><input type="submit" value="提交"/></p>
</form>
</body>
</html>
10.9 创建FlowerController
@Controller
@RequestMapping
public class FlowerController {@Autowiredprivate FlowerService flowerService;@RequestMapping("/{url}")public String url(@PathVariable String url) {return url;}@RequestMapping("/upfile")public String upfile(Flower flower, MultipartFile photo) throws IOException {int i = flowerService.save(flower, photo);if (i > 0) {// 直接return "success"是转发,url地址不会变,如果要url地址跟这边就写 "redirect:success"return "success";}return "save";}// 查看@RequestMapping("/getAll")@ResponseBodypublic List<Flower> getAll() {return flowerService.findAll();}@RequestMapping("/download")public void download(String gname, String orname, HttpServletResponse response) throws IOException {//下载功能,需要特殊设置一下 响应头:设置响应结果为一个附件,而不是普通的响应主体或者json字符串String uuname= UUID.randomUUID()+".jpg";response.setHeader("content-disposition","attachment;filename="+uuname);InputStream inputStream = FastDFSUtils.downloadFile(gname, orname);ServletOutputStream outputStream = response.getOutputStream();System.out.println("inputStream:"+inputStream);System.out.println("outputStream:"+outputStream);IOUtils.copy(inputStream,outputStream);outputStream.close();inputStream.close();}
}
10.10 编写成功页面 success.html
在 resources 目录下创建 static\js,在 js下添加:jquery-1.8.3.js(从别的项目粘过来)
在 templates 下创建 success.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>查看花卉列表</title><script type="text/javascript" src="../js/jquery-1.8.3.js"></script><script type="text/javascript">$(document).ready(function(){$.get("getAll",function (dt){JSON.stringify(dt);$("tbody").empty();for (var i=0;i<dt.length;i++){$(" <tr>\n" +" <td>"+dt[i].id+"</td>\n" +" <td>"+dt[i].name+"</td>\n" +" <td>"+dt[i].price+"</td>\n" +" <td>"+dt[i].production+"</td>\n" +" <td><img title='"+dt[i].orname+"' height='150px' src='http://192.168.36.31:8888/"+dt[i].groupname+"/"+dt[i].remotefilename+"'/></td>\n" +" <td><a href='download?gname="+dt[i].groupname+"&orname="+dt[i].remotefilename+"'>下载</a></td>\n" +" </tr>").appendTo("tbody");}});});</script>
</head>
<body>
上传成功
<table><thead><tr><td>花卉编号</td><td>名称</td><td>价格</td><td>产地</td><td>图片</td><td>操作</td></tr></thead><tbody></tbody>
</table></body>
</html>
10.11 编写启动类 SpringBootMain
@SpringBootApplication
public class SpringBootMain {public static void main(String[] args) {SpringApplication.run(SpringBootMain.class, args);}
}
项目结构:
10.12 运行 启动类SpringBootMain
输入127.0.0.1:8866/save
添加,查询
-->
IDEA 控制台也会输出:
这是在 save 页面添加时 ↓
这是在 success 页面查询展示时的 ↓
刷新数据库表查看:
输入网址查看图片:(记得把端口号和图片名改成自己的)
http://192.168.36.31:8888/group1/M00/00/00/wKgkYmjeCMyANOqrAABti-fouGU691.jpg
点击下载
浏览器:
idea 控制台: