【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.x文档
2>上传图片请求URL
3>返回值
参考文档说明,返回格式(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中引入)
步骤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();
}