商城03——完成商品添加功能

1.   课程计划

完成商品添加功能

1、商品类目选择

2、图片上传

3、图片服务器搭建

4、kindEditor富文本编辑器的使用

5、商品添加功能

2.   实现商品类目选择功能

2.1. 需求

在商品添加页面,点击“选择类目”显示商品类目列表:

使用easyUI的异步tree控件来完成。

异步tree的行为

  树控件读取URL。子节点的加载依赖于父节点的状态。当展开一个封闭的节点,如果节点没有加载子节点,它将会把节点id的值作为http请求参数并命名为‘id’,通过URL发送到服务器上面检索子节点。

异步tree的数据结构

  • id:节点id
  • text:节点名称
  • state:如果不是叶子节点就是closed,叶子节点就是open。close的节点点击后会在此发送请求查询子项目。

{

  "id":1,

  "text":"Node1",

  "state":"closed"     //如果节点为父节点则状态为“closed”,如果是叶子节点“open”

}

2.2.数据库

表中的数据为树形结构,可以满足要求

2.3 功能分析

 index.jsp:新增商品--->item-add.jsp

 

item-add.jsp: 选择类目--->selectItemCat

selectItemCat在common.js中实现   请求初始化树形控件的url:/item/cast/list

 

点击父节点,请求初始化子节点动作是tree空间封装好的,每打开一个父节点做一次ajax请求

请求参数:id:当前节点的id。根据此id查询子节点

返回结果:一个json数据,是一个列表:

[

{

  "id":1,

  "text":"Node1",

  "state":"closed"     //如果节点为父节点则状态为“closed”,如果是叶子节点“open”

},

{

  "id":2,

  "text":"Node2",

  "state":"closed"     //如果节点为父节点则状态为“closed”,如果是叶子节点“open”

}

]

2.4. DAO层

  select * from tb_item_cat where parent_id=2

  使用逆向工程生成的mapper文件:TbItemCatMapper.java、TbItemCatMapper.xml

2.5. Service层

  功能:接收parentid参数,根据parentid查询子类目类别。返回一个分类列表。可以创建一个pojo来描述一个节点的格式,返回一个pojo列表。

  创建一个pojo:

    包含id,text,state属性。因为其他工程也可能用到此pojo,所以应放在taotao-common中:

public class EasyUITreeNode implements Serializable {
    private Long id;  //节点id
    private String text; //节点名称
    private String state; //如果不是叶子节点就是close,叶子节点就是open
    set/get。。。。。
}

public interface ItemCatService {
    public List<TbItemCat> getItemCatList(Long parentId);
}

@Service
public class ItemCatServiceImpl implements ItemCatService {

    @Autowired
    private TbItemCatMapper itemCatMapper;
    
    @Override
    public List<EasyUITreeNode> getItemCatList(Long parentId) {
        //创建查询条件
        TbItemCatExample example = new TbItemCatExample();
        Criteria criteria = example.createCriteria();
        //根据parentid查询子节点
        criteria.andParentIdEqualTo(parentId);
        List<TbItemCat> list = itemCatMapper.selectByExample(example);
        //把列表转换成treeNodeList
        List<EasyUITreeNode> resultList = new ArrayList<EasyUITreeNode>();
        for(TbItemCat tbItemCat:list){
            EasyUITreeNode node = new EasyUITreeNode();
            node.setId(tbItemCat.getId());
            node.setText(tbItemCat.getName());
            node.setState(tbItemCat.getIsParent()?"closed":"open");
            resultList.add(node);
        }
        //返回结果
        return resultList;
    }
}

2.6 Controller层

 功能:接收页面请求的参数,名为id。调用service查询分类列表。返回json格式是列表。需要使用@ResponseBody注解。

 

@Controller
@RequestMapping("/item/cat")
public class ItemCatController {

    @Autowired
    private ItemCatService itemCatService;
    
    @RequestMapping("/list")
    @ResponseBody
    public List<EasyUITreeNode> getItemCatList(@RequestParam(value="id",defaultValue="0")Long parentId){
        List<EasyUITreeNode> list = itemCatService.getItemCatList(parentId);
        return list;
    }
}

3.实现上传图片功能

首先安装nginx来提供http服务。过程略。

FastDFS只是解决了图片保存问题,要通过http访问图片 必须安装nginx。

3.1 分布式文件服务器FastDFS

3.1.1 什么是FastDFS

  FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

  FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

  Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

  Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。

服务端两个角色:

Tracker:管理集群,tracker 也可以实现集群。每个 tracker 节点地位平等。收集 Storage 集群的状态。

Storage:实际保存文件   Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。

3.1.2 文件上传及下载的流程

(1) 文件上传流程

 

  客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

  

   组名:文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回,需要客户端自行保存。

  虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

  数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

  文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

(2) 文件下载流程

 

3.1.3 最简单的 FastDFS 架构

 

3.1.4 FastDFS安装

  略,自己安装的FastDFS的IP地址固定为192.168.25.133

