1. 1 什么是FastDFS?
FastDFS是用c语言编写的一款开源的分布式文件系统。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
2. FastDFS的架构
FastDFS架构包括 Tracker server和Storage server。客户端请求Tracker server进行文件上传、下载,通过Tracker server调度最终由Storage server完成文件上传和下载。
Tracker server作用是负载均衡和调度,通过Tracker server在文件上传时可以根据一些策略找到Storage server提供文件上传服务。可以将tracker称为追踪服务器或调度服务器。
Storage server作用是文件存储,客户端上传的文件最终存储在Storage服务器上,Storage server没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。
![](https://img.haomeiwen.com/i2632731/3dcfde11ba5589a3.png)
服务端两个角色:
Tracker:管理集群,tracker也可以实现集群。每个tracker节点地位平等。
收集Storage集群的状态。
Storage:实际保存文件
Storage分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。
3. 文件上传的流程
![](https://img.haomeiwen.com/i2632731/5a98014fe4716579.png)
组名:文件上传后所在的storage组名称,在文件上传成功后有storage服务器返回,需要客户端自行保存。
虚拟磁盘路径:storage配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1则是M01,以此类推。
数据两级目录:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
4. 图片服务器的安装
请单独查阅安装文档
这里我们可以使用已将搭建好图片服务器的虚拟机进行测试
5. 封装工具进行文件上传
FastDFSClient
package common.utils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
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);
}
}
6. 使用工具上传
- 导入文件上传需要的jar包
<!-- 文件上传组件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
- 在springmvc.xml中配置多媒体解析器
<!-- 配置多媒体解析器 -->
<!-- 定义文件上传解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设定文件上传的最大值5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
</bean>
- 创建配置文件client.conf,指定tracker_server的地址,进行FastDFSClient的创建
tracker_server=192.168.25.133:22122
- 在资源文件中,配置文件上传的url
在config文件夹中创建,resource.properties
#图片服务器的地址
IMAGE_SERVER_URL=http://192.168.25.133/
- 在springmvc.xml中读取配置文件
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:conf/resource.properties" />
- 提供Object转Json的工具类
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
JsonUtils
package common.utils;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 淘淘商城自定义响应结构
*/
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* <p>Title: pojoToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 创建controller进行文件上传
package controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import common.utils.FastDFSClient;
import common.utils.JsonUtils;
/**
* 图片上传处理Controller
* <p>Title: PictureController</p>
* <p>Description: </p>
* <p>Company: www.itcast.cn</p>
* @version 1.0
*/
@Controller
public class PictureController {
// 使用properties中配置的图片服务器地址进行上传
@Value("${IMAGE_SERVER_URL}")
private String IMAGE_SERVER_URL;
// uploadFile:前端上传时,需要使用这个字段作为参数
@RequestMapping(value="/pic/upload", produces=MediaType.TEXT_PLAIN_VALUE+";charset=utf-8")
@ResponseBody
public String uploadFile(MultipartFile uploadFile) {
try {
//把图片上传的图片服务器
FastDFSClient fastDFSClient = new FastDFSClient("classpath:conf/client.conf");
//取文件扩展名
String originalFilename = uploadFile.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//得到一个图片的地址和文件名(group/M00/01/02/dddwdqsqxaxa.jpg)
String url = fastDFSClient.uploadFile(uploadFile.getBytes(), extName);
//补充为完整的url(http://192.168.11.23/group/M00/01/02/dddwdqsqxaxa.jpg)
url = IMAGE_SERVER_URL + url;
//封装到map中返回
Map result = new HashMap<>();
result.put("error", 0);
result.put("url", url);
return JsonUtils.objectToJson(result);
} catch (Exception e) {
e.printStackTrace();
Map result = new HashMap<>();
result.put("error", 1);
result.put("message", "图片上传失败");
return JsonUtils.objectToJson(result);
}
}
}
7. bigint类型的商品id,主键生成策略
使用工具进行主键生成,当前时间的毫秒值+随机数
IDUtils
package common.utils;
import java.util.Random;
/**
* 各种id生成策略
* <p>Title: IDUtils</p>
* <p>Description: </p>
* <p>Company: www.itcast.com</p>
* @author 入云龙
* @date 2015年7月22日下午2:32:10
* @version 1.0
*/
public class IDUtils {
/**
* 图片名生成
*/
public static String genImageName() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上三位随机数
Random random = new Random();
int end3 = random.nextInt(999);
//如果不足三位前面补0
String str = millis + String.format("%03d", end3);
return str;
}
/**
* 商品id生成
*/
public static long genItemId() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上两位随机数
Random random = new Random();
int end2 = random.nextInt(99);
//如果不足两位前面补0
String str = millis + String.format("%02d", end2);
long id = new Long(str);
return id;
}
public static void main(String[] args) {
for(int i=0;i< 100;i++)
System.out.println(genItemId());
}
}
8. 返回给前端的数据封装类
UjpResult
package common.utils;
import java.io.Serializable;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UjpResult implements Serializable{
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
public static UjpResult build(Integer status, String msg, Object data) {
return new UjpResult(status, msg, data);
}
public static UjpResult ok(Object data) {
return new UjpResult(data);
}
public static UjpResult ok() {
return new UjpResult(null);
}
public UjpResult() {
}
public static UjpResult build(Integer status, String msg) {
return new UjpResult(status, msg, null);
}
public UjpResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public UjpResult(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
// public Boolean isOK() {
// return this.status == 200;
// }
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
/**
* 将json结果集转化为TaotaoResult对象
*
* @param jsonData json数据
* @param clazz TaotaoResult中的object类型
* @return
*/
public static UjpResult formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, UjpResult.class);
}
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (clazz != null) {
if (data.isObject()) {
obj = MAPPER.readValue(data.traverse(), clazz);
} else if (data.isTextual()) {
obj = MAPPER.readValue(data.asText(), clazz);
}
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
/**
* 没有object对象的转化
*
* @param json
* @return
*/
public static UjpResult format(String json) {
try {
return MAPPER.readValue(json, UjpResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Object是集合转化
*
* @param jsonData json数据
* @param clazz 集合中的类型
* @return
*/
public static UjpResult formatToList(String jsonData, Class<?> clazz) {
try {
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (data.isArray() && data.size() > 0) {
obj = MAPPER.readValue(data.traverse(),
MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
}
9. 添加商品
ItemServiceImpl
@Override
public UjpResult addItem(TbItem item, String desc) {
//生成商品id
long itemId = IDUtils.genItemId();
//补全item的属性
item.setId(itemId);
//1-正常,2-下架,3-删除
item.setStatus((byte) 1);
item.setCreated(new Date());
item.setUpdated(new Date());
//向商品表插入数据
itemMapper.insert(item);
//创建一个商品描述表对应的pojo对象。
TbItemDesc itemDesc = new TbItemDesc();
//补全属性
itemDesc.setItemId(itemId);
itemDesc.setItemDesc(desc);
itemDesc.setCreated(new Date());
itemDesc.setUpdated(new Date());
//向商品描述表插入数据
itemDescMapper.insert(itemDesc);
//返回成功
return UjpResult.ok();
}
网友评论