跳至主要內容

【taotao】②商品添加实现

holic-x...大约 13 分钟商城系统taotao

【taotao】②商品添加实现

完成商品添加功能

  • 商品类目选择(商品种类确定)

  • 图片上传

  • 图片服务器搭建

  • kindEditor富文本编辑器的使用

  • 商品添加功能

1.商品类目选择功能

【1】实现说明

需求分析

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

实现步骤

​ 按钮添加点击事件,弹出窗口,加载数据显示tree

​ 将选择类目的组件封装起来,通过TT.iniit()初始化,最终调用initItemCat()方法进行初始化

​ 创建数据库、以及tb_item_cat表,初始化数据

​ 编写Controller、Service、Mapper

【2】参考步骤

🔖Easy UI tree数据结构分析

​ 可以根据parentid查询分类列表

数据结构:

{
	"id": "节点id"
	"text": "节点名称"
	"state": "叶子节点为open,非叶子节点为close,如果为close的节点在点击后会发送请求查询子项目"
	"children":[子节点列表,其结构与节点保持一致]
}

🔖mapper层

​ 使用逆向工程生成的mapper文件

🔖service层

实现说明

  • 功能:根据parentId查询商品分类列表

  • 参数:parentId

  • 返回值:返回tree对应的数据结构(节点列表),可以通过map实现,也可自定义一个pojo表示节点数据List<TreeNode>

1>TreeNode

​ TreeNode用于定义节点数据格式,其核心字段和构造函数定义如下

public class TreeNode {
	private long id;
	private String text;
	private String state;
	public TreeNode(long id, String text, String state) {
		this.id = id;
		this.text = text;
		this.state = state;
	}
}
2>Service实现

ItemCatService接口

public interface ItemCatService {
	public List<TreeNode> getItemCatList(long parentId);
}

ItemCatServiceImpl实现类

@Service
public class ItemCatServiceImpl implements ItemCatService {
	@Autowired
	private TbItemCatMapper itemCatMapper;
	@Override
	public List<TreeNode> getItemCatList(long parentId) {
		//根据parentId查询分类列表
		TbItemCatExample example = new TbItemCatExample();
		//设置查询条件
		Criteria criteria = example.createCriteria();
		criteria.andParentIdEqualTo(parentId);
		//执行查询
		List<TbItemCat> list = itemCatMapper.selectByExample(example);
		//分类列表转换成TreeNode的列表
		List<TreeNode> resultList = new ArrayList<>();
		for (TbItemCat tbItemCat : list) {
			//创建一个TreeNode对象
			TreeNode node = new TreeNode(tbItemCat.getId(), tbItemCat.getName(), 
					tbItemCat.getIsParent()?"closed":"open");
			resultList.add(node);
		}
		return resultList;
	}
}

🔖controller层

实现说明

  • 功能:接收页面传递过来的id,作为parentId查询子节点。

  • 参数:Long id

  • 返回值:要返回json数据要使用@ResponseBody List<TreeNode>

Controller实现
@Controller
@RequestMapping("/item/cat")
public class ItemCatController {
	@Autowired
	private ItemCatService itemCatService;
	@RequestMapping("/list")
	@ResponseBody
	public List<TreeNode> getItemCatList(@RequestParam(value="id", defaultValue="0")Long parentId) {
		List<TreeNode> list = itemCatService.getItemCatList(parentId);
		return list;
	}
}

2.图片上传

【1】图片服务器

🔖图片管理概念

(1)传统项目中的图片管理

​ 传统项目中,可以在web项目中添加一个文件夹,来存放上传的图片。例如在工程的根目录WebRoot下创建一个images文件夹。把图片存放在此文件夹中就可以直接使用在工程中引用。

优点:

​ 引用方便,便于管理

缺点:

​ 如果是分布式环境图片引用会出现问题

​ 图片的下载会给服务器增加额外的压力

(2)分布式环境的图片管理

​ 分布式环境一般都有一个专门的图片服务器存放图片,在项目中可以通过虚拟机搭建一个专门的服务器来进行图片的存取。在服务器上安装一个nginx来提供http服务,安装一个ftp服务器来提供图片上传服务

​ http、ftp服务的安装可参考网络资源或者相应的安装手册,此处不作赘述。安装完成可进行图片服务器的测试

FileZilla连接测试ftp

Java程序测试:ftp依赖commons-net-3.3.jar

public static void main(String[] args) throws Exception {
		FTPClient ftpClient = new FTPClient();
		ftpClient.connect("192.168.25.200");
		ftpClient.login("ftpuser", "ftpuser");
		FileInputStream inputStream = new FileInputStream(new File("D:\\Documents\\Pictures\\pics\\21.jpg"));
		ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
		ftpClient.storeFile("123.jpg", inputStream);
		inputStream.close();
		
		ftpClient.logout();
	}

