FastDFS架构
FastDFS架构包括 Tracker server和Storage server。客户端请求Tracker server进行文件上传、下载,通过Tracker server调度最终由Storage server完成文件上传和下载。
Tracker server作用是负载均衡和调度
Storage server作用是文件存储,客户端上传的文件最终存储在Storage服务器上,可以将storage称为存储服务器。
服务端两个角色:
Tracker:管理集群,tracker也可以实现集群。每个tracker节点地位平等。
收集Storage集群的状态。
Storage:实际保存文件
Storage分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。
文件上传
客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
安装好的fastdfs服务器 安装步骤:https://blog.youkuaiyun.com/lushizhuo9655/article/details/98868186
将fastdfs客户端安装到本地仓库 将此项目导入到eclipse中,然后install即可
然后将依赖添加进e3-manager-web的pom文件即可
<dependency>
<groupId>fastdfs_client</groupId>
<artifactId>fastdfs_client</artifactId>
<version>1.25</version>
</dependency>
通过fastdfs客户端测试上传图片
配置文件client.conf(ip是变化的)
tracker_server=192.168.0.242:22122
public class FastdfsTest {
@Test
public void showUpload() throws Exception{
// 1、加载配置文件,配置文件中的内容就是tracker服务的地址。这是配置文件的绝对路径
ClientGlobal.init("E:/20200520/e3-manager-web/conf/client.conf");
// 2、创建一个TrackerClient对象。直接new一个。
TrackerClient trackerClient = new TrackerClient();
// 3、使用TrackerClient对象创建连接,获得一个TrackerServer对象。
TrackerServer trackerServer = trackerClient.getConnection();
// 4、创建一个StorageServer的引用,值为null
StorageServer storageServer = null;
// 5、创建一个StorageClient对象,需要两个参数TrackerServer对象、StorageServer的引用
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
// 6、使用StorageClient对象上传图片。
//扩展名不带“.” 上传文件的全路径 扩展名 元数据
String[] strings = storageClient.upload_file("D:/practice/1.jpg", "jpg", null);
// 7、返回数组。包含组名和图片的路径。
for (String string : strings) {
System.out.println(string);
}
}
}
控制台打印结果
图片上传功能
使用的是KindEditor的多图片上传插件。
KindEditor 4.x 文档
将pom依赖从e3-manager-web的pom中移到common中,然后提供一个FastDFSClient的工具类调用
package cn.e3mall.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);
}
}
- 需要把commons-io、fileupload 的jar包添加到工程中。(目前缺fileupload的依赖)
<!-- 文件上传组件 -->
<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>
配置一个上传图片的服务器地址,方便修改resource.properties
IMAGE_SERVER_URL=http://192.168.0.242/
在springmvc.xml中加载次配置文件
<context:property-placeholder location="classpath:conf/resource.properties"/>
/**
* 图片上传管理
*/
@Controller
@ResponseBody
public class PictureController {
@Value("${IMAGE_SERVER_URL}")
private String IMAGE_SERVER_URL;
@RequestMapping("/pic/upload")
public Map uploadPic(MultipartFile uploadFile){
try {
//把图片上传到图片服务器
FastDFSClient fastDFSClient=new FastDFSClient("classpath:conf/client.conf");
//取文件扩展名
String originalFilename = uploadFile.getOriginalFilename();
String extName=originalFilename.substring(originalFilename.lastIndexOf(".")+1);
//得到一个图片的地址和文件名
String url = fastDFSClient.uploadFile(uploadFile.getBytes(), extName);//上传内容 扩展名
//补充为完整的url
url=IMAGE_SERVER_URL+url;
Map result=new HashMap<>();
result.put("error", 0);
result.put("url", url);
return result;
} catch (Exception e) {
e.printStackTrace();
Map result=new HashMap<>();
result.put("error", 1);
result.put("message", "上传图片失败");
return result;
}
}
}
但是这样只能在google浏览器上传成功,别的浏览器会失败,原因是浏览器的兼容性对kindeditor不好
响应Map,注解是responsebody 响应的content-Type为application/json
兼容性最好的是text/plain 表示普通文本
提供一个对象和json字符串转换的工具类
package cn.e3mall.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;
}
}
修改代码,解决浏览器的兼容性问题
指定响应结果的content-type和字符集是utf-8
富文本编辑器的使用方法
KindEditor
UEditor:百度编辑器
http://ueditor.baidu.com/website/
纯js开发,跟后台语言没有关系。
使用方法
第一步:在jsp中引入KindEditor的css和js代码。
第二步:在表单中添加一个textarea控件。是一个富文本编辑器的载体。类似数据源。
第三步:初始化富文本编辑器。使用官方提供的方法初始化。
第四步:取富文本编辑器的内容。
表单提交之前,把富文本编辑器的内容同步到textarea控件中。
<link href="/js/kindeditor-4.1.10/themes/default/default.css" type="text/css" rel="stylesheet">
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/kindeditor-all-min.js"></script>
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/lang/zh_CN.js"></script>
商品添加功能实现
返回值:
Json数据。应该包含一个status的属性。
可以使用E3Result,放到e3-common中。
package cn.e3mall.common.utils;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 淘淘商城自定义响应结构
*/
public class E3Result implements Serializable{
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
public static E3Result build(Integer status, String msg, Object data) {
return new E3Result(status, msg, data);
}
public static E3Result ok(Object data) {
return new E3Result(data);
}
public static E3Result ok() {
return new E3Result(null);
}
public E3Result() {
}
public static E3Result build(Integer status, String msg) {
return new E3Result(status, msg, null);
}
public E3Result(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public E3Result(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 E3Result formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, E3Result.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 E3Result format(String json) {
try {
return MAPPER.readValue(json, E3Result.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Object是集合转化
*
* @param jsonData json数据
* @param clazz 集合中的类型
* @return
*/
public static E3Result 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;
}
}
}
1、生成商品id
实现方案:
a)Uuid,字符串,不推荐使用。
b)数值类型,不重复。日期+时间+随机数20160402151333123123
c)毫秒值+随机数。可以使用。
d)使用redis。Incr。推荐使用。
生成商品id的工具类
package cn.e3mall.common.utils;
import java.util.Random;
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());
}
}
Dao层
向tb_item, tb_item_desc表中插入数据
可以使用逆向工程
Service层
参数:TbItem item,String desc
业务逻辑:略,参加上面
返回值:E3Result
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private TbItemMapper itemMapper;
@Autowired
private TbItemDescMapper tbItemDescMapper;
public E3Result saveItem(TbItem tbItem, String desc) {
// 生成商品id
long itemId = IDUtils.genItemId();
// 2、补全TbItem对象的属性
tbItem.setId(itemId);
//商品状态1-正常,2-下架,3-删除
tbItem.setStatus((byte)1);
Date date=new Date();
tbItem.setCreated(date);
tbItem.setUpdated(date);
itemMapper.insert(tbItem);
// 4、创建一个TbItemDesc对象
TbItemDesc tbItemDesc=new TbItemDesc();
// 5、补全TbItemDesc的属性
tbItemDesc.setItemId(itemId);
tbItemDesc.setItemDesc(desc);
tbItemDesc.setCreated(date);
tbItemDesc.setUpdated(date);
tbItemDescMapper.insert(tbItemDesc);
return E3Result.ok();
}
@Controller
public class ItemController {
@Autowired
private ItemService itemService;
@RequestMapping(value="item/save",method=RequestMethod.POST)
@ResponseBody
public E3Result saveItem(TbItem tbItem,String desc){
E3Result result =itemService.saveItem(tbItem, desc);
return result;
}
}
商品修改、商品删除、上架下架。以后有空在补上(比较简单目前就不写了)