商品模块
商品添加,新的知识点批量添加图片,商品详情和商品是多对一的。

product实体类
public class Product {
private Long productId;
private String productName;
private String productDesc;
//简略图
private String imgAddr;
//原价
private String normalPrice;
//折扣价
private String promotionPrice;
private Integer priority;
private Date createTime;
private Date lastEditTime;
//-1 不可用 0 下架 1在前端页面展示
private Integer enableStatus;
//一对多
private List<ProductImg> productImgList;
private ProductCategory productCategory;
private Shop shop;
productimg实体类
public class ProductImg {
private Long productImgId;
private String imgAddr;
private String imgDesc;
private Integer priority;
private Date createTime;
private Long productId;
dao

其中图片是批量添加的,因为一个商品可以上传很多张图片

dao实现
<mapper namespace="com.imooc.o2o.dao.ProductImgDao">
<insert id="batchInsertProductImg" parameterType="java.util.List">
INSERT INTO
tb_product_img(img_addr,img_desc,priority,
create_time,product_id)
VALUES
<foreach collection="list" item="productImg" index="index"
separator=",">
(
#{productImg.imgAddr},
#{productImg.imgDesc},
#{productImg.priority},
#{productImg.createTime},
#{productImg.productId}
)
</foreach>
</insert>
</mapper>
<mapper namespace="com.imooc.o2o.dao.ProductDao">
<insert id="insertProduct" parameterType="com.imooc.o2o.entity.Product"
useGeneratedKeys="true" keyProperty="productId" keyColumn="product_id">
INSERT INTO
tb_product(product_name,product_desc,img_addr,
normal_price,promotion_price,priority,create_time,
last_edit_time,enable_status,product_category_id,
shop_id)
VALUES
(#{productName},#{productDesc},#{imgAddr},
#{normalPrice},#{promotionPrice},#{priority},#{createTime},
#{lastEditTime},#{enableStatus},#{productCategory.productCategoryId},
#{shop.shopId})
</insert>
</mapper>
jt测试
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProductImgDaoTest extends BaseTest {
@Autowired
private ProductImgDao productImgDao;
@Test
public void testAbatchInsertProductImg() throws Exception {
// productId为1的商品里添加两个详情图片记录
ProductImg pi1 = new ProductImg();
pi1.setImgAddr("图片1");
pi1.setImgDesc("测试图片1");
pi1.setPriority(1);
pi1.setCreateTime(new Date());
pi1.setProductId(1L);
ProductImg pi2 = new ProductImg();
pi2.setImgAddr("图片2");
pi2.setImgDesc("测试图片2");
pi2.setPriority(1);
pi2.setCreateTime(new Date());
pi2.setProductId(1L);
List<ProductImg> pil = new ArrayList<ProductImg>();
pil.add(pi1);
pil.add(pi2);
int effectedNum = productImgDao.batchInsertProductImg(pil);
assertEquals(2, effectedNum);
}
}
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProductDaoTest extends BaseTest {
@Autowired
private ProductImgDao productImgDao;
@Autowired
private ProductDao productDao;
@Test
public void testAInsertProduct() throws Exception {
Shop shop1 = new Shop();
shop1.setShopId(1L);
ProductCategory pc1 = new ProductCategory();
pc1.setProductCategoryId(1L);
// 初始化三个商品实例并添加进shopId为1的店铺里,
// 同时商品类别id也为1
Product p1 = new Product();
p1.setProductName("测试1");
p1.setProductDesc("测试desc1");
p1.setImgAddr("test1");
p1.setPriority(1);
p1.setEnableStatus(1);
p1.setCreateTime(new Date());
p1.setLastEditTime(new Date());
p1.setShop(shop1);
p1.setProductCategory(pc1);
Product p2 = new Product();
p2.setProductName("测试2");
p2.setProductDesc("测试desc2");
p2.setImgAddr("test2");
p2.setPriority(2);
p2.setEnableStatus(0);
p2.setCreateTime(new Date());
p2.setLastEditTime(new Date());
p2.setShop(shop1);
p2.setProductCategory(pc1);
Product p3 = new Product();
p3.setProductName("测试3");
p3.setProductDesc("测试desc3");
p3.setImgAddr("test3");
p3.setPriority(3);
p3.setEnableStatus(0);
p3.setCreateTime(new Date());
p3.setLastEditTime(new Date());
p3.setShop(shop1);
p3.setProductCategory(pc1);
//判断是否添加成功
int effectedNum = productDao.insertProduct(p1);
assertEquals(1, effectedNum);
effectedNum = productDao.insertProduct(p2);
assertEquals(1, effectedNum);
effectedNum = productDao.insertProduct(p3);
assertEquals(1, effectedNum);
}
}
service
跟前面一样定义service的返回类型execution,还有enum,还有exception
之后再定义service接口,该接口接收5个参数,分别是商品实体类,缩略图流,缩略图名字,详情图片流集合,和对应的详情图片流名字集合。

excution
public class ProductCategoryExecution {
// 结果状态
private int state;
// 结果标识
private String stateInfo;
private List<ProductCategory> productCategoryList;
public ProductCategoryExecution() {
}
//操作成功时使用的构造函数
public ProductCategoryExecution(ProductCategoryStateEnum stateEnum, List<ProductCategory> productCategoryList) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.productCategoryList = productCategoryList;
}
//操作失败时使用的构造函数
public ProductCategoryExecution(ProductCategoryStateEnum stateEnum) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public List<ProductCategory> getProductCategoryList() {
return productCategoryList;
}
public void setProductCategoryList(List<ProductCategory> productCategoryList) {
this.productCategoryList = productCategoryList;
}
}
exception
/*
* 必须继承RuntimeException,如果继承的是Exception,当已经进行了DML操作但是事务还未关闭时,此时如果抛出异常,则DML不会回滚.
* 当然如果硬要继承Exception并且还想回滚事务可以通过在@Transactional注解指定rollbackFor=Exception.class来解决.
*/
public class ProductExecutionException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -3936557078550899764L;
public ProductExecutionException(String msg) {
super(msg);
}
}
dao接口的其中thumbnail是属于缩略图的,thumbnailName和productImageList是属于详情图的,五个参数可以发现有两对是差不多的,我们可以提取出来封装成一个类,就是把流和名字组成一个dto
如下
package com.imooc.o2o.dto;
import java.io.InputStream;
public class ImageHolder {
private InputStream ImageInputStream;
private String ImageName;
public ImageHolder(InputStream imageInputStream, String imageName) {
ImageInputStream = imageInputStream;
ImageName = imageName;
}
public InputStream getImageInputStream() {
return ImageInputStream;
}
public void setImageInputStream(InputStream imageInputStream) {
ImageInputStream = imageInputStream;
}
public String getImageName() {
return ImageName;
}
public void setImageName(String imageName) {
ImageName = imageName;
}
}
然后接口参数就变三个

public ProductExecution addProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImageList)
throws ProductExecutionException;
我们把之前的店铺的添加和修改的service有店铺缩略图和缩略图的名字,这两个是重复的,我们需要重构一下。重构要趁早。
service实现,其中分为四步获取前端的图片流然后处理成缩略图,并且赋值给product,然后添加到tb_product表中,并且通过设置mapper里的useGeneratedKeys="true"当插入成功就返回它的id,