FastDFS上传测试

​ 在taotao-parent定义依赖版本,需要手动更新版本到本地仓库

​ 在taotao-manager-web下通过Maven中引入相关jar包(需要将jar导入maven仓库,中央仓库可能没有该jar包的存在)

 <dependency>
 		<groupId>org.csource</groupId>
		<artifactId>fastdfs-client-java</artifactId>
		<version>1.27-SNAPSHOT</version>
 </dependency> 

​ 编写文件client.conf:tracker_server=10.0.7.68:22122

​ 在taotao-common下加载工具类(相应地也要修改pom.xml)

​ 编写测试代码测试服务器连接:

	@Test
	public void testUpload() throws Exception {
		// 1.把FastDFS提供的jar包添加到工程中
		// 2.初始化全局配置,加载一个配置文件
//		String url = "e:/workspace/eclipse/Projects/taotao-manager/taotao-manager-web/src/main/resources/conf/client.conf";
		String url = "E:\\workspace\\eclipse\\Projects\\taotao-manager\\taotao-manager-web\\src\\main\\resources\\conf\\client.conf";
		ClientGlobal.init(url);
		// 3.创建一个TrackerClient对象。
		TrackerClient trackerClient = new TrackerClient();
		// 4.创建一个TrackerServer对象。
		TrackerServer trackerServer = trackerClient.getConnection();
		// 5.声明一个StorageServer对象,null。
		StorageServer storageServer = null;
		// 6.获得StorageClient对象。
		StorageClient storageClient = new StorageClient(trackerServer, storageServer);
		// 7.直接调用StorageClient对象方法上传文件即可。
		String source= "D:\\Documents\\Pictures\\pics\\21.jpg";
		String[] strings = storageClient.upload_file(source, "png", null);
		for (String string : strings) {
			System.out.println(string);
		}
	}
SpringMVC实现图片上传

步骤说明

# a.导入common-fileupload的依赖
		<!-- 文件上传组件 -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
		</dependency>
		
# b.在SpringMVC配置文件中添加文件上传解析器
	<!-- 定义文件上传解析器 -->
	<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】文件上传功能实现

​ 在基于上述基本环境搭建和配置下,实现图片上传

🔖Service实现

1>概念介绍
1)获取资源配置文件内容

步骤1:创建资源配置文件

FILI_UPLOAD_PATH=D:/temp/imagestest/webapps/images
IMAGE_BASE_URL=http://localhost:9000/images

步骤2:在Spring(taotao-manage-servlet.xml)容器中加载资源文件

步骤3:在Service中获取资源配置(借助注解获取资源配置信息)

	@Value("${FILI_UPLOAD_PATH}")
	private String FILI_UPLOAD_PATH;
	@Value("${IMAGE_BASE_URL}")
	private String IMAGE_BASE_URL;
2)图片名生成策略

方式1:UUID

UUID.randomUUID();

方式2:时间+随机数概念

	/**
	 * 图片名生成
	 */
	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;
	}
2>service实现

​ 读取配置文件中的数据(设置文件上传所需的基本属性),在taotao-manager-web下配置属性文件resource.properties

​ 利用spring框架提供的读取文件的机制完成文件信息的读取,在applicationContext-dao.xml下加载相关的配置文件

​ 定义PictureService接口和相应的实现类完成图片上传操作(ftp连接相关属性定义与配置文件内容对应一致)

public class PictureServiceImpl implements PictureService{
	@Value("${FTP_ADDRESS}")
	private String FTP_ADDRESS;
	@Value("${FTP_PORT}")
	private Integer FTP_PORT;
	@Value("${FTP_USERNAME}")
	private String FTP_USERNAME;
	@Value("${FTP_PASSWORD}")
	private String FTP_PASSWORD;
	@Value("${FTP_BASE_PATH}")
	private String FTP_BASE_PATH;
	@Value("${IMAGE_BASE_URL}")
	private String IMAGE_BASE_URL;
	@Override
	public Map uploadPicture(MultipartFile uploadFile) {
		Map resultMap = new HashMap<>();
		try {
			//生成一个新的文件名
			//取原始文件名
			String oldName = uploadFile.getOriginalFilename();
			//生成新文件名
			//UUID.randomUUID();
			String newName = IDUtils.genImageName();
			newName = newName + oldName.substring(oldName.lastIndexOf("."));
			//图片上传
			String imagePath = new DateTime().toString("/yyyy/MM/dd");
			boolean result = FtpUtil.uploadFile(FTP_ADDRESS, FTP_PORT, FTP_USERNAME, FTP_PASSWORD, 
					FTP_BASE_PATH, imagePath, newName, uploadFile.getInputStream());
			//返回结果
			if(!result) {
				resultMap.put("error", 1);
				resultMap.put("message", "文件上传失败");
				return resultMap;
			}
			resultMap.put("error", 0);
			resultMap.put("url", IMAGE_BASE_URL + imagePath + "/" + newName);
			return resultMap;
			
		} catch (Exception e) {
			resultMap.put("error", 1);
			resultMap.put("message", "文件上传发生异常");
			return resultMap;
		}
	}
}

