北京尚学堂2020java_FastDFS+Nginx
FastDFS是一个专门做文件存储的应用,不再是用IO流,而是用FastDFS作为文件系统。
第一节 项目架构的改变
如果客户端上传了一个文件到集群,集群把文件放在了A,后来请求的时候集群把请求发给了B,B中没有,简直要死了。
可以把架构变成这样:
第二节 分布式文件系统概述
2.1 分类
通用分布式文件系统:和传统的本地文件系统(ext3、NTFS等)相对应,典型代表:lustre、MooseFS
优点:标准文件系统操作方式,对开发者门槛较低
缺点:系统复杂度较高,需要支持若干标准的文件操作,如:目录结构、文件读写权限、文件锁等。复杂性更高
专用分布式文件系统
基于google File System思想,文件上传后不能修改。需要使用专有API对文件进行访问,也可称作分布式文件存储服务。典型代表:MogileFs、FastDFS、TFS。
属性之类的就是不是必要的。
优点:系统复杂性低,不需要支持若干标准的文件操作,如目录结构、文件读写权限、文件锁等,系统比较简洁。无需支持POSIX标准
缺点:采用专有API,对开发者门槛较高。
2.2 Google FS体系结构
两个角色:名字服务器(索引服务器) 存储服务器
架构特点:不支持文件修改功能,文件分块存储需要索引服务器;一个文件可以存储多份,一个文件存储的哪些存储服务器,通常采用动态分配的方式。
分块:一个很大的文件,分成三块;很多个文件在一起就很大了。
第三节 FastDFS简介
即快速的分布式文件系统,主要解决了大容量的文件存储和高并发访问的问题,文件存取的同时实现了复杂均衡。实现了软件方式的磁盘阵列(RAID),可以使用廉价的IDE(Integrated Drive Eletronics)硬盘进行存储。
FastDFS只能通过Client API访问,不支持POSIX访问方式,不是一个传统的文件系统。特别使用大中型网站使用,用来存储文件(图片文档音频视频。
第五节 FastDFS安装文档
首先安装环境:
yum install -y make cmake gcc gcc-c++
然后解压lib那个zip,移动到/usr/local,接着“./make.sh"接着“./make.sh install”
ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
然后解压缩FastDFS,cd进去./make.sh即可。
ls /usr/bin | grep fdfs:可执行文件
ls /etc/fdfs 配置文件
ls /usr/lib 主程序代码所在位置
ls /usr/include/fastdfs 包含一些插件组所在的位置
5.2 Tracker跟踪器配置及启动
也是启动追踪器的索引服务
复制/etc/dfds下的tracker_conf然后修改一下base,自定义即可,修改好了别忘了创建自定义的文件夹。
// 启动服务
service fdfs_trackerd start
或者直接:
/etc/init.d/fdfs_trackerd start
5.3 配置storage
一般tracker和storage可以不在同一台服务器上,这里就先放在一起了。
cd /etc/fdfs
cp storage.conf.smaple storage.conf
然后再修改它,base_path指的是运行过程一些文件的存储路径,store_path_count=是存储真正文件数据的路径有几个,store_path0定义了存储文件的路径。还需要配置tracker_server的地址
tracker的端口是22122和8080,storage有23000和8888。接下来就是创建文件并启动服务:
mkdir -p /fastdfs/storage/base
mkdir -p /fastdfs/storage/store
service fdfs_storaged start
编写配置文件20_fdfs/fdfs_java/src/main/resources/fdfs_config.conf:
connect_timeout=10
network_timeout=30
charset=UTF-8
http.tracker_http_port=8080
tracker_server=192.168.2.129:22122
第六节 FastDFS增删改查
首先引入依赖20_fdfs/fdfs_java/pom.xml:
<dependencies>
<!-- https://mvnrepository.com/artifact/cn.bestwu/fastdfs-client-java -->
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
然后利用静态代码块来那个初始化服务端客户端追踪器存储器20_fdfs/fdfs_java/src/main/java/com/bjsxt/utils/fdfs/FdfsUtil.java:
/**
* 工具类型:提供链接创建、上传、下载和删除等操作
*/
public class FdfsUtil {
// 通过static初始化代码块,读取配置文件,初始化客户端链接对象
// 客户端链接对象,用于实现文件读写操作时
private static StorageClient storageClient;
static {
try {
// 读取配置文件
// 获取文件名称
String path = Thread.currentThread().getContextClassLoader()
.getResource("").getPath() + "fdfs_config.conf";
// 加载配置文件
ClientGlobal.init(path);
// 创建Tracker客户端对象
TrackerClient trackerClient = new TrackerClient();
// 创建tacker服务器对象
TrackerServer trackerServer = trackerClient.getConnection();
// 创建Storage服务器对象
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
// 创建Storage客户端对象
storageClient = new StorageClient(trackerServer, storageServer);
} catch (Exception e) {
e.printStackTrace();
// 初始代码块出错,一定要抛出错误,停止虚拟机
throw new ExceptionInInitializerError(e);
}
}
}
然后编写上传函数:
/**
* 使用StorageClient对象,实现文件上传
*/
public static String[] uploadFile(byte[] datas, String fileName, String authName) {
try {
// 获取文件的拓展名
String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 创建文件拓展信息, 包括文件的原始名称,文件的大小,文件的上传者姓名
NameValuePair[] metaDatas = new NameValuePair[3];
metaDatas[0] = new NameValuePair("fileName", fileName);
metaDatas[1] = new NameValuePair("fileSize", datas.length + "");
metaDatas[2] = new NameValuePair("auth", authName);
/**
* 文件上传
* 获取文件的拓展名
* String[] storageClient.upload_file(byte[], String extName, NameValuePair[] metaDatas)
* datas - 要上传的文件的内容
* extName - 上传的文件的拓展名
* metaDatas - 上传文件的拓展信息是什么 如:文件的原始名称、文件的容量大小、文件的上传者等等。
*/
String[] result = storageClient.upload_file(datas, extName, metaDatas);
// 上传成功 无异常 返回字符串数组
// 字符串数组长度为2 0下标位置 卷名|组名 1下标位置是文件名(目录/文件)
// fdfs为了保证上传的文件原始名称冲突内容不冲突的情况而覆盖的问题,存储文件的时候,会提供一个uuid文件名称。
return result;
} catch (Exception e) {
e.printStackTrace();
return null; // 异常发生 返回null 代表上传失败
}
}
接着调用上传函数20_fdfs/fdfs_java/src/main/java/com/bjsxt/test/TestFdfs.java:
public class TestFdfs {
public static void main(String[] args)throws Exception {
upload();
}
public static void upload() throws Exception{
// 读取文件内容
String fileName = "1.png";
InputStream inputStream = TestFdfs.class.getClassLoader().getResourceAsStream(fileName);
// 创建字节数组
byte[] datas = new byte[inputStream.available()];
// 读取文件内容到字节数组中
inputStream.read(datas, 0, datas.length);
// 上传文件
String[] result = FdfsUtil.uploadFile(datas, fileName, "老张");
System.out.println("卷名:" + result[0]);
System.out.println("文件名:" + result[1]);
System.out.println("返回数组的长度:" + result.length);
}
}
最后输出的信息:
所谓的就是设置的文件存储路径/fastdfs/storage/store/data/
接着编写下载函数20_fdfs/fdfs_java/src/main/java/com/bjsxt/utils/fdfs/FdfsUtil.java:
public static byte[] downloadFile(String groupName, String fileName, NameValuePair[] metaDatas) {
try {
/**
* byte[] storageClient.download_file(String groupName, String fileName)
* groupName - 卷名 | 组名
* fileName - 文件名,是文件保存在
*/
byte[] datas = storageClient.download_file(groupName, fileName);
// 要下载的文件的扩展信息
if(metaDatas != null) {
// 一个引用值,外部变量指向的是相同的数据
NameValuePair[] tmp = storageClient.get_metadata(groupName, fileName);
// 把查询到的文件拓展信息保存到传入的数组中
for(int i = 0; i < tmp.length; i++) {
metaDatas[i] = tmp[i];
}
}
// 返回下载的文件内容
return datas;
} catch (Exception e) {
e.printStackTrace();
return null; //下载失败,返回Null
}
}
接着使用该函数20_fdfs/fdfs_java/src/main/java/com/bjsxt/test/TestFdfs.java,注意文件路径需要写好盘符:
// 文件下载 下载的文件保存在D盘根目录下。文件名称是文件的原始名称
public static void download() throws Exception {
// 用于保存文件拓展信息的数组
String groupName = "group1";
String fileName = "M00/00/00/wKgCgWJBMsaANUUdAAuBRYY3e1M093.png";
NameValuePair[] metaDatas = new NameValuePair[3];
// 下载文件
byte[] datas = FdfsUtil.downloadFile(groupName, fileName, metaDatas);
System.out.println(datas);
System.out.println(metaDatas);
String localName = "";
for(NameValuePair nameValuePair: metaDatas) {
System.out.println(nameValuePair.getName() + " - " + nameValuePair.getValue());
if(nameValuePair.getName().equals("fileName")) {
localName = nameValuePair.getValue();
}
}
// 把下载的文件内容保存到
FileOutputStream outputStream = new FileOutputStream("D:\\" + localName);
outputStream.write(datas);
outputStream.flush();
outputStream.close();
}
启动服务
systemctl stop firwalld
systemctl start fdfs_trackerd
systemctl start fdfs_storaged
删除代码20_fdfs/fdfs_java/src/main/java/com/bjsxt/utils/fdfs/FdfsUtil.java:
/**
* 文件删除,FastDFS会自动删除这个文件对应的metaData
*/
public static boolean deleteFile(String groupName, String fileName) {
try {
int result = storageClient.delete_file(groupName, fileName);
// 返回结果为0代表删除成功,其他是删除失败。
return result == 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
测试删除代码20_fdfs/fdfs_java/src/main/java/com/bjsxt/test/TestFdfs.java:
public static void delete() throws Exception{
String groupName = "group1";
String fileName = "M00/00/00/wKgCgWJBMsaANUUdAAuBRYY3e1M093.png";
boolean flag = FdfsUtil.deleteFile(groupName, fileName);
System.out.println(flag ? "删除文件成功": "删除文件失败");
}
第七节 FastDFS与nginx简介
要支持在线浏览,需要Nginx,是一个高性能HTTP和反向代理服务。Nginx是Apache服务不错的替代品,是美国做虚拟主机生意老板们经常选择的软件平台之一。
7.1 正向代理与反向代理
正向代理:vpn 一个位于客户端和原始服务器(origin server),其实就是FQ软件。
为了从google取得内容,客户端向代理器发送一个请求并指定原始服务器,代理向原始服务器转交请求并将获得的内容返回给客户端。
反向代理 Reverse Proxy,类似于经理的角色,对于外部过来的网络请求,决定交给我们搭建的集群中的那台机器来解决。DNS就是反向代理服务器。
比如这里你是为了看图片,那么我就由nginx决定将请求交给哪台服务器。
正向代理假设在客户端和目标主机之间,反向代理假设在服务器端。
代理对象不同:
- 正向代理,代理客户端,服务端不知道实际发起请求的客户端
- 反向代理,代理服务端,客户端不知道实际提供的服务的服务端
7.3 Nginx作用
- HTTP协议代理
- 只要支持HTTP协议访问的内容,都可以由Nginx进行代理,Nginx只支持HTTP协议的代理,其他协议不支持。
- 搭建虚拟主机
- 可以监听所安装的主机的某个端口,对外支持这个端口的HTTP访问。
- 当接收到外部HTTP请求后把本机资源返回给客户端
- 使用Nginx的搭建虚拟主机的功能,外部请求客户端,把图片信息响应给请求。
- 负载均衡
- Nginx可以代理多个主机,内置负载均衡策略
第八节 Nginx安装
8.1 fastdfs-nginx组件
安装依赖:
yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
把插件组fastdfs-nginx-module压缩包解压并且把文件移动:
mv fastdfs-nginx-module /usr/local/fastdfs/fdfs-nginx-module
修改配置文件/usr/local/fastdfs/fdfs-nginx-module/src/config
重要的就是将CORE_INCS文件夹中的/local去掉.
8.2 安装nginx
也是解压源代码,然后配置最后编译。
最后一行表示的是插件模块的位置,这样即可把nginx和插件联系起来
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--add-module=/usr/local/fastdfs/fdfs-nginx-module/src
只要在项目文件目录/root/nginx-1.16.1下执行:
make
make install
然后能在/usr/local/nginx中看到一些文件,说明Nginx安装好了。
把插件移动到/etc下:
cd /usr/local/fastdfs/fdfs-nginx-module/src
cp mod_fastdfs.conf /etc/fdfs
然后修改插件配置:
![]()
将之前解压的fastdfs源码文件/conf中的两个配置文件复制到/etc/fdfs中:
![]()
创建一个软件链接:
ln -s /fastdfs/storage/store/data/ /fastdfs/storage/store/M00
还需要修改nginx的配置,/usr/local/nginx/conf/nginx.conf将身份改成root:
还需要新增一个server,监听的端口就是fastdfs storage的http端口8888:
然后启动nginx:
退出的命令是:
sbin/nginx -s quit
然后就可以通过浏览器去访问图片:
http://192.168.2.129:8888/group1/M00/00/00/wKgCgWJBMdaAcmJUAAuBRYY3e1M274.png
但很遗憾我失败了。(P543_https://www.bilibili.com/video/BV13z4y167eH?p=543)
第九节 KEditor
新建一个project,把Keditor引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
然后把软件Keditor中的相关文件复制进resource/static/keditor:

在前端代码中引入相关的文件20_fdfs/keditor/src/main/resources/static/index.html:
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="keditor/themes/default/default.css">
<script src="keditor/kindeditor-all-min.js"></script>
<script src="keditor/lang/zh_CN.js"></script>
<script type="text/javascript">
KindEditor.ready(function (K) {
var params = {
allowFileManager: true,
uploadJson : 'upload'
};
var editor = K.create('textarea[name="content"]',params);
});
</script>
</head>
<body>
<form>
<label>图文编辑:</label>
<textarea name="content" ></textarea>
</form>
</body>
第十节 图片管理应用
10.1 img_pojo模块
引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
创建表:
create table img_manage(
id bigint(20) not null auto_increment COMMENT '主键',
url varchar(255) default '' comment '访问这个图片的HTTP地址',
group_name varchar(64) default 'group1' comment 'FastDFS中的卷名',
remote_file_name varchar(255) default '' comment 'FastDFS中的文件名,如:M00/00/00/abc.jpg',
orgin_file_name varchar(255) default '' comment '上传的图片的原始名称',
upload_time date not null comment '上传图片的时间',
primary key(id)
) comment '保存上传到FastDFS中的图片信息表';
写实体类20_fdfs/img_manager/img_pojo/src/main/java/com/bjsxt/img/pojo/Image.java:
/**
* 自定义实体类型,代表一个上传的文件对象
* 不适用逆向工程,是为了带着多写点代码
*/
public class Image implements Serializable {
private long id;
private String groupName;
private String remoteFileName;
private String originFileName;
private Date uploadTime;
public String getUrl() {
return groupName + "/" + remoteFileName;
}
public void setUrl(String url) {
// url属性为推导属性。url的值,是由groupName和remoteFileName组合得到的。
}
这里的url是推到属性,比如一个人如果有了姓和名,那么就不需要再弄一个name属性了,不过数据库里可以有。
mybatis逆向工程:指的是连Pojo类都不写,直接建好表后由mybatis生成
10.2 img_mapper模块
引入依赖:
<dependencies>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>img_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
定义好接口20_fdfs/img_manager/img_mapper/src/main/java/com/bjsxt/img/mapper/ImageMapper.java:
/**
* 图片数据访问接口,链接数据库MySql实现CURD操作
*/
public interface ImageMapper {
int insert(Image image);
int deleteByPK(LocateRegistry id);
Image selectById(Long id);
List<Image> select();
}
在resources中定义好mapper的xml规则20_fdfs/img_manager/img_mapper/src/main/resources/mybatis/mapper/ImageMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjsxt.img.mapper.ImageMapper">
<!-- 映射规则-->
<resultMap id="ImageMap" type="com.bjsxt.img.pojo.Image">
<id property="id" column="id"></id>
<result property="groupName" column="group_name"></result>
<result property="remoteFileName" column="remote_file_name"></result>
<result property="originFileName" column="origin_file_name"></result>
<result property="uploadTime" column="upload_time"></result>
<result property="url" column="url"></result>
</resultMap>
<insert id="insert" keyProperty="id" useGeneratedKeys="true" keyColumn="id">
insert into img_manage(id, url, group_name, remote_file_name, origin_file_name, upload_time)
values(DEFAULT, #{url}, #{groupName}, #{remoteFileName}, #{originFileName}, #{uploadTime})
</insert>
<delete id="deleteByPK">
delete from img_manage
where id = #{id}
</delete>
<select id="selectById" resultMap="ImageMap">
select id, url, group_name, remote_file_name, origin_file_name, upload_time
from img_manage
where id = #{id}
</select>
<select id="select" resultMap="ImageMap">
select id, url, group_name, remote_file_name, origin_file_name, upload_time
from img_manage
</select>
</mapper>
最后配置一下接口文件等20_fdfs/img_manager/img_mapper/src/main/resources/application-db.yml:
mybatis: #sql映射文件
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
username: root
password:
10.3 img_service_api
把刚刚写好的实体类引入依赖:
<dependencies>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>img_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
新建一个模块,并且在其中定义好需要实现的接口
20_fdfs/img_manager/img_service_api/src/main/java/com/bjsxt/img/serviceapi/ImageServiceAPI.java:
/**
* 图片服务标准接口
* dubbo的Provider需要实现这个接口提供服务
* dubbo的Consumer需要使用这个接口调用服务
*/
public interface ImageServiceAPI {
// 新增图片
int save(Image image);
// 删除图片
int remove(Long id);
// 查看图片详情
Image getById(Long id);
// 分页查看图片信息,使用PageHelper实现分页
// 返回的结果是: {row=[{图片对象}] total=总计图片数量, currentPage=当前页面,pages=总计页数, size=每页行数}
Map<String, Object> getImages();
}
10.4 img_service_provider
将必要的东西引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>img_service_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>img_mapper</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
实现api中定义的方法20_fdfs/img_manager/img_service_provider/src/main/java/com/bjsxt/img/service/impl/ImageServiceImpl.java:
/**
* 服务提供者,实现服务标准
*/
@Service //有了这个注解,dubbo会把服务自动注入进zookeeper
public class ImageServiceImpl implements ImageServiceAPI {
// 注入Mapper对象
@Autowired
private ImageMapper imageMapper;
/**
* 新增图片到数据库
* @param image 要新增的数据对象
* @return
*/
@Override
@Transactional
public int save(Image image) {
return imageMapper.insert(image);
}
/**
* 删除数据
* @param id 要删除的数据主键
* @return
*/
@Override
@Transactional
public int remove(Long id) {
return imageMapper.deleteByPK(id);
}
/**
* 主键查询
* @param id 要查询的数据主键
* @return
*/
@Override
public Image getById(Long id) {
return imageMapper.selectById(id);
}
/**
* 分页查询
* @param page 第几页
* @param rows 第几行
* @return
*/
@Override
public Map<String, Object> getImages(int page, int rows) {
// 使用分页查询
PageHelper.startPage(page, rows);
// 分页查询,返回的结果是PageHelper封装的List实现的类型Page
List<Image> list = imageMapper.select();
// 使用PageInfo辅助工具对象,实现分页数据的获取
PageInfo<Image> info = new PageInfo<>(list);
// 创建返回结果
Map<String ,Object> result = new HashMap<>();
result.put("total", info.getTotal()); // 总计数据行数
result.put("rows", list); // 当前页面的数据集合
result.put("currentPage", page); // 当前是第几页
result.put("pages", info.getPages()); // 总计多少页
return result;
}
}
写好启动类20_fdfs/img_manager/img_service_provider/src/main/java/com/bjsxt/img/ImageProviderApp.java:
@SpringBootApplication
@EnableDubbo
@MapperScan(basePackages = {"com.bjsxt.img.mapper"})
@EnableDubboConfig
public class ImageProviderApp {
public static void main(String[] args) {
SpringApplication.run(ImageProviderApp.class, args);
}
}
配置好项目20_fdfs/img_manager/img_service_provider/src/main/resources/application.yml:
spring:
profiles:
active: db
dubbo:
application: # dubbo应用必须提供一个唯一的命名,同名的dubbo应用自动组成集群
name: img_manage_provider
protocol:
name: dubbo
port: 20880
registry:
address: zookeeper://192.168.2.129:2181
config-center:
timeout: 30000
server:
port: 8081
pagehelper:
helper-dialect: mysql
10.5 img_service_consumer
首先配置文件:
<dependencies>
<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>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
可以参考之前项目实现的FastDFS工具类来实现,注意把所有方法的static删掉,不需要静态,只要一个实例类即可20_fdfs/img_manager/img_service_consumer/src/main/java/com/bjsxt/img/utils/FastDFSUtils.java:
public class FastDFSUtils {
// 通过static初始化代码块,读取配置文件,初始化客户端链接对象
// 客户端链接对象,用于实现文件读写操作时
private static StorageClient storageClient;
public FastDFSUtils() {
// 提供默认配置
Properties properties = new Properties();
properties.setProperty("fastdfs.connect_timeout_in_seconds", "5");
properties.setProperty("fastdfs.network_timeout_in_seconds", "30");
properties.setProperty("fastdfs.charset", "UTF-8");
properties.setProperty("fastdfs.http_tracker_http_port", "8080");
properties.setProperty("fastdfs.tracker_servers", "localhost:22122");
init(properties);
}
public FastDFSUtils(Properties properties) {
}
private void init(Properties properties) {
try {
// 读取配置文件,借助springboot实现配置
ClientGlobal.initByProperties(properties);
// 获取文件名称
String path = Thread.currentThread().getContextClassLoader()
.getResource("").getPath() + "fdfs_config.conf";
// 加载配置文件
ClientGlobal.init(path);
// 创建Tracker客户端对象
TrackerClient trackerClient = new TrackerClient();
// 创建tacker服务器对象
TrackerServer trackerServer = trackerClient.getConnection();
// 创建Storage服务器对象
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
// 创建Storage客户端对象
storageClient = new StorageClient(trackerServer, storageServer);
} catch (Exception e) {
e.printStackTrace();
// 初始代码块出错,一定要抛出错误,停止虚拟机
throw new ExceptionInInitializerError(e);
}
}
/**
* 使用StorageClient对象,实现文件上传
*/
public String[] uploadFile(byte[] datas, String fileName, String authName) {
try {
// 获取文件的拓展名
String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 创建文件拓展信息, 包括文件的原始名称,文件的大小,文件的上传者姓名
NameValuePair[] metaDatas = new NameValuePair[3];
metaDatas[0] = new NameValuePair("fileName", fileName);
metaDatas[1] = new NameValuePair("fileSize", datas.length + "");
metaDatas[2] = new NameValuePair("auth", authName);
/**
* 文件上传
* 获取文件的拓展名
* String[] storageClient.upload_file(byte[], String extName, NameValuePair[] metaDatas)
* datas - 要上传的文件的内容
* extName - 上传的文件的拓展名
* metaDatas - 上传文件的拓展信息是什么 如:文件的原始名称、文件的容量大小、文件的上传者等等。
*/
String[] result = storageClient.upload_file(datas, extName, metaDatas);
// 上传成功 无异常 返回字符串数组
// 字符串数组长度为2 0下标位置 卷名|组名 1下标位置是文件名(目录/文件)
// fdfs为了保证上传的文件原始名称冲突内容不冲突的情况而覆盖的问题,存储文件的时候,会提供一个uuid文件名称。
return result;
} catch (Exception e) {
e.printStackTrace();
return null; // 异常发生 返回null 代表上传失败
}
}
/**
* 文件下载
* @Param 传入一个数组,用于保存下载文件的扩展信息,如果传入null,则不需要文件拓展信息。如果不是null,则需要文件的拓展信息
* @return下载的文件内容
*/
public byte[] downloadFile(String groupName, String fileName, NameValuePair[] metaDatas) {
try {
/**
* byte[] storageClient.download_file(String groupName, String fileName)
* groupName - 卷名 | 组名
* fileName - 文件名,是文件保存在
*/
byte[] datas = storageClient.download_file(groupName, fileName);
// 要下载的文件的扩展信息
if(metaDatas != null) {
// 一个引用值,外部变量指向的是相同的数据
NameValuePair[] tmp = storageClient.get_metadata(groupName, fileName);
// 把查询到的文件拓展信息保存到传入的数组中
for(int i = 0; i < tmp.length; i++) {
metaDatas[i] = tmp[i];
}
}
// 返回下载的文件内容
return datas;
} catch (Exception e) {
e.printStackTrace();
return null; //下载失败,返回Null
}
}
/**
* 文件删除,FastDFS会自动删除这个文件对应的metaData
*/
public boolean deleteFile(String groupName, String fileName) {
try {
int result = storageClient.delete_file(groupName, fileName);
// 返回结果为0代表删除成功,其他是删除失败。
return result == 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
然后实现一个工具类,
/**
* FastDFS客户端工具类型需要使用的自动装配对象
* 用来创建工具类型的对象
*/
@Configuration
public class FastDFSAutoConfigure {
/**
* 依托SpringBoot配置文件特性,读取配置文件中配置的信息
*/
@Value("${fdfs.connect-timeout}")
private String connectTimeout;
@Value("${fdfs.network-timeout}")
private String networkTimeout;
@Value("${fdfs.charset}")
private String charSet;
@Value("${fdfs.http.tracker_http_port}")
private String trackerHttpPort;
@Value("${fdfs.tracker_server}")
private String trackerServer;
/**
* 创建工具类型的对象
* @return
*/
@Bean
public FastDFSUtils fastDFSUtils() {
// 根据SpringBoot配置文件,创建一个Properties对象
Properties properties = new Properties();
properties.setProperty("fastdfs.connect_timeout_in_seconds", connectTimeout);
properties.setProperty("fastdfs.network_timeout_in_seconds", networkTimeout);
properties.setProperty("fastdfs.charset", charSet);
properties.setProperty("fastdfs.http_tracker_http_port", trackerHttpPort);
properties.setProperty("fastdfs.tracker_servers", trackerServer);
FastDFSUtils fastDFSUtils = new FastDFSUtils(properties);
return fastDFSUtils;
}
}
10.5.1 查看图片
首先写一个逻辑类20_fdfs/img_manager/img_service_consumer/src/main/java/com/bjsxt/img/controller/ImageController.java:
@GetMapping(value = {"/", "/index"})
public String toIndex(@RequestParam(value = "page", defaultValue = "1") int page,
@RequestParam(value = "rows", defaultValue = "5") int rows,
Model model) {
// 调用服务逻辑,分页查询
Map<String, Object> result = this.imageService.getImages(page, rows);
// 查询结果使用请求作用域传递给页面
model.addAttribute("datas", result);
return "index";
}
用service类来实现20_fdfs/img_manager/img_service_consumer/src/main/java/com/bjsxt/img/service/impl/ImageServiceImpl.java:
@Service
public class ImageServiceImpl implements ImageService {
// 注入远程代理对象
@Reference
private ImageServiceAPI imageServiceAPI;
@Override
public int save(Image image) {
return 0;
}
@Override
public int remove(Long id) {
return 0;
}
public Image getById(Long id) {
return imageServiceAPI.getById(id);
}
@Override
public Map<String, Object> getImages(int page, int rows) {
return imageServiceAPI.getImages(page, rows);
}
}
前端代码:
<head>
<meta charset="UTF-8">
<title>图片管理-分页查看</title>
</head>
<body>
<div style="width: 800px; margin: auto; padding-top: 50px; text-align: center">
<a href="/toUpload">上传图片</a>
</div>
<div style="width: 800px; margin: auto; padding-top: 50px; text-align: center">
<table border="1">
<caption>图片列表</caption>
<thead>
<tr>
<th>序号</th>
<th>卷名</th>
<th>文件原始名</th>
<th>上传时间</th>
<th>预览</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="imageObj : ${datas.rows}">
<th th:text="${imageObj.id}">序号</th>
<th th:text="${imageObj.groupName}">卷名</th>
<th th:text="${imageObj.originFileName}">文件原始名</th>
<th th:text="${#dates.format(imageObj.uploadTime, 'yyyy-MM-dd HH:mm:ss')}">上传时间</th>
<th><img style="height: 50px;" th:src="@{'http://192.168.2.129:8888/' + ${imageObj.url}}"></th>
<th>
<a>下载</a>
<a>删除</a>
</th>
</tr>
<tr>
<th colspan="6">
<span th:text="${#strings.concat('总计', datas.total, '条')}"></span>
<a th:if="${datas.currentPage} !=1" th:href="@{/index(page=datas.currentPage-1,rows=datas.size)}">上一页</a>
<a th:if="${datas.currentPage} != datas.pages" th:href="@{/index(page=datas.currentPage+1,rows=datas.size)}">上一页</a>
<a>下一页</a>
<span th:text="${#strings.concat('总计', datas.pages, '页')}"></span>
</th>
</tr>
</tbody><img style="height: 50px;" src="http://192.168.2.129:8888/group1/M00/00/00/wKgCgWJBMduASUGRAAuBRYY3e1M933.png">
</table>
</div>
</body>
10.5.2 上传图片
有点不太明白业务逻辑,所以没做
10.5.3 删除图片
@GetMapping("/remove")
public String remove(Long id) {
// 从数据库查询id对应的Image对象
Image image = imageService.getById(id);
int rows = imageService.remove(id); // 删除数据库中的数据
if(rows == 1) {
// 删除数据成功,需要从FastDFS中删除对应的图片
fastDFSUtils.deleteFile(image.getGroupName(), image.getRemoteFileName());
}
return "redirect:/index?page=1&rows=5";
}
前端代码:
<th>
<a th:href="@{/download(id=${imageObj.id)}">下载</a>
<a th:href="@{/remove(id=${imageObj.id})}">删除</a>
</th>
10.5.4 下载图片
前端代码:
<th>
<a th:href="@{/download(id=${imageObj.id)}">下载</a>
<a th:href="@{/remove(id=${imageObj.id})}">删除</a>
</th>
下载:
/**
* 下载图片
* 1. 从数据库查询id对应的数据
* 2. 根据查询结果到FastDFS中招文件数据
* 3. 文件下载
*/
@GetMapping("/download")
public void download(Long id, HttpServletResponse httpServletResponse) {
try {
// 查询数据
Image images = imageService.getById(id);
// 从FastDFS中查询要下载的文件内容
NameValuePair[] metaDatas = new NameValuePair[3];
byte[] datas = fastDFSUtils.downloadFile(images.getGroupName(), images.getRemoteFileName(),metaDatas);
// 使用响应输出流向客户端输出文件内容并提示下载
// 设置响应头为流输出
httpServletResponse.setContentType("application/octet-stream");
// 获取文件的原始名称
String fileName = "";
for(NameValuePair nvp: metaDatas) {
if(nvp.getName().equals("fileName")) {
fileName = nvp.getValue();
}
}
// 编码处理,避免响应头设置的中文出现乱码
fileName = URLEncoder.encode(fileName, "UTF-8");
// 设置响应头,并标记附件文件名为fileName
httpServletResponse.setHeader("content-disposition", "attachment;filename="+fileName);
// 输出文件内容到客户端
httpServletResponse.getOutputStream().write(datas);
// 刷新输出缓冲流
httpServletResponse.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
return;
}
}