其中的addThumbnail,是在通过类里面定义的,首先调用工具类里的获取是win还是linux,然后就把上传的文件流变成缩略图和存储到目录里

批量插入商品详情图


插入之前得先压缩跟缩略图差不多但是size不一样,也是一样加水印

ut测试


controller





前台,在商品管理里点击新增就跳到商品编辑页面,商品编辑和商品添加是同样的界面跟店铺注册和店铺信息编辑是一样的,那么如何看是编辑还是添加,只需要看是否有请求参数,有productid是编辑


批量添详情图的前端,每次点击上传详情页之后,会重新生成另一个详情图片的选项

并且引入common.js和接下来要编写的productoperation.js,其中common.js是从前端链接获取shopid,而productoperation.js无非就是判断当前页面是来添加的还是商品编辑的,就是看productId是否为空,

商品编辑前端,我们自己先手动输入productid=11,然后修改调试,然后提交看看有没有被修改就行了。

商品的管理
实现商品列表展示
商品下架功能,这个可以复用商品编辑的功能
首先从商店列表进入获取,获取店铺id,之后点击商品管理进入,之前我们点击商品管理之后进入的是商品添加,所以这里我们就得增加这个页面productmanagement,这个界面跟商品分类的界面很像,所以几乎是复用这个界面