3.1.5 FastDFS实现图片上传小测试

这里我们在taotao-manager-web工程下做测试,在其下的的 src/test/java 下建一个测试类FastDfsTest。

(1)首先在pom.xml引入fastdfs的依赖:

<dependency>
            <groupId>org.csource.fastdfs</groupId>
            <artifactId>fastdfs</artifactId>
            <version>1.2</version>
        </dependency>
View Code

(2)在 src/main/resources 的resource 文件夹下建一个资源文件fastdfs.conf,里面内容为trackServer的IP地址与端口号:

  tracker_server=192.168.25.133:22122

(3)FastDfsTest:

public class FastDfsTest {

    @Test
    public void testUplode() throws Exception{
        //创建一个配置文件,文件名任意,内容就是tracker服务器的地址。在fastdfs.conf中已配置,名称为tracker_server
        //1.加载配置文件。(这时我们就知道tracker在什么地方了)
        ClientGlobal.init("D:/Workspaces/MyEclipse 2015/taotao-manager-web/src/main/resources/resource/fastdfs.conf");
        //2.构建一个管理者客户端TrackerClient
        TrackerClient trackerClient = new TrackerClient();
        //3.连接管理者服务端
        TrackerServer trackerServer = trackerClient.getConnection();
        //4.声明存储服务端StorageServer
        StorageServer storageServer = null;
        //5.获取存储服务端的客户端对象
        StorageClient storageClient = new StorageClient(trackerServer, storageServer);
        //6.上传文件
        String[] strings = storageClient.upload_file("F:/pictures/1.jpg", "jpg", null);
        for (String string : strings) {
            System.out.println(string);
        }
    }
}

输出结果

 group1
 M00/00/00/wKgZhV0A-POAUmyRAAE0WLGwjmM340.jpg

测试:网页输入http://192.168.25.133/group1/M00/00/00/wKgZhV0A-POAUmyRAAE0WLGwjmM340.jpg便得到了要显示的图片:

3.1.6 使用FastDFSClient工具类实现图片上传功能

将 fastDFS 上传文件封装到一个工具类FastDFSClient中,放到taotao-manager-common工程下的util包下,注意引入fastDFS依赖。

package com.taotao.common.util;

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);
    }
    
    /**
     * 上传文件方法
     * @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);
    }
    
    /**
     * 上传文件方法
     * @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);
    }
}
View Code

测试:

@Test
    public void testFastDfsClient() throws Exception {
        FastDFSClient fastDFSClient = new FastDFSClient("D:/Workspaces/MyEclipse 2015/taotao-manager-web/src/main/resources/resource/fastdfs.conf");
        String string = fastDFSClient.uploadFile("F:/pictures/2.jpg", "jpg");
            System.out.println(string);
    }

输出结果:
group1/M00/00/00/wKgZhV0BAaiAYtIxAAHlC6LIRGI190.jpg

3.2 实现图片上传

前端:

item-add:上传图片--->picFileUpload

common.js: initPicUpload --->kingEditorParams

kingEditorParams:

使用的是KindEditor的多图片上传插件。

KindEditor4.x文档:http://kindeditor.net/doc.php

请求的url:/pic/upload

参数:MultiParFile uploadFile

返回值:(json数据)

可以创建一个pojo对应返回值,也可以使用map。

图片上传功能在taotao-manager-web(控制层)实现:

3.2.1 配置文件

(1)在pinyougou-shop-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>

(2)在 src/main/resources 的resource 文件夹下建一个资源文件resource.properties,里面内容为 图片服务器 的地址:

    TAOTAO_IMAGE_SERVER_URL=http://192.168.25.133/

  然后在springmvc.xml中加载该配置文件:   (之后在Controller中加@value("${...}")取值)

  <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:resource/resource.properties" />

3.2.2 Controller层:

@Controller
public class PictureController {
    
    @Value("${TAOTAO_IMAGE_SERVER_URL}")
    private String TAOTAO_IMAGE_SERVER_URL;
    
    
    @RequestMapping("/pic/upload")
    @ResponseBody
    public Map uploadFile(MultipartFile uploadFile) {
        try {
            //1.创建一个 FastDFS 的客户端
            FastDFSClient fastDFSClient = new FastDFSClient("classpath:resource/fastdfs.conf");
            //取文件扩展名
            String originalFilename = uploadFile.getOriginalFilename();
            String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
            //执行上传处理
            String url = fastDFSClient.uploadFile(uploadFile.getBytes(), extName);
            
            //拼接返回的 url和 ip 地址,拼装成完整的 url
            url = TAOTAO_IMAGE_SERVER_URL + url;
            //封装到map中返回
            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;
        }    
    }
}

 这里在谷歌浏览器上传失败了!是因为浏览兼容性问题

3.2.3 解决浏览器兼容性问题

使Controller层返回String类型

@Response 直接响应浏览器,不走逻辑视图,相当于调用response对象调用read方法,往浏览器写内容。如果返回值是对象,它会默认将对象变为json再响应。如果直接返回字符串Sting,它就不转换了,因为浏览器可以识别字符串。

为了解决浏览器兼容问题,我们不再返回Map,而是返回String,需要将原来封装到Map的信息手工转为json(把java对象变为json串),再转为String。

JsonUtils实现把java对象变为json串,放在taotao-manager-common的util包下

import java.util.List;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
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;
    }
    
}
View Code

调用其objectToJson()方法即可实现。

Controller层代码改为:

@Controller
public class PictureController {
    
    @Value("${TAOTAO_IMAGE_SERVER_URL}")
    private String TAOTAO_IMAGE_SERVER_URL;
    
    
    @RequestMapping("/pic/upload")
    @ResponseBody
    public String uploadFile(MultipartFile uploadFile) {
        try {
            //1.创建一个 FastDFS 的客户端
            FastDFSClient fastDFSClient = new FastDFSClient("classpath:resource/fastdfs.conf");
            //取文件扩展名
            String originalFilename = uploadFile.getOriginalFilename();
            String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
            //执行上传处理
            String url = fastDFSClient.uploadFile(uploadFile.getBytes(), extName);
            
            //拼接返回的 url和 ip 地址,拼装成完整的 url
            url = TAOTAO_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);
        }    
    }
}

这里我点击上传失败:

报错信息:

java.io.FileNotFoundException: D:WorkspacesMyEclipse%202015 aotao-manager-web argetclasses esourcefastdfs.conf (系统找不到指定的路径。)

不明白为什么路径会不对,FastDFSClient(String conf)是这样定义的:

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);
    }

这里claspath:识别的路径和fastdfs.conf所在路径不符。然后我改成全路径就对了。。。问题还有待解决。。。

FastDFSClient fastDFSClient = new FastDFSClient("D:/Workspaces/MyEclipse 2015/taotao-manager-web/src/main/resources/resource/fastdfs.conf");

 上传成功:

 

 4.富文本编辑器

 

纯js实现,略。

5.提交表单,完成商品添加功能

 

前端:

itme-add.jsp:提交--->submitForm()

submitForm():

 

1) url:/item/save 

2)要响应一个json数据,包含status属性。这里我们封装一个类TaotaoResult(自定义响应类),放在taotao-manager-common的util包下:

  • private Integer status; // 响应业务状态
  • private String msg; // 响应消息
  • private Object data; // 响应中的数据
package com.taotao.common.util;

import java.io.Serializable;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 淘淘商城自定义响应结构
 */
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;
        }
    }

}
View Code

