不久之前,我们实现了商品的类目选择这个功能,但这只是万里长征的第一步,我们还有很多事情需要做,例如怎样实现图片上传这个功能。本文就来教大家如何实现图片上传。
图片上传分析
我们知道,对于传统项目来说,所有的模块都在一个项目中开发,包括所有静态资源文件比如图片等,都存储在这一个tomcat服务器上,如下图所示。
如果访问量小的话,这样做问题倒不大,但是对于互联网项目来说,用户访问量很大,这样一个tomcat服务器是远远不能满足业务需求的。这就需要部署tomcat集群,有集群就需要用到负载均衡,我们一般都会使用nginx来作为负载均衡服务器,如下图所示。
但是这种tomcat集群的缺点也很明显,假如我们把一张a.jpg的图片上传到了tomcat1的images目录下了,由于Nginx负责均衡处理请求,当用户去请求访问这张图片的时候,假设第一次,Nginx把请求交给tomcat1去处理,它到自己的images目录下去找这张图片,发现是可以找到的,于是我们便能看到这张图片,当我们第二次通过Nginx去请求访问该图片时,Nginx把请求交给tomcat2去处理了,这时tomcat2去它自己的images目录下去查找这张图片,发现并没有这张图片,因此页面上便看不到图片。作为用户来讲,一次访问能看到,再刷新就看不到,再刷新能看到,用户会很不理解,直观的感觉便是我们的系统太烂了。
针对上面提到的问题,我们对集群做下改善,我们专门搞一个图片服务器,所有的tomcat都将用户上传的图片上传到图片服务器上,tomcat本身并不保存图片。我们采用http的方式来访问图片,这样我们就需要使用http服务器,能作为http服务器的有很多种选择。
- tomcat可以作为http服务器,但是由于tomcat服务器的强项并不在于处理静态资源( 它的强项是处理servlet和jsp等动态页面)因此我们不选择tomcat。
- 使用Apache作为http服务器,Apache是由C语言编写的一款服务器(注意,这里指的并不是Apache组织,而仅仅是一个服务器),这款服务器在以前用的人是很多的,不过现在用的人少了。
- 使用nginx,nginx因为其独特的优势,作为http服务器是目前最火的。我们就使用nginx来统一管理这些图片,这样用户要访问图片的时候,nginx直接把图片服务器上的图片给返回就可以了,从而解决了tomcat集群资源无法共享的问题。
但是这里需要考虑一个问题,那就是作为服务器,容量肯定是有限的,当这个服务器容量满了,怎么办呢?还有就是图片服务器挂了,怎么办呢?这些都是必须要解决的问题,为了解决这两个问题,我们使用FastDFS集群来解决。FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。它的优势是可以水平扩容,FastDFS存储资源的设备是按组来区分的,当存储空间不足时,便可以通过水平增加分组并相应添加设备来达到扩容的目的,而且是没有上限的。它还有个优势是高可用,也就是说FastDFS集群能够做到当提供服务的nginx发生故障时,自动切换到另一台nginx设备上,保障服务的稳定。
图片服务器的安装
在此提醒大家搭建FastDFS集群是非常非常麻烦的,至少我现在就不会,所以,作为练习,我们只需搭建单机版FastDFS即可。一个最简单的FastDFS架构如下:
这个架构就是我给大家提供的一个图片服务器的架构,当然了,这个最简单的架构在真正的生产环境中是远远不够的。由于图片服务器的安装过程非常复杂,就不需要大家掌握了,所以我会给大家提供一个安装好的图片服务器。大家可参考我的这篇文章淘淘商城系列——VMware添加已配置好的虚拟机进行学习。
使用FastDFS-Client客户端上传图片
下面我便用FastDFS-Client来进行上传和下载图片的测试,不过在这之前我们需要先做两件事。
第一件事:由于中央仓库并没有fastdfs-client的包,因此需要我们自己整。这里我提供了一个有关fastdfs-client的maven工程,如下图所示。
我们所要做的就是将fastdfs-client这个maven工程导入到Eclipse中,如下图所示,导入完成之后定要将这个maven工程打包到本地maven仓库中。
至此,是不是万事大吉了呢?很显然并不是,我们直接通过表现层上传图片到图片服务器,大可不必再经过Service层,所以taotao-manager-web工程需要依赖fastdfs-client工程,因此需要在taotao-manager-web工程的pom.xml文件中添加对fastdfs-client的依赖,如下所示。
<dependency>
<groupId>fastdfs_client</groupId>
<artifactId>fastdfs_client</artifactId>
<version>1.25</version>
</dependency>
第二件事:在taotao-manager-web工程的src/main/resources目录下新建一个resource文件夹并在它下面创建fast_dfs.conf文件,fast_dfs.conf文件中输入tracker所在的设备的IP及端口,由于我的tracker是在ip为192.168.25.133的虚拟机上,因此我这里写的是”tracker_server=192.168.156.13:22122”,如下图所示。
下面我们新建一个测试类来进行图片上传的测试,我们在src/test/java目录下新建一个com.taotao.fdfs包,并在该包下新建一个测试类TestFastDfs.java,如下图所示。
为方便复制,现把测试类代码粘贴如下:
public class TestFastDfs {
@Test
public void testUpload() throws Exception {
// 1.先创建一个配置文件——fast_dfs.conf,配置文件的内容就是指定TrackerServer的地址
// 2.使用全局方法加载配置文件
ClientGlobal.init("F:/Java/my-taotao/taotao-manager-web/src/main/resources/resource/fast_dfs.conf");
// 3.创建一个TrackerClient对象
TrackerClient trackerClient = new TrackerClient();
// 4.通过TrackerClient对象获得TrackerServer对象
TrackerServer trackerServer = trackerClient.getConnection();
// 5.创建StorageServer的引用,null就可以了
StorageServer storageServer = null;
// 6.创建一个StorageClient对象,其需要两个参数,一个是TrackerServer,一个是StorageServer
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
// 7.使用StorageClient对象上传文件(图片)
// 参数1:文件名,参数名:扩展名,不能包含".",参数3:文件的元数据,保存文件的原始名、大小、尺寸等,如果没有可为null
String[] strings = storageClient.upload_file("F:/fastdfs_test/meinv.jpg", "jpg", null);
for (String string : strings) {
System.out.println(string);
}
}
}
上面的代码有两点需要注意:
taotao-manager-web工程还未添加对junit的依赖,因此需要在pom.xml文件中添加对junit的依赖,如下所示,这里之所以不用写版本是因为在taotao-parent工程中已经统一定义好版本了。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency>
- 我们在复制粘贴win10系统的本地文件绝对路径(例如
F:\fastdfs_test\meinv.jpg
)时,Eclipse是识别不了的,运行会报如下错误。解决方法是手动输入,而且要注意把包括双引号在内的这个路径串删除("E:/images/2.jpg"
),然后手动输入一遍。
注意上面两点之后,我们再执行这个测试方法便可成功,如下图所示。回显信息的第一行是该图片被保存到哪个组了,由于我们现在只是用的单机FastDFS服务器,因此现在都是group1。第二行是存放的具体位置。
既然图片上传上去了,现在我们试着用http的方式来访问下该图片,我们需要把group1和M00/00/00/wKgZhVksG-6AOre0AAFkkATUCZ8885.jpg拼接到一块来访问,这样我们便可以查看到我们刚才上传的照片了,如下图所示。
经过上面的操作,说明我们搭建的图片服务器没问题。但有个问题是,图片上传的操作步骤繁琐,因此我们迫切需要对其进行封装,现在我把封装好的工具类——FastDFSClient.java的内容粘贴如下。
public class FastDFSClient {
private TrackerClient trackerClient = null;
private TrackerServer trackerServer = null;
private StorageServer storageServer = null;
private StorageClient1 storageClient = null;
public FastDFSClient(String conf) throws Exception {
if (conf.contains("classpath:")) {
conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());
}
ClientGlobal.init(conf);
trackerClient = new TrackerClient();
trackerServer = trackerClient.getConnection();
storageServer = null;
storageClient = new StorageClient1(trackerServer, storageServer);
}
/**
* 上传文件方法
* <p>Title: uploadFile</p>
* <p>Description: </p>
* @param fileName 文件全路径
* @param extName 文件扩展名,不包含(.)
* @param metas 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileName, extName, metas);
return result;
}
public String uploadFile(String fileName) throws Exception {
return uploadFile(fileName, null, null);
}
public String uploadFile(String fileName, String extName) throws Exception {
return uploadFile(fileName, extName, null);
}
/**
* 上传文件方法
* <p>Title: uploadFile</p>
* <p>Description: </p>
* @param fileContent 文件的内容,字节数组
* @param extName 文件扩展名
* @param metas 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileContent, extName, metas);
return result;
}
public String uploadFile(byte[] fileContent) throws Exception {
return uploadFile(fileContent, null, null);
}
public String uploadFile(byte[] fileContent, String extName) throws Exception {
return uploadFile(fileContent, extName, null);
}
}
该工具类构造方法中的
conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());
这句话的意思是,如果用户传入的文件路径是相对路径(相对路径以src/main/resources目录为根目录),比如用户传入的文件路径是”classpath:applications.properties”,那么需要转为绝对路径,因此需要把”classpath:”给替换掉,改为F:/Java/my-taotao/taotao-manager-web/src/main/resources。而且封装类中使用的Storage客户端是StorageClient1而不是StorageClient。这个客户端的好处是能够帮我们自动把文件所在的组以及存放位置拼接到一块。
我们在taotao-manager-web工程下新建一个com.taotao.utils工具包,然后把我们的封装类FastDFSClient.java放到下面。我们来测测这个工具类是否好使,我们再在TestFastDfs单元测试类中新建一个测试方法testFastDfsClient,如下图所示。
testFastDfsClient单元测试方法代码如下:
@Test
public void testFastDfsClient() throws Exception {
FastDFSClient fastDFSClient = new FastDFSClient("F:/Java/my-taotao/taotao-manager-web/src/main/resources/resource/fast_dfs.conf");
String string = fastDFSClient.uploadFile("F:/fastdfs_test/a.jpg");
// 注意,你再上传一遍,刚才你上传的那个图片就已经丢了,它已经存在服务器上了,你再也访问不到了
System.out.println(string);
我们运行testFastDfsClient方法,返回的结果如下图所示。
我们来访问这个图片,如下图所示。