🔖Controller实现

​ 功能:接收MultiPartFile对象,调用Service上传图片,返回json数据格式,使用@ResponseBody注解。

​ 需要引入commons-fileupload.jar并在springmvc.xml中配置多部件解析器

​ PictureController定义

@Controller
public class PictureController {
	@Autowired
	private PictureService pictureService;	
	@RequestMapping("/pic/upload")
	@ResponseBody
	public String pictureUpload(MultipartFile uploadFile) {
		Map result = pictureService.uploadPicture(uploadFile);
		//为了保证功能的兼容性,需要把Result转换成json格式的字符串。
		String json = JsonUtils.objectToJson(result);
		return json;
	}
}

🔖前端JS实现

1>JS实现逻辑

上传图片使用kindeditor的上传组件实现,参考KindEditor 4.xopen in new window文档

2>上传图片请求URL

3>返回值

参考文档说明open in new window,返回格式(JSON)

//成功时
{
        "error" : 0,
        "url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
        "error" : 1,
        "message" : "错误信息"
}

返回值数据类型定义

public class PictureResult {

	/**
	 * 上传图片返回值,成功:0	失败:1	
	 */
	private Integer error;
	/**
	 * 回显图片使用的url
	 */
	private String url;
	/**
	 * 错误时的错误消息
	 */
}

文件上传可能存在的问题

​ 文件服务器部署存在问题,考虑调整为存储到本地的服务器

​ 文件上传插件:存在浏览器兼容问题,在google能正常使用,在firefox无法正常使用(可以通过f12查看相关的json数据,考虑这一问题(保证功能的兼容性),在controller层需要将Result转化为相应的json格式的字符串数据)

3.kindeditor(富文本器)的使用

步骤1:导入js:(在item.add.jsp中引入)

image-20190103091727539

步骤2:定义多行文本(设置不可见、给定name)

步骤3:调用TT.createEditor(查看common.js)

​ 创建富文本编辑器,初始化类目选择和图片上传器

​ 提交表单之前,需要先将富文本编辑器中的内容和textarea中的内容进行同步

步骤4:测试效果

​ 取文本编辑器中的内容:将编辑器的内容设置到原来的textarea控件里

editor.sync();

4.新增商品功能实现

【1】功能实现说明

🔖JS逻辑处理

​ 表单提交请求:(请求url、表单数据序列化成key-value形式)

	//提交表单
	function submitForm(){
		//有效性验证
		if(!$('#itemAddForm').form('validate')){
			$.messager.alert('提示','表单还未填写完成!');
			return ;
		}
		//取商品价格,单位为“分”
		$("#itemAddForm [name=price]").val(eval($("#itemAddForm [name=priceView]").val()) * 100);
		//同步文本框中的商品描述
		itemAddEditor.sync();
		//取商品的规格
		/*
		var paramJson = [];
		$("#itemAddForm .params li").each(function(i,e){
			var trs = $(e).find("tr");
			var group = trs.eq(0).text();
			var ps = [];
			for(var i = 1;i<trs.length;i++){
				var tr = trs.eq(i);
				ps.push({
					"k" : $.trim(tr.find("td").eq(0).find("span").text()),
					"v" : $.trim(tr.find("input").val())
				});
			}
			paramJson.push({
				"group" : group,
				"params": ps
			});
		});
		//把json对象转换成字符串
		paramJson = JSON.stringify(paramJson);
		$("#itemAddForm [name=itemParams]").val(paramJson);
		*/
		//ajax的post方式提交表单
		//$("#itemAddForm").serialize()将表单序列号为key-value形式的字符串
		$.post("/item/save",$("#itemAddForm").serialize(), function(data){
			if(data.status == 200){
				$.messager.alert('提示','新增商品成功!');
			}
		});
	}

🔖提交请求格式设定

​ $("#itemAddForm").serialize()将表单序列号为key-value形式的字符串,以post 的形式将表单的内容提交。

​ 请求的url:/item/save