5.1 数据库

用到tb_item和tb_item_desc两张表:

5.2 DAO层

利用反向工程生成的mapper

5.3 Service层

taotao-manager-interface下 com.taotao.service包下的 ItemService 类:

public interface ItemService {/**
     * 添加商品基本数据和描述数据
     * @param item
     * @param desc
     * @return
     */
    public TaotaoResult saveItem(TbItem item,String desc);
}

taotao-manager-service下 com.taotao.service.impl的 ItemServiceImpl 实现 saveItem() 方法:

@Service
public class ItemServiceImpl implements ItemService {
    
    //注入mapper
    @Autowired
    private TbItemMapper itemMapper;
    @Autowired
    private TbItemDescMapper itemDescMapper;

    @Override
    public TaotaoResult saveItem(TbItem item, String desc) {
        //生成商品的id (利用工具类IDUtils下的genItemId()方法生成)
        long itemId = IDUtils.genItemId();
        
        //1.补全item的其他属性
        item.setId(itemId);
        item.setStatus((byte) 1);//1-正常,2-下架,3-删除
        item.setCreated(new Date());
        item.setUpdated(new Date());
        //2.向商品表(item表)插入数据
        itemMapper.insert(item);
        //3.创建一个商品描述表对应的pojo对象
        TbItemDesc itemDesc = new TbItemDesc();
        //4.补全商品描述中的属性
        itemDesc.setItemId(itemId);
        itemDesc.setItemDesc(desc);
        itemDesc.setCreated(new Date());
        itemDesc.setUpdated(new Date());
        //5.插入商品描述数据
        itemDescMapper.insert(itemDesc);
        //6.返回TaotaoResult
        return TaotaoResult.ok();
    }

}

5.4 Controller层

@Controller
public class ItemController {
    
    //1.引入服务(springmvc.xml中引入)
    //2.注入服务
    @Autowired
    private ItemService itemService;
    /**
     * 商品添加功能
     */
    @RequestMapping(value="/item/save",method=RequestMethod.POST)
    @ResponseBody
    public TaotaoResult saveItem(TbItem item, String desc) {
        //1.引入服务(springmvc.xml中引入)
        //2.注入服务(@Autowired ItemService)
        //3.调用
        return itemService.saveItem(item, desc);
    }
}

原文地址:https://www.cnblogs.com/toria/p/taotao03.html