第三篇:实现图片上传功能和KindEditor的使用
前言:
上面我们已经实现了框架的整合和利用插件来生产对应的Mybatis数据查询所需要的代码和利用PageHelper插件来实现分页。下面进行的是:
1.商品类目的选择
2.图片上传:
a) 图片服务器FastDFS
b) 图片上传功能的实现
3.富文本编辑器KindEditor
4.商品添加功能的完成
1.商品类目的选择
先来看看要实现的效果:
image.png
1.1 功能分析
来看看前端的代码:
image.png
image.png
对应所实现的绑定事件在common.js里面可以看到。
展示商品分类列表使用EasyUI的tree控件实现。那么我们返回的数据格式是什么样的呢?下面做分析:
初始化tree请求的url:/item/cat/list
参数:
初始化tree的时候只需要把第一级的节点展示,子节点异步加载
long id(父节点id)
返回值:json。数据格式
[{
"id": 1,
"text": "Node 1",
"state": "closed"
},{
"id": 2,
"text": "Node 2",
"state": "closed"
}]
state:如果节点下面还有节点则值为“closed”,否则为“open”。
我们看到返回的数据格式了。我们最好的做法是创建一个专门的类来存储这些数据;将类放在common-utils包中
image.png
具体代码:
package com.taotao.common.pojo;
import java.io.Serializable;
/**
* 商品分类返回数据的实体
*/
public class EasyUITreeNode implements Serializable{
private long id;
private String text;
private String state;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
其中id:父节点的id;text:内容;state:是否有子节点
1.2 Dao层
数据库要查询的表为tb_item_cat;查询的列为id,name,isparent;
我们可以用上一篇中逆向工程生产的代码
1.3Service层
参数:long parentId
业务逻辑:
1.根据parentid查询节点列表
2.将数据封装成EasyUITreeNode的格式
3.返回值:List<EasyUITreeNode>
代码如下:
interface
public interface ItemCatService {
List<EasyUITreeNode> getItemCatList(long parentId);
}
impl:
@Service
public class ItemCatServiceImpl implements ItemCatService {
@Autowired
private TbItemCatMapper tbItemCatMapper;
@Override
public List<EasyUITreeNode> getItemCatList(long parentId) {
//1.根据父id查询,创建一个对应的Excample
TbItemCatExample example = new TbItemCatExample();
//2.设置查询条件
Criteria criteria = example.createCriteria();
//设置parentid
criteria.andParentIdEqualTo(parentId);
//3.执行查询,返回数据库的数据
List<TbItemCat> list = tbItemCatMapper.selectByExample(example);
//4.封装数据
List<EasyUITreeNode> result = new ArrayList<>();
//转换成EasyUITreeNode列表
for (TbItemCat tbItemCat:
list) {
EasyUITreeNode node = new EasyUITreeNode();
node.setId(tbItemCat.getId());
node.setText(tbItemCat.getName());
//getIsParent是否父节点,是的话closed不是的话open
node.setState(tbItemCat.getIsParent()? "closed":"open");
result.add(node);
}
return result;
}
}
1.4 在applicatonContext-service中发布服务
image.png1.5表现层
接收服务
Controller:
参数:
long id(父节点id)
返回值:json。数据格式
List<EasyUITreeNode>
@Controller
public class ItemCatController {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
private ItemCatService itemCatService;
@RequestMapping("/item/cat/list")
@ResponseBody
public List<EasyUITreeNode> getItemCatList(@RequestParam(name = "id",defaultValue = "0") long parentId) {
List<EasyUITreeNode> result = itemCatService.getItemCatList(parentId);
return result;
}
}
效果:
image.png
我们可以通过查看请求头看到id
2.图片上传服务器
我们先来看看传统的上传方式:
传统方式的话,由于我们面对的客户群较少。所有的模块都放在一个项目中开发;这样在客户群较少的情况下影响不大。但是如果是互联网项目,用户访问量大,这样一个Tomcat服务器是远远不能满足业务需求。这就需要利用集群的技术去解决。如下图;但是这样也会有一个问题。就是我们将a.jpg放在了Tomcat1中。但是由于Ngnix的负载均衡处理请求。第二次发送访问图片的请求的时候Ngnix就把请求给了Tomcat2中,但是我们Tomcat2中是不存在a.jpg的。这样就会出现无法访问的情况
image.png
为了解决以上的情况我们可以用FastDFS集群来解决这个问题。什么是FastDFS?
科普时间:
FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。它的优势是可以水平扩容,FastDFS存储资源的设备是按组来区分的,当存储空间不足时,便可以通过水平增加分组并相应添加设备来达到扩容的目的,而且是没有上限的。还有个优势是高可用,也就是说FastDFS集群能够做到当提供服务的nginx发生故障时,自动切换到另一台nginx设备上,保障服务的稳定。想了解更多关于FastDFS内容可以百度一下~所以我们采取的方式如下:
image.png
2.1 图片服务器的搭建
在这里我们使用的单机版的FastDFS。FastDFS推荐在linux情况下搭建。。。 所以笔者就跑去搭建Linux环境去了(花了一天,-_-||。。流下了没有技术的流水o(╥﹏╥)o)。在搭建的时候参考的博文是:https://blog.csdn.net/u012453843/article/details/69951920
2.2 测试FastDFS
在我们进行测试的时候要先准备两点:
1.将fastdfs-client(可以从github中下载源码)转化成maven工程;在idea中先导入工程,然后
image.png
,然后在taotao-manager-web中添加响应的依赖
2.创建一个client.conf文件保存我们的trackserver地址:
image.png
记得改成自己配的地址!不然会报错的
具体步骤:
//1.向工程中添加jar包
//2.创建一个配置文件、配置tracker的服务器地址
//3.加载配置文件
//4.创建一个TrackerCilent对象
//5.使用TrackerClient对象获得TrackerServer对象
//6.创建一个StorageServer的null
//7.创建一个StorageClient对象、TrackerServer,StorageServer两个参数
//8/使用StorageClient对象上传文件
下面是代码:
public class TestUploadFile {
@Test
public void uploadFile() throws IOException, MyException {
//1.向工程中添加jar包
//2.创建一个配置文件、配置tracker的服务器地址
//3.加载配置文件
//4.创建一个TrackerCilent对象
//5.使用TrackerClient对象获得TrackerServer对象
//6.创建一个StorageServer的null
//7.创建一个StorageClient对象、TrackerServer,StorageServer两个参数
//8/使用StorageClient对象上传文件
ClientGlobal.init("S:/develop/IdeaProjests/shopping/taotao-manager-web/src/main/resources/resource/client.conf");
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer,storageServer);
String[] filePath = storageClient.upload_file("F:/pictures/1.jpg","jpg",null);
for (String str:
filePath) {
System.out.println(str);
}
}
我们可以利用返回中的字符串去访问我们上传的图片,效果如下
image.png
我们看到上面的代码十分繁琐。上传图片的功能在以后的功能中也可能使用。所以我们考虑把他抽取出来封装一下,让其变成工具类:
image.png
/**
* 上传图片工具类
*/
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);
}
}
2.3 图片上传功能实现
功能分析:
image.png
我们看下list-add.jsp页面,可以看到上传图片触发方法picFileUpload是通过class来处理的,在<a>标签的下方是一个隐藏域,是用来接收上传到图片服务器的回显地址的,当我们提交表单的时候,可以把这些图片地址保存到数据库中。
image.png
页面加载完之后,会自动调用TAOTAO.init进行初始化
image.png
image.png
image.png
TAOTAO在common.js中定义。我们可以到请求的地址和请求的参数和一些相关的配置。作为后台开发的话关注点在于请求的地址和参数就OK啦~
下面进行具体的开发:
1.导入jar包。和文件上传相关的commons-io和commons-fileupload开发包
在taotao-manager-web中的pom.xml中添加
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
</dependency>
2.在taotao-manager-web工程的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>
3.配置访问图片前缀
我们访问图片是以http方式访问的。例如:http://192.168.208.50:8080/group1/M00/00/00/wKjQMlsux8WAKmnxAAF9oSwPz9g765.jpg。在配置文件中配置的话可以使我们的代码更加灵活。至于这一块在哪会用到可以看一下的代码
4.加载配置文件
在spring中我们需要加载该配置文件,因此我们在springmvc.xml中加入<context:property-placeholder location="classpath:resource/resource.properties"/>
image.png
5.创建Controller
业务逻辑:
1、接收页面传递的图片信息uploadFile
2、把图片上传到图片服务器。使用封装的工具类实现。需要取文件的内容和扩展名。
3、图片服务器返回图片的url
4、将图片的url补充完整,返回一个完整的url。
5、把返回结果封装到一个Map对象中返回。
为什么我们要这样返回数据呢?们可以从kindeditor官网http://kindeditor.net/docs/upload.html查看一下
image.png
显然我们可以创建一个类来表示。但是我们这里采取Map的方式来处理
@Controller
public class PicController {
//获得配置文件的值
@Value("${IMAGE_SERVER_URL}")
private String IMAGE_SERVER_URL;
@RequestMapping("/pic/upload")
@ResponseBody
public String fileUpload(MultipartFile uploadFile) {
try {
//获得文件的扩展名
String originalFilename = uploadFile.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//创建一个FastDFS客户端
FastDFSClient fastDFSClient = new FastDFSClient("classpath:resource/client.conf");
//执行上传处理,返回一个路径
String path = fastDFSClient.uploadFile(uploadFile.getBytes(),extName);
String url = IMAGE_SERVER_URL + path;
Map result = new HashMap<>();
result.put("error",0);
result.put("url",url);
String json = JsonUtils.objectToJson(result);
return json;
}catch (Exception e) {
e.printStackTrace();
Map result = new HashMap<>();
result.put("error",1);
result.put("message","上传图片失败");
String json = JsonUtils.objectToJson(result);
return json;
}
}
}
我们可以看到上面我们用到了一个json的工具类
/**
* 淘淘商城自定义响应结构
*/
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;
}
}
为什么要这样用呢?
这是因为KindEditor的图片上传插件,对浏览器兼容性不好。使用火狐浏览器浏览的时候无法使用这个功能(,因为Map类型的返回值在火狐浏览器无法识别)。因此采用json串的方式来解决~(大家可以自己去测试一下,在不使用String返回类型的时候)。
2.4测试结果:
记得我们在增加了接口的时候和工具类的时候记得重新将包导入到本地仓库中
image.png
image.png
image.png
点一张进去瞧瞧:
image.png
好了~!我们图片上传的功能实现了
3.KindEditor
富文本编辑器是什么?如图
image.png
3.1使用方法
1.在jsp中引入KindEditor的css和js代码
image.png
2.在表单中添加一个textarea控件。是一个富文本编辑器的载体。类似数据源
image.png
3.初始化富文本编辑器。使用官方提供的方法初始化
image.png
image.png
4.取富文本编辑器的内容。表单提交之前,把富文本编辑器的内容同步到textarea控件中
image.png
3.2 商品添加功能的实现
请求的url:/item/save
参数:表单的数据。可以使用pojo接收表单的数据,要求pojo的属性和input的name属性要一致。
使用TbItem对象接收表单的数据。
TbItem item,String desc
返回值:
Json数据。应该包含一个status的属性。
这里我们用一个类来封装数据:
/**
* 淘淘商城自定义响应结构
*/
public class TaotaoResult implements Serializable{
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
public static TaotaoResult build(Integer status, String msg, Object data) {
return new TaotaoResult(status, msg, data);
}
public static TaotaoResult ok(Object data) {
return new TaotaoResult(data);
}
public static TaotaoResult ok() {
return new TaotaoResult(null);
}
public TaotaoResult() {
}
public static TaotaoResult build(Integer status, String msg) {
return new TaotaoResult(status, msg, null);
}
public TaotaoResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public TaotaoResult(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 TaotaoResult formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, TaotaoResult.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 TaotaoResult format(String json) {
try {
return MAPPER.readValue(json, TaotaoResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Object是集合转化
*
* @param jsonData json数据
* @param clazz 集合中的类型
* @return
*/
public static TaotaoResult 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(采用毫秒值加随机数)
**
* 各种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());
}
}
2.补全TbItem对象的属性
3.插入商品表
4.创建一个TbItemDesc对象
5.补全TbItemDesc对象的属性
6.向商品描述表中插入数据
7.TaotaoRestful.ok();
Dao层
向tb_item, tb_item_desc表中插入数据
可以使用逆向工程
Service层
参数:TbItem item,String desc
业务逻辑:略,参加上面
返回值:TaotaoResult
在interface中添加:
image.png
在ItemServiceImpl中
@Override
public TaotaoResult addItem(TbItem item, String desc) {
long id = IDUtils.genItemId();
item.setId(id);
item.setCreated(new Date());
item.setUpdated(new Date());
item.setStatus((byte) 1);
tbItemMapper.insert(item);
TbItemDesc itemDesc = new TbItemDesc();
itemDesc.setItemDesc(desc);
itemDesc.setCreated(new Date());
itemDesc.setUpdated(new Date());
itemDescMapper.insert(itemDesc);
return TaotaoResult.ok();
}
发布服务、接收服务
由于我们这个不是新的服务,所以配置文件不用做修改。
表现层
请求的url:/item/save
参数:TbItem item,String desc
返回值:TaotaoResult
@RequestMapping("/item/save")
@ResponseBody
public TaotaoResult addItem(TbItem item,String desc) {
return itemService.addItem(item,desc);
}