​ 返回的结果:自定义返回结果:状态码、响应的消息、响应的数据

public class TaotaoResult {
    // 定义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;
        }
    }
}

🔖商品ID定义

	/**
	 * 商品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;
	}

【2】业务逻辑处理

🔖ItemService实现

​ 调用mapper的insert方法添加商品信息

	@Override
	public void saveItem(TbItem item, String desc, String itemParams) throws Exception {
		Date date = new Date();
		//获得商品id
		long id = IDUtils.genItemId();
		//添加商品信息
		item.setId(id);
		//商品状态,1-正常,2-下架,3-删除
		item.setStatus((byte) 1);
		item.setCreated(date);
		item.setUpdated(date);
		itemMapper.insert(item);
		//添加商品描述
		//创建TbItemDesc对象
		TbItemDesc itemDesc = new TbItemDesc();
		//获得一个商品id
		itemDesc.setItemId(id);
		itemDesc.setItemDesc(desc);
		itemDesc.setCreated(date);
		itemDesc.setUpdated(date);
		//插入数据
		itemDescMapper.insert(itemDesc);
	}

🔖ItemController实现

	@RequestMapping("/item/save")
	@ResponseBody
	public TaotaoResult saveItem(TbItem item, String desc) throws Exception {
		//添加商品信息
		itemService.saveItem(item, desc, null);
		return TaotaoResult.ok();
	}

5.fastDFS实现文件上传

​ 参考已有的虚拟机服务器,修改相关文件:

​ 修改/etc/fdfs/storage.conf和/etc/fdfs/client.conf文件,修改对应的ip和端口号信息:tracker_server=192.168.187.128:22122

# 进入指定目录
cd /usr/bin/
# 重新启动fastDFS、nginx使配置生效
fdfs_trackerd /etc/fdfs/tracker.conf
fdfs_storaged /etc/fdfs/storage.conf
# 重启nginx
cd /usr/local/nginx
./sbin/nginx

​ 测试访问链接:http://192.168.187.128/group1/M00/00/00/wKi7gFwu_ZyAUs-BAAHUEC1DZhw324.png

使用fastDFS工具类实现图片上传

@Test
	public void testFastDfsClient() throws Exception {
		String url = "E:\\workspace\\eclipse\\Projects\\taotao-manager\\taotao-manager-web\\src\\main\\resources\\conf\\client.conf";
		FastDFSClient client = new FastDFSClient(url);
		String source = "C:\\Users\\noob\\Desktop\\image\\2.jpg";
		String uploadFile = client.uploadFile(source, "jpg");
		System.out.println(uploadFile);
	}

​ 上传的图片在服务器中存储的位置:/home/fastdfs/storage/data/00/00

​ 上传测试:第一次上传测试需要加载flash插件,否则可能出现“添加图片”的按钮不显示,其次添加图片完成之后,图片数据不回显,如下图所示

火狐浏览器兼容性问题解决

​ 返回的数据是一个文本类型,要求content-type 为text/plan

6.商品描述

【1】功能介绍

​ 后台要接收前台页面提交的商品信息,及商品描述。商品信息保存还要保存商品描述。数据库中商品信息和商品描述是分开存储的

【2】功能实现

dao层

​ 把商品描述信息保存到tb_item_desc表中,使用逆向工程生成相关代码

service层

功能说明

​ 接收商品描述调用dao把商品描述插入到表中。

​ 参数:String 商品描述

​ 返回值:TaotaoResult

功能实现

@Override
	public TaotaoResult createItem(TbItem item, String desc) throws Exception {
		//item补全:生成商品ID
		Long itemId = IDUtils.genItemId();
		item.setId(itemId);
		// '商品状态,1-正常,2-下架,3-删除',
		item.setStatus((byte) 1);
		item.setCreated(new Date());
		item.setUpdated(new Date());
		//插入到数据库
		itemMapper.insert(item);
		//添加商品描述信息
		TaotaoResult result = insertItemDesc(itemId, desc);
		if (result.getStatus() != 200) {
			throw new Exception();
		}
		return TaotaoResult.ok();
	}
	/**
	 * 添加商品描述
	 * <p>Title: insertItemDesc</p>
	 * <p>Description: </p>
	 * @param desc
	 */
	private TaotaoResult insertItemDesc(Long itemId, String desc) {
		TbItemDesc itemDesc = new TbItemDesc();
		itemDesc.setItemId(itemId);
		itemDesc.setItemDesc(desc);
		itemDesc.setCreated(new Date());
		itemDesc.setUpdated(new Date());
		itemDescMapper.insert(itemDesc);
		return TaotaoResult.ok();
	}

controller层

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3