商品管理页面具备几个功能
1、获取这个店铺下的商品列表并且进行展示
2、返回按钮返回到商店管理界面,新增按钮跳转到新增商品页面
3、点击编辑就会跳转到编辑商品界面
4、下架功能,点击会弹出是否下架,这个下架我们可以调用编辑商品的这个方法,Modifyproduct,因为我们这需要修改状态为0,它就不会再前台展示,毕竟1就展示。
5、预览功能,点击之后就会跳转到商品详情页里
dao层,queryProductList接口,跟queryShopList接口一样,传入一个productCondition可以根据商品名模糊查询,商品状态,店铺id,商品类别去组合查询,然后还可以根据rowIndex就是从数据库的第几行开始查然后返回多少条记录就是这个pageSize,第二个接口,返回分页,返回满足这个productCondition的商品总数
/**
* 查询商品列表并分页,可输入的条件有:商品名(模糊),商品状态,店铺Id,商品类别
*
* @param productCondition
* @param beginIndex
* @param pageSize
* @return
*/
List<Product> queryProductList(
@Param("productCondition") Product productCondition,
@Param("rowIndex") int rowIndex, @Param("pageSize") int pageSize);
/**
* 查询对应的商品总数
*
* @param productCondition
* @return
*/
int queryProductCount(@Param("productCondition") Product productCondition);
mapper实现
<select id="queryProductList" resultMap="productMap">
SELECT
product_id,
product_name,
product_desc,
img_addr,
normal_price,
promotion_price,
priority,
create_time,
last_edit_time,
enable_status,
product_category_id,
shop_id
FROM
tb_product
<where>
<if
test="productCondition.shop!=null
and productCondition.shop.shopId!=null">
and shop_id = #{productCondition.shop.shopId}
</if>
<if
test="productCondition.productCategory!=null
and productCondition.productCategory.productCategoryId!=null">
and product_category_id =
#{productCondition.productCategory.productCategoryId}
</if>
<!-- 写like语句的时候 一般都会写成 like '% %' 在mybatis里面写就是应该是 like '%${name} %' 而不是
'%#{name} %' ${name} 是不带单引号的,而#{name} 是带单引号的 -->
<if test="productCondition.productName!=null">
and product_name like '%${productCondition.productName}%'
</if>
<if test="productCondition.enableStatus!=null">
and enable_status = #{productCondition.enableStatus}
</if>
</where>
ORDER BY
priority DESC
<!-- 支持分页 -->
LIMIT #{rowIndex},#{pageSize};
</select>
<select id="queryProductCount" resultType="int">
SELECT count(1) FROM tb_product
<where>
<if
test="productCondition.shop!=null
and productCondition.shop.shopId!=null">
and shop_id = #{productCondition.shop.shopId}
</if>
<if
test="productCondition.productCategory!=null
and productCondition.productCategory.productCategoryId!=null">
and product_category_id =
#{productCondition.productCategory.productCategoryId}
</if>
<!-- 写like语句的时候 一般都会写成 like '% %' 在mybatis里面写就是应该是 like '%${name} %' 而不是
'%#{name} %' ${name} 是不带单引号的,而#{name} 是带单引号的 -->
<if test="productCondition.productName!=null">
and product_name like '%${productCondition.productName}%'
</if>
<if test="productCondition.enableStatus!=null">
and enable_status = #{productCondition.enableStatus}
</if>
</where>
</select>
jt
service层
/**
* 分页查询获取商品列表 可输入的条件有:商品名(模糊名),商品状态,商品所属店铺id,商品类别id.
*
* @param productCondition
* @param pageIndex
* @param pageSize
* @return
*/
public ProductExecution getProductList(Product productCondition, int pageIndex, int pageSize);
service实现无非就是把我们的queryproductlist和queryproductcout整合到一块
@Override
public ProductExecution getProductList(Product productCondition, int pageIndex, int pageSize) {
//行页转换,调用dao取回指定页码的商品.
int rowIndex = PageCalculator.calculateRowIndex(pageIndex, pageSize);
List<Product> productList = productDao.queryProductList(productCondition, rowIndex, pageSize);
//基于同样的查询条件下的商品数量
int count = productDao.queryProductCount(productCondition);
ProductExecution pe = new ProductExecution();
pe.setProductList(productList);
pe.setCount(count);
return pe;
}
controller
/*
* 查询某一个店铺下的所有的商品(分页查询)
*/
@RequestMapping(value = "/getproductlistbyshop", method = RequestMethod.GET)
@ResponseBody
private Map<String, Object> getProductListByShop(HttpServletRequest request) {
Map<String, Object> modelMap = new HashMap<String, Object>();
// 获取前台传过来的页码
int pageIndex = HttpServletRequestUtil.getInt(request, "pageIndex");
// 获取前台传过来的每页显示数量上限
int pageSize = HttpServletRequestUtil.getInt(request, "pageSize");
// 获取当前店铺信息,主要是shopId
Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
// 分页查询空值判断
if ((pageIndex > -1) && (pageSize > -1) && (currentShop != null) && (currentShop.getShopId() != null)) {
// 获取传入的进行分页查询的条件,包括商品的模糊名,商品类别.这些条件可以进行组合使用
long productCategoryId = HttpServletRequestUtil.getLong(request, "productCategoryId");
String productName = HttpServletRequestUtil.getString(request, "productName");
// 整合获取到的查询条件
Product productCondition = compactProductCondition4Search(currentShop.getShopId(), productCategoryId,
productName);
// 分页查询
ProductExecution pe = productService.getProductList(productCondition, pageIndex, pageSize);
modelMap.put("productList", pe.getProductList());
modelMap.put("count", pe.getCount());
modelMap.put("success", true);
} else {
modelMap.put("success", false);
modelMap.put("errMsg", "empty pageSize or pageIndex or shopId");
}
return modelMap;
}
// 对零散的查询条件进行包装整合.
private Product compactProductCondition4Search(long shopId, long productCategoryId, String productName) {
Product productCondition = new Product();
Shop shop = new Shop();
shop.setShopId(shopId);
productCondition.setShop(shop);
if (productCategoryId != -1L) {
ProductCategory productCategory = new ProductCategory();
productCategory.setProductCategoryId(productCategoryId);
productCondition.setProductCategory(productCategory);
}
if (productName != null) {
productCondition.setProductName(productName);
}
return productCondition;
}
验证:
首先点击


商品列表前台展示
