后台管理分为三大模块:商品分类管理、用户管理和订单管理。商品分类下包含属性管理、产品管理,而产品管理下又包含图片管理和属性值设置功能。核心就是CRUD。
目录
4.4.3 ProductServiceImpl中实现新增方法
一、分类管理
1.1 前端需求
需要展示的分类数据有:ID、分类对应的图片、分类名称。
操作:编辑和删除。编辑是跳转到下一个页面进行的。
分类管理下包含分类对应属性的管理和分类下所有商品的管理,在后边介绍。
1.2 前端代码
分为三部分:数据表格、分页条、新增表单。
<div class="workingArea">
<h1 class="label label-info">分类管理</h1>
<br>
<br>
<div class="listDataTableDiv">
<table class="table table-striped table-bordered table-hover table-condensed">
<c:if test="${cs.size() != 0}">
<thead>
<tr class="success">
<th>ID</th>
<th>图片</th>
<th>分类名称</th>
<th>属性管理</th>
<th>产品管理</th>
<th>编辑</th>
<th>删除</th>
</tr>
</thead>
<tbody>
<c:forEach items="${cs}" var="c">
<tr>
<td>${c.id}</td>
<td><img height="40px" src="${pageContext.request.contextPath}/image/category/${c.id}.jpg"></td>
<td>${c.name}</td>
<td><a href="admin_property_list?cid=${c.id}"><span class="glyphicon glyphicon-th-list"></span></a></td>
<td><a href="admin_product_list?cid=${c.id}"><span class="glyphicon glyphicon-shopping-cart"></span></a></td>
<td><a href="admin_category_edit?id=${c.id}"><span class="glyphicon glyphicon-edit"></span></a></td>
<td><a deleteLink="true" href="admin_category_delete?id=${c.id}"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</c:forEach>
</tbody>
</c:if>
<c:if test="${cs.size() == 0}">
<thead>
<tr class="success">
<th colspan="7" class="text-center">请添加数据</th>
</tr>
</thead>
</c:if>
</table>
</div>
<div class="pageDiv">
<%@include file="../include/admin/adminPage.jsp" %>
</div>
<div class="panel panel-warning addDiv">
<div class="panel-heading">新增分类</div>
<div class="panel-body">
<form method="post" id="addForm" action="admin_category_add" enctype="multipart/form-data">
<table class="addTable">
<tr>
<td>分类名称</td>
<td><input id="name" name="name" type="text" class="form-control"></td>
</tr>
<tr>
<td>分类圖片</td>
<td>
<input id="categoryPic" accept="image/*" type="file" name="image" />
</td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
1.2.1 数据表格
ID、图片、分类名称等基本信息是通过<c:forEach>语句渲染后台传来的数据对象,也就是这里的cs,它里面包含了所有的分类数据。
删除操作是通过点击超链接向后台发起请求,在请求路径中添加对应的分类id。
编辑操作是跳转到一个新的页面,代码如下:
<div class="workingArea">
<ol class="breadcrumb">
<li><a href="admin_category_list">所有分类</a></li>
<li class="active">编辑分类</li>
</ol>
<div class="panel panel-warning editDiv">
<div class="panel-heading">编辑分类</div>
<div class="panel-body">
<form method="post" id="editForm" action="admin_category_update" enctype="multipart/form-data">
<table class="editTable">
<tr>
<td>分类名称</td>
<td><input id="name" name="name" value="${c.name}" type="text" class="form-control"></td>
</tr>
<tr>
<td>分类圖片</td>
<td>
<input id="categoryPic" accept="image/*" type="file" name="image" />
</td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<input type="hidden" name="id" value="${c.id}">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
在发起编辑请求时,也会向后台传递对应分类的id,查询得到对应分类的信息后,在渲染编辑视图时保存到对象c中,最后在表单中相应位置显示要修改的分类信息。当修改完成后再发起更新请求。
1.2.2 分页条
为了提高复用性,将分页条单独抽取到一个jsp页面中,在有需要的时候直接引入即可。使用时,只需在页面中添加一个分类对象page即可,具体请参考
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<script>
$(function(){
$("ul.pagination li.disabled a").click(function(){
return false;
});
});
</script>
<nav>
<ul class="pagination">
<li <c:if test="${!page.hasPreviouse}">class="disabled"</c:if>>
<a href="?start=0${page.param}" aria-label="Previous" >
<span aria-hidden="true">«</span>
</a>
</li>
<li <c:if test="${!page.hasPreviouse}">class="disabled"</c:if>>
<a href="?start=${page.start-page.count}${page.param}" aria-label="Previous" >
<span aria-hidden="true">‹</span>
</a>
</li>
<c:forEach begin="0" end="${page.totalPage-1}" varStatus="status">
<li <c:if test="${status.index*page.count==page.start}">class="disabled"</c:if>>
<a href="?start=${status.index*page.count}${page.param}"
<c:if test="${status.index*page.count==page.start}">class="current"</c:if>>${status.count}</a>
</li>
</c:forEach>
<li <c:if test="${!page.hasNext}">class="disabled"</c:if>>
<a href="?start=${page.start+page.count}${page.param}" aria-label="Next">
<span aria-hidden="true">›</span>
</a>
</li>
<li <c:if test="${!page.hasNext}">class="disabled"</c:if>>
<a href="?start=${page.last}${page.param}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
1.2.3 新增表单
填好数据直接提交即可,注意input中的name值一定与对应的pojo对象一致。
1.3 后端接口
有关Page对象请参考《工具类》。
后端中的pojo、mapper和mapper对应的xml文件都是通过逆向工具自动生成的。
1.3.1 查询
Controller
@RequestMapping("admin_category_list")
public String list(Model model, Page page){
//页面中的start会自动注入到page对象当中
PageHelper.offsetPage(page.getStart(),page.getCount());
System.out.println("开始:"+page.getStart()+","+"数量:"+page.getCount());
List<Category> categoryList = categoryService.list();
for (Category c:categoryList) {
System.out.println(c);
}
int total =(int) new PageInfo<>(categoryList).getTotal();
page.setTotal(total);
model.addAttribute("cs",categoryList);
model.addAttribute("page",page);
return "admin/listCategory";
}
在model中添加要进行渲染的cs和page。
Service
接口
/**
* 分页查询
* @return
*/
List<Category> list();
实现类
@Override
public List<Category> list() {
CategoryExample example = new CategoryExample();
example.setOrderByClause("id desc");
return categoryMapper.selectByExample(example);
}
1.3.2 增加
在增加中涉及到了图片的上传,相关工具请参考《工具类》。
Controller
@RequestMapping("admin_category_add")
public String add(Category category, HttpSession httpSession, UploadedImageFile uploadedImageFile) throws IOException {
categoryService.add(category);
File imageFolder = new File(httpSession.getServletContext().getRealPath("image/category"));
System.out.println(httpSession.getServletContext().getRealPath("image/category"));
File file = new File(imageFolder, category.getId()+".jpg");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
uploadedImageFile.getImage().transferTo(file);
BufferedImage image = ImageUtil.change2jpg(file);
ImageIO.write(image,"jpg",file);
return "redirect:/admin_category_list";
}
在这里强调一下,本项目对于图片的保存方案:
对于那些需要图片属性的对象,在它的pojo对象中不会直接创建image属性字段的,而是在上传图片时以该对象的唯一id来重新命名图片,将图片保存在独立的文件夹中,前端页面渲染时直接从文件夹中根据id显示即可。
Service
增加功能直接调用mapper中提供的insert方法即可。
1.3.3 删除
此时删除还有一些问题,因为与分类表中的主键id是其他表的外键,所以删除时会有约束,需要级联删除。
Controller
@RequestMapping("admin_category_delete")
public String delete(int id,HttpSession session){
categoryService.delete(id);
File imageFolder= new File(session.getServletContext().getRealPath("image/category"));
File file = new File(imageFolder,id+".jpg");
file.delete();
return "redirect:/admin_category_list";
}
Service
直接根据主键删除即可。
1.3.4 编辑
主要功能就是数据回显。
Controller
@RequestMapping("admin_category_edit")
public String edit(int id,Model model){
Category category= categoryService.get(id);
model.addAttribute("c", category);
return "admin/editCategory";
}
1.3.5 修改
分类信息修改以及图片修改
Controller
@RequestMapping("admin_category_update")
public String update(Category c, HttpSession session, UploadedImageFile uploadedImageFile) throws IOException {
categoryService.update(c);
MultipartFile image = uploadedImageFile.getImage();
if(null!=image &&!image.isEmpty()){
File imageFolder= new File(session.getServletContext().getRealPath("image/category"));
File file = new File(imageFolder,c.getId()+".jpg");
image.transferTo(file);
BufferedImage img = ImageUtil.change2jpg(file);
ImageIO.write(img, "jpg", file);
}
return "redirect:/admin_category_list";
}
二、属性管理
2.1 前端需求
页面与分类页面大体相似,只是多了一个导航条,俗称"面包屑"。
点击编辑时跳转到编辑页面
2.2 前端代码
<div class="workingArea">
<ol class="breadcrumb">
<li><a href="admin_category_list">所有分类</a></li>
<li><a href="admin_property_list?cid=${category.id}">${category.name}</a></li>
<li class="active">属性管理</li>
</ol>
<div class="listDataTableDiv">
<table class="table table-striped table-bordered table-hover table-condensed">
<c:if test="${propertyList.size() != 0}">
<thead>
<tr class="success">
<th>ID</th>
<th>属性名称</th>
<th>编辑</th>
<th>删除</th>
</tr>
</thead>
<tbody>
<c:forEach items="${propertyList}" var="p">
<tr>
<td>${p.id}</td>
<td>${p.name}</td>
<td><a href="admin_property_edit?id=${p.id}"><span class="glyphicon glyphicon-edit"></span></a></td>
<td><a deleteLink="true" href="admin_property_delete?id=${p.id}"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</c:forEach>
</tbody>
</c:if>
<c:if test="${propertyList.size() == 0}">
<thead>
<tr class="success">
<th colspan="4" class="text-center">请添加数据</th>
</tr>
</thead>
</c:if>
</table>
</div>
<div class="pageDiv">
<%@include file="../include/admin/adminPage.jsp"%>
</div>
<div class="panel panel-warning addDiv">
<div class="panel-heading">新增属性</div>
<div class="panel-body">
<form method="post" id="addForm" action="admin_property_add">
<table class="addTable">
<tr>
<td>属性名称</td>
<td><input id="name" name="name" type="text" class="form-control"></td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<input type="hidden" name="cid" value="${category.id}">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
代码分为四部分:面包屑、数据表格、分页条、新增表单。页面中需要渲染的对象有:
分类对象(category):在面包屑中使用,用来显示层次关系,属性管理是针对某一分类的属性进行管理,所以在面包屑中显示对应分类的名称。
属性对象(propertyList):它是一个列表,因为属性有多种
分页对象(page):分页。
2.3 后端接口
根据实际业务需求,需要对Pojo中Property实体类进行修改,添加一个Category字段,以及get和set方法,方便以后使用。
2.3.1 查询
@RequestMapping("admin_property_list")
public String list(int cid, Model model, Page page){
Category category = categoryService.get(cid);
PageHelper.offsetPage(page.getStart(),page.getCount());
List<Property> propertyList = propertyService.list(cid);
int total =(int)new PageInfo<>(propertyList).getTotal();
page.setTotal(total);
page.setParam("&cid="+category.getId());
model.addAttribute("propertyList",propertyList);
model.addAttribute("category",category);
model.addAttribute("page",page);
return "admin/listProperty";
}
查询地址admin_property_list映射的是PropertyController的list()方法
1. 获取分类 cid,和分页对象page
2. 通过PageHelper设置分页参数
3. 基于cid,获取当前分类下的属性集合
4. 通过PageInfo获取属性总数
5. 把总数设置给分页page对象
6. 拼接字符串"&cid="+c.getId(),设置给page对象的Param值。 因为属性分页都是基于当前分类下的分页,所以分页的时候需要传递这个cid
7. 把属性集合设置到 request的 "propertyList" 属性上
8. 把分类对象设置到 request的 "category" 属性上。(面包屑中使用)
9. 把分页对象设置到 request的 "page" 对象上
10. 服务端跳转到admin/listProperty.jsp页面
11. 在listProperty.jsp页面上使用c:forEach 遍历ps集合,并显示
2.3.2 增加
@RequestMapping("admin_property_add")
public String add(Property property){
propertyService.add(property);
return "redirect:admin_property_list?cid="+property.getCid();
}
2.3.3 编辑
1. 在PropertyController的edit方法中,根据id获取Property对象
2. 根据properoty对象的cid属性获取Category对象,并把其设置在Property对象的category属性上,面包屑上使用。
3. 把Property对象放在request的 "property" 属性中
4. 服务端跳转到admin/editProperty.jsp
5. 在editProperty.jsp中显示属性名称
6. 在editProperty.jsp中隐式提供id和cid( cid 通过 p.category.id 获取),这样才能确定是对哪一个分类下的属性进行的修改。
property表结构:
其中cid就是对应的分类id。
@RequestMapping("admin_property_edit")
public String edit(Model model,Integer id){
Property property = propertyService.get(id);
Category category = categoryService.get(property.getCid());
property.setCategory(category);
model.addAttribute("property",property);
return "admin/editProperty";
}
2.3.4 修改
@RequestMapping("admin_property_update")
public String update(Property property){
propertyService.update(property);
return "redirect:admin_property_list?cid="+property.getCid();
}
2.3.5 删除
@RequestMapping("admin_property_delete")
public String delete(Integer id){
Property property = propertyService.get(id);
propertyService.delete(id);
return "redirect:admin_property_list?cid="+property.getCid();
}
2.3.6 分页
这里的分页比起分类管理中的分页多了一个参数cid,因为属性分页查询时要确定是哪一个分类下的属性,所以需要cid。
1. 在PropertyController.list() 方法中,把&cid= 参数设置到在page对象的param属性上
page.setParam("&cid="+c.getId());
2. 在adminPage.jsp页面中通过${page.param}取出这个参数
三、产品管理
3.1 前端需求
编辑时需要跳转页面
3.2 前端代码
<div class="workingArea">
<ol class="breadcrumb">
<li><a href="admin_category_list">所有分类</a></li>
<li><a href="admin_product_list?cid=${category.id}">${category.name}</a></li>
<li class="active">产品管理</li>
</ol>
<div class="listDataTableDiv">
<table class="table table-striped table-bordered table-hover table-condensed">
<c:if test="${products.size() != 0}">
<thead>
<tr class="success">
<th>ID</th>
<th>图片</th>
<th>产品名称</th>
<th>产品小标题</th>
<th width="53px">原价格</th>
<th width="80px">优惠价格</th>
<th width="80px">库存数量</th>
<th width="80px">图片管理</th>
<th width="80px">设置属性</th>
<th width="42px">编辑</th>
<th width="42px">删除</th>
</tr>
</thead>
<tbody>
<c:forEach items="${products}" var="p">
<tr>
<td>${p.id}</td>
<td>
<c:if test="${!empty p.productImage}">
<img height="60px" width="100px" src="${pageContext.request.contextPath}/image/productSingle/${p.productImage.id}.jpg">
</c:if>
</td>
<td>${p.name}</td>
<td>${p.subTitle}</td>
<td>${p.originalPrice}</td>
<td>${p.promotePrice}</td>
<td>${p.stock}</td>
<td><a href="admin_productImage_list?pid=${p.id}"><span class="glyphicon glyphicon-picture"></span></a></td>
<td><a href="admin_propertyValue_edit?pid=${p.id}"><span class="glyphicon glyphicon-th-list"></span></a></td>
<td><a href="admin_product_edit?id=${p.id}"><span class="glyphicon glyphicon-edit"></span></a></td>
<td><a deleteLink="true" href="admin_product_delete?id=${p.id}"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</c:forEach>
</tbody>
</c:if>
<c:if test="${products.size() == 0}">
<thead>
<tr class="success">
<th colspan="11" class="text-center">请添加数据</th>
</tr>
</thead>
</c:if>
</table>
</div>
<div class="pageDiv">
<%@include file="../include/admin/adminPage.jsp"%>
</div>
<div class="panel panel-warning addDiv">
<div class="panel-heading">新增产品</div>
<div class="panel-body">
<form method="post" id="addForm" action="admin_product_add">
<table class="addTable">
<tr>
<td>产品名称</td>
<td><input id="name" name="name" type="text" class="form-control"></td>
</tr>
<tr>
<td>产品小标题</td>
<td><input id="subTitle" name="subTitle" type="text" class="form-control"></td>
</tr>
<tr>
<td>原价格</td>
<td><input id="originalPrice" value="99.98" name="originalPrice" type="text" class="form-control"></td>
</tr>
<tr>
<td>优惠价格</td>
<td><input id="promotePrice" value="19.98" name="promotePrice" type="text" class="form-control"></td>
</tr>
<tr>
<td>库存</td>
<td><input id="stock" value="99" name="stock" type="text" class="form-control"></td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<input type="hidden" name="cid" value="${category.id}">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
前端和前面的模块大同小异。
3.3 后端接口
在Product中添加category属性,以后的操作中会用到。
product表:
表中有cid。
3.3.1 查询
查询地址admin_product_list映射的是ProductController的list()方法
1. 获取分类 cid,和分页对象page
2. 通过PageHelper设置分页参数
3. 基于cid,获取当前分类下的产品集合
4. 通过PageInfo获取产品总数
5. 把总数设置给分页page对象
6. 拼接字符串"&cid="+c.getId(),设置给page对象的Param值。 因为产品分页都是基于当前分类下的分页,所以分页的时候需要传递这个cid
7. 把产品集合设置到 request的 "products" 产品上
8. 把分类对象设置到 request的 "category" 产品上,面包屑上使用,新增产品时需要。
9. 把分页对象设置到 request的 "page" 对象上
10. 服务端跳转到admin/listProduct.jsp页面
11. 在listProduct.jsp页面上使用c:forEach 遍历ps集合,并显示
@RequestMapping("admin_product_list")
public String list(int cid, Model model, Page page){
Category category = categoryService.get(cid);
PageHelper.offsetPage(page.getStart(),page.getCount());
List<Product> products = productService.list(cid);
int total = (int) new PageInfo<>(products).getTotal();
page.setTotal(total);
page.setParam("&cid="+category.getId());
model.addAttribute("category",category);
model.addAttribute("products",products);
model.addAttribute("page",page);
return "admin/listProduct";
}
3.3.2 增加
1. 在listProduct.jsp提交数据的时候,除了提交产品名称,小标题,原价格,优惠价格,库存外还会提交cid
2. 在ProductController中获取Product对象,并插入到数据库
3. 客户端跳转到admin_product_list,并带上参数cid
@RequestMapping("admin_product_add")
public String add(Product product){
product.setCreateDate(new Date());
productService.add(product);
return "redirect:admin_product_list?cid="+product.getCid();
}
3.3.3 编辑
1. 在ProductController的edit方法中,根据id获取product对象
2. 根据product对象的cid产品获取Category对象,并把其设置在product对象的category产品上
3. 把product对象放在request的 "product" 产品中
3. 服务端跳转到admin/editProduct.jsp
4. 在editProduct.jsp中显示产品名称
5. 在editProduct.jsp中隐式提供id和cid( cid 通过 p.category.id 获取)
@RequestMapping("admin_product_edit")
public String edit(Model model, int id){
Product product = productService.get(id);
Category category = categoryService.get(product.getCid());
product.setCategory(category);
model.addAttribute("product",product);
return "admin/editProduct";
}
3.3.4 修改
@RequestMapping("admin_product_update")
public String update(Product product){
productService.update(product);
return "redirect:admin_product_list?cid="+product.getCid();
}
3.3.5 删除
这里面也存在外键约束,所以要级联删除。
@RequestMapping("admin_product_delete")
public String delete(Integer id){
Product product = productService.get(id);
productService.delete(id);
return "redirect:admin_product_list?cid="+product.getCid();
}
3.4 category属性的用途
3.4.1 前端页面展示需要
略
3.4.2 商品编辑页面需要
编辑页面中面包屑要显示分类信息
修改完产品信息后,提交时要提交cid。
3.5 存在的问题
此时产品管理页面还无法显示具体的图片信息,此功能放在下一节介绍。
四、产品图片管理
4.1 前端需求
前端功能比较简单:添加和删除
4.2 前端代码
<div class="workingArea">
<ol class="breadcrumb">
<li><a href="admin_category_list">所有分类</a></li>
<li><a href="admin_product_list?cid=${product.category.id}">${product.category.name}</a></li>
<li class="active">${product.name}</li>
<li class="active">产品图片管理</li>
</ol>
<table class="addPictureTable" align="center">
<tr>
<td class="addPictureTableTD">
<div>
<div class="panel panel-warning addPictureDiv">
<div class="panel-heading">
新增产品<b class="text-primary">单个</b>图片
</div>
<div class="panel-body">
<form method="post" class="addFormSingle" action="admin_productImage_add" enctype="multipart/form-data">
<table class="addTable">
<tr>
<td>请选择本地图片尺寸400X400为佳</td>
</tr>
<tr>
<td>
<input id="filepathSingle" type="file" name="image" />
</td>
</tr>
<tr class="submitTR">
<td align="center">
<input type="hidden" name="type" value="type_single" />
<input type="hidden" name="pid" value="${product.id}" />
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
<table class="table table-striped table-bordered table-hover table-condensed">
<c:if test="${imageSingle.size() != 0}">
<thead>
<tr class="success">
<th>ID</th>
<th>产品单个图片缩略图</th>
<th>删除</th>
</tr>
</thead>
<tbody>
<c:forEach items="${imageSingle}" var="pi">
<tr>
<td>${pi.id}</td>
<td>
<a title="点击查看原图" href="${pageContext.request.contextPath}/image/productSingle/${pi.id}.jpg"><img height="50px" src="${pageContext.request.contextPath}/image/productSingle/${pi.id}.jpg"></a>
</td>
<td>
<a deleteLink="true" href="admin_productImage_delete?id=${pi.id}"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</c:forEach>
</tbody>
</c:if>
<c:if test="${imageSingle.size() != 0}">
<thead>
<tr class="success">
<th colspan="3" class="text-center">请添加图片</th>
</tr>
</thead>
</c:if>
</table>
</div>
</td>
<td class="addPictureTableTD">
<div>
<div class="panel panel-warning addPictureDiv">
<div class="panel-heading">
新增产品<b class="text-primary">详情</b>图片
</div>
<div class="panel-body">
<form method="post" class="addFormDetail" action="admin_productImage_add" enctype="multipart/form-data">
<table class="addTable">
<tr>
<td>请选择本地图片宽度790为佳</td>
</tr>
<tr>
<td>
<input id="filepathDetail" type="file" name="image" />
</td>
</tr>
<tr class="submitTR">
<td align="center">
<input type="hidden" name="type" value="type_detail" />
<input type="hidden" name="pid" value="${product.id}" />
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
<table class="table table-striped table-bordered table-hover table-condensed">
<c:if test="${imageDetail.size() != 0}">
<thead>
<tr class="success">
<th>ID</th>
<th>产品详情图片缩略图</th>
<th>删除</th>
</tr>
</thead>
<tbody>
<c:forEach items="${imageDetail}" var="pi">
<tr>
<td>${pi.id}</td>
<td>
<a title="点击查看原图" href="${pageContext.request.contextPath}/image/productDetail/${pi.id}.jpg"><img height="50px" src="${pageContext.request.contextPath}/image/productDetail/${pi.id}.jpg"></a>
</td>
<td><a deleteLink="true" href="admin_productImage_delete?id=${pi.id}"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</c:forEach>
</tbody>
</c:if>
<c:if test="${imageDetail.size() == 0}">
<thead>
<tr class="success">
<th colspan="3" class="text-center">请添加图片</th>
</tr>
</thead>
</c:if>
</table>
</div>
</div>
</td>
</tr>
</table>
</div>
4.3 后端接口
4.3.1 查询
通过产品页面的图片管理访问ProductImageController的list()方法
1. 获取参数pid
2. 根据pid获取Product对象
3. 根据pid对象获取单个图片的集合ImageSingle
4. 根据pid对象获取详情图片的集合ImageDetail
5. 把product 对象,ImageSingle ,ImageDetail放在model上
6. 服务端跳转到admin/listProductImage.jsp页面
7. 在listProductImage.jsp,使用c:forEach 遍历pisSingle
8. 在listProductImage.jsp,使用c:forEach 遍历pisDetail
@RequestMapping("admin_productImage_list")
public String list(Integer pid, Model model){
Product product = productService.get(pid);
List<ProductImage> imageSingle = productImageService.list(pid,ProductImageService.TYPE_SINGLE);
List<ProductImage> imageDetail = productImageService.list(pid,ProductImageService.TYPE_DETAIL);
model.addAttribute("product",product);
model.addAttribute("imageSingle",imageSingle);
model.addAttribute("imageDetail",imageDetail);
return "admin/listProductImage";
}
4.3.2 增加
增加产品图片分单个和详情两种,其区别在于增加所提交的type类型不一样。将处理图片单独抽取为一个函数ImageAddProcess。
首先, 在listProductImage.jsp准备一个form,提交到admin_productImage_add
<form method="post" class="addFormSingle" action="admin_productImage_add" enctype="multipart/form-data">
接着在ProductImageController的add()方法中进行处理
1. 通过pi对象接受type和pid的注入
2. 借助productImageService,向数据库中插入数据。
@RequestMapping("admin_productImage_add")
public String add(ProductImage productImage, HttpSession session, UploadedImageFile uploadedImageFile) throws IOException {
productImageService.add(productImage);
ImageAddProcess(productImage,session,uploadedImageFile);
return "redirect:admin_productImage_list?pid="+productImage.getPid();
}
ImageAddProcess函数中
3. 根据session().getServletContext().getRealPath( "img/productSingle"),定位到存放单个产品图片的目录除了productSingle,还有productSingle/middle和productSingle/small。 因为每上传一张图片,都会有对应的正常,中等和小的三种大小图片,并且放在3个不同的目录下
4. 文件命名以保存到数据库的产品图片对象的id+".jpg"的格式命名
5. 通过uploadedImageFile保存文件
6. 借助ImageUtil.change2jpg()方法把格式真正转化为jpg,而不仅仅是后缀名为.jpg
7. 再借助ImageUtil.resizeImage把正常大小的图片,改变大小之后,分别复制到productSingle_middle和productSingle_small目录下。
8. 处理完毕之后,客户端条跳转到admin_productImage_list?pid=,并带上pid。
详情图片做的是一样的事情,区别在于复制到目录productDetail下,并且不需要改变大小
public void ImageAddProcess(ProductImage productImage,HttpSession session,UploadedImageFile uploadedImageFile) throws IOException{
String fileName = productImage.getId() + ".jpg";
String imageFolder;
String imageFolder_small = null;
String imageFolder_middle = null;
//如果上传的是产品单个图片的缩略图,将图片转换成两种不同规格大小的图片
if (ProductImageService.TYPE_SINGLE.equals(productImage.getType())){
imageFolder = session.getServletContext().getRealPath("image/productSingle");
imageFolder_small = session.getServletContext().getRealPath("image/productSingle/small");
imageFolder_middle = session.getServletContext().getRealPath("image/productSingle/middle");
}else {
//如果上传的是产品详情图片缩略图
imageFolder = session.getServletContext().getRealPath("image/productDetail");
}
//创建文件夹
File file = new File(imageFolder,fileName);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
uploadedImageFile.getImage().transferTo(file);
BufferedImage image = ImageUtil.change2jpg(file);
ImageIO.write(image,"jpg",file);
if (ProductImageService.TYPE_SINGLE.equals(productImage.getType())){
File file_small = new File(imageFolder_small,fileName);
File file_middle = new File(imageFolder_middle,fileName);
ImageUtil.resizeImage(file,56,56,file_small);
ImageUtil.resizeImage(file,217,190,file_middle);
}
}
4.3.3 删除
点击删除超链,进入ProductImageController的delete方法
1. 获取id
2. 根据id获取ProductImage 对象pi
3. 借助productImageService,删除数据
具体删除图片操作在函数ImageDeleteProcess中
@RequestMapping("admin_productImage_delete")
public String delete(Integer id,HttpSession session){
ProductImage productImage = productImageService.get(id);
ImageDeleteProcess(productImage,session);
productImageService.delete(id);
return "redirect:admin_productImage_list?pid="+productImage.getPid();
}
4. 如果是单个图片,那么删除3张正常,中等,小号图片
5. 如果是详情图片,那么删除一张图片
6. 客户端跳转到admin_productImage_list地址
public void ImageDeleteProcess(ProductImage productImage,HttpSession session){
String fileName = productImage.getId() + ".jpg";
String imageFolder;
String imageFolder_small = null;
String imageFolder_middle = null;
if (ProductImageService.TYPE_SINGLE.equals(productImage.getType())){
imageFolder = session.getServletContext().getRealPath("image/productSingle");
imageFolder_small = session.getServletContext().getRealPath("image/productSingle/small");
imageFolder_middle = session.getServletContext().getRealPath("image/productSingle/middle");
File imageFile = new File(imageFolder,fileName);
File file_small = new File(imageFolder_small,fileName);
File file_middle = new File(imageFolder_middle,fileName);
imageFile.delete();
file_small.delete();
file_middle.delete();
}else {
imageFolder = session.getServletContext().getRealPath("image/productDetail");
File imageFile = new File(imageFolder,fileName);
imageFile.delete();
}
}
4.4 产品管理重构
回到产品管理页面,在产品列表页面,是没有图片的。 因为当时还没有产品图片管理功能,现在支持了,所以需要对Product做一些调整。
4.4.1 新增属性
4.4.2 ProductService中新增方法
4.4.3 ProductServiceImpl中实现新增方法
增加方法 setFirstProductImage(Product p):
根据pid和图片类型查询出所有的单个图片,然后把第一个取出来放在firstProductImage上。
增加方法 setFirstProductImage(List<Product> ps)
给多个产品设置图片
在get方法中调用setFirstProductImage(Product p) 为单个产品设置图片
在list方法中调用setFirstProductImage(List<Product> ps) 为多个产品设置图片
package com.li.tmall.service.impl;
import com.li.tmall.mapper.ProductMapper;
import com.li.tmall.pojo.Category;
import com.li.tmall.pojo.Product;
import com.li.tmall.pojo.ProductExample;
import com.li.tmall.pojo.ProductImage;
import com.li.tmall.service.CategoryService;
import com.li.tmall.service.ProductImageService;
import com.li.tmall.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: 98050
* Time: 2018-09-18 20:31
* Feature:CRUD
*/
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private CategoryService categoryService;
@Autowired
private ProductImageService productImageService;
@Override
public void add(Product product) {
productMapper.insert(product);
}
@Override
public void delete(Integer id) {
productMapper.deleteByPrimaryKey(id);
}
@Override
public void update(Product product) {
productMapper.updateByPrimaryKeySelective(product);
}
@Override
public Product get(Integer id) {
Product product = productMapper.selectByPrimaryKey(id);
set(product);
return product;
}
@Override
public List<Product> list(Integer cid) {
ProductExample example = new ProductExample();
example.createCriteria().andCidEqualTo(cid);
example.setOrderByClause("id desc");
List<Product> products = productMapper.selectByExample(example);
setCategory(products);
setFirstProductImage(products);
return products;
}
@Override
public void setFirstProductImage(Product product) {
List<ProductImage> list = productImageService.list(product.getId(),ProductImageService.TYPE_SINGLE);
if (!list.isEmpty()){
ProductImage productImage = list.get(0);
product.setProductImage(productImage);
}
}
public void setFirstProductImage(List<Product> products){
for (Product product : products){
setFirstProductImage(product);
}
}
public void setCategory(List<Product> products){
for (Product p:products){
set(p);
}
}
public void set(Product product){
int cid = product.getCid();
Category category = categoryService.get(cid);
product.setCategory(category);
}
}