校园商铺-8商品模块-2商品添加之Service层的实现
1. 创建ProductService接口使用的操作类、枚举类、异常类
1.1 枚举类
package com.csj2018.o2o.enums;
public enum ProductStateEnum {
SUCCESS(1,"创建成功"),INNER_ERROR(-1001,"操作失败"),EMPTY_LIST(-1002,"添加数少于1");
private int state;
private String stateInfo;
private ProductStateEnum(int state,String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static ProductStateEnum stateOf(int index) {
for(ProductStateEnum state:values()) {
if(state.getState() == index) {
return state;
}
}
return null;
}
}
1.2 异常类
package com.csj2018.o2o.exceptions;
public class ProductOperationException extends RuntimeException{
/**
* add generated serial verisonID
*/
private static final long serialVersionUID = -5157230159775214912L;
public ProductOperationException(String msg) {
super(msg);
}
}
1.3 执行结果类
package com.csj2018.o2o.dto;
import java.util.List;
import com.csj2018.o2o.entity.Product;
import com.csj2018.o2o.enums.ProductStateEnum;
public class ProductExecution {
//结果状态
private int state;
//状态标识
private String stateInfo;
//商品数量
private int count;
//操作的product(增删改商品的时候用)
private Product product;
//获取到饿product列表(查询商品列表的时候用)
private List<Product> productList;
public ProductExecution() {}
//失败的构造器
public ProductExecution(ProductStateEnum stateEnum) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
//成功的构造器
public ProductExecution(ProductStateEnum stateEnum,Product product) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.product = product;
}
//成功的构造器
public ProductExecution(ProductStateEnum stateEnum,List<Product> productList) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.productList = productList;
}
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 int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
}
2.ProductService接口
package com.csj2018.o2o.service;
import java.io.InputStream;
import java.util.List;
import com.csj2018.o2o.dto.ProductExecution;
import com.csj2018.o2o.entity.Product;
import com.csj2018.o2o.exceptions.ProductOperationException;
public interface ProductService {
/**
* 添加商品信息以及图片处理
* @param product
* @param thumbnail 缩略图
* @param thumbnailName
* @param productImgList 详情图
* @param productImgNameList
* @return
* @throws ProductOperationException
*/
ProductExecution addProduct(Product product, InputStream thumbnail,String thumbnailName,List<InputStream> productImgList,List<String> productImgNameList) throws ProductOperationException;
}
缩略图和详情图参数是相似的,因此可以封装一个类让方法更清晰。
3重构
3.1 封装图片和流
package com.csj2018.o2o.dto;
import java.io.InputStream;
public class ImageHolder {
private String imageName;
private InputStream image;
public ImageHolder(String imageName,InputStream image) {
this.imageName = imageName;
this.image = image;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
public InputStream getImage() {
return image;
}
public void setImage(InputStream image) {
this.image = image;
}
}
3.2 修改Service层
package com.csj2018.o2o.service;
import java.util.List;
import com.csj2018.o2o.dto.ImageHolder;
import com.csj2018.o2o.dto.ProductExecution;
import com.csj2018.o2o.entity.Product;
import com.csj2018.o2o.exceptions.ProductOperationException;
public interface ProductService {
/**
* 添加商品信息以及图片处理
* @param product
* @param thumbnail 缩略图
* @param productImgList 详情图
* @return
* @throws ProductOperationException
*/
ProductExecution addProduct(Product product, ImageHolder thumbnail,List<ImageHolder> productImgList) throws ProductOperationException;
}
4.实现Service
package com.csj2018.o2o.service.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.csj2018.o2o.dao.ProductDao;
import com.csj2018.o2o.dao.ProductImgDao;
import com.csj2018.o2o.dto.ImageHolder;
import com.csj2018.o2o.dto.ProductExecution;
import com.csj2018.o2o.entity.Product;
import com.csj2018.o2o.entity.ProductImg;
import com.csj2018.o2o.enums.ProductStateEnum;
import com.csj2018.o2o.exceptions.ProductOperationException;
import com.csj2018.o2o.service.ProductService;
import com.csj2018.o2o.util.*;
@Service
public class ProductServiceImpl implements ProductService{
@Autowired
private ProductDao productDao;
@Autowired
private ProductImgDao productImgDao;
/*
* 1.处理缩略图,获取缩略图相对路径并赋值给product
* 2.往tb_product写入商品信息,获取productId
* 3.结合productId批量处理商品详情图
* 4.将商品详情图列表批量插入tb_product_img中
* 通过spring的事务管理去执行这4不操作,任何一步出错就回滚,不会往表里写入
*/
@Override
@Transactional
public ProductExecution addProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImgHolderList)
throws ProductOperationException {
//空值判断
if(product != null && product.getShop() != null && product.getShop().getShopId() != null) {
//给商品设置默认属性
product.setCreateTime(new Date());
product.setLastEditTime(new Date());
//默认为上架的状态
product.setEnableStatus(1);
//若商品缩略图不为空,则添加缩略图信息到商品
if(thumbnail != null) {
addThumbnail(product,thumbnail);
}
try {
int effectedNum = productDao.insertProduct(product);
if(effectedNum <= 0) {
throw new ProductOperationException("创建商品失败");
}
}catch (Exception e) {
throw new ProductOperationException("创建商品失败:"+e.toString());
}
//若商品详情图不为空,则向tb_product_img添加商品详情图
if(productImgHolderList != null && productImgHolderList.size() >0) {
addProductImgList(product,productImgHolderList);
}
return new ProductExecution(ProductStateEnum.SUCCESS,product);
}else {
//传参为空,则返回控制错误信息
return new ProductExecution(ProductStateEnum.EMPTY);
}
}
/**
* 向product对象添加缩略图
* @param product
* @param thumbnail
*/
private void addThumbnail(Product product, ImageHolder thumbnail) {
String dest = PathUtil.getShopImagePath(product.getShop().getShopId());
String thumbnailAddr = ImageUtil.generateThumbnail(thumbnail, dest);
product.setImgAddr(thumbnailAddr);
}
private void addProductImgList(Product product, List<ImageHolder> productImgHolderList) {
//获取图片存储路径,这里直接存放到相应店铺的文件夹下,同缩略图
String dest = PathUtil.getShopImagePath(product.getShop().getShopId());
List<ProductImg> productImgList = new ArrayList<ProductImg>();
//遍历,获得详情图片列表
for(ImageHolder productImgHolder:productImgHolderList) {
String imgAddr = ImageUtil.generateNormalImg(productImgHolder, dest);
ProductImg productImg = new ProductImg();
productImg.setImgAddr(imgAddr);
productImg.setProductId(product.getProductId());
productImg.setCreateTime(new Date());
productImgList.add(productImg);
}
if(productImgList.size() > 0) {
try {
int effectedNum = productImgDao.batchInsertProductImg(productImgList);
if(effectedNum <= 0) {
throw new ProductOperationException("创建商品详情图片失败");
}
}catch (Exception e) {
throw new ProductOperationException("创建商品详情图片失败:"+e.toString());
}
}
}
}
5.Image工具类
package com.csj2018.o2o.util;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import com.csj2018.o2o.dto.ImageHolder;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
public class ImageUtil {
private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
private static final Random r = new Random();
private static Logger logger = LoggerFactory.getLogger(ImageUtil.class);
/**
* 将文件流CommonsMultipartFile转换成File
* @param cFile
* @return
*/
public static File transferCommonsMultipartFileToFile(CommonsMultipartFile cFile) {
File newFile = new File(cFile.getOriginalFilename());
try {
cFile.transferTo(newFile);
}catch(IllegalStateException e) {
logger.error(e.toString());
e.printStackTrace();;
}catch (IOException e) {
logger.error(e.toString());
e.printStackTrace();
}
return newFile;
}
/**
* 根据输入流,处理缩略图,并返回新生成图片的相对路径
* @param thumbnailInputStream
* @param targetAddr
* @return
*/
public static String generateThumbnail(ImageHolder thumbnail ,String targetAddr) {
//获取不重复的随机名
String realFileName = getRandomFileName();
//获取文件的扩展名如png,jpg等
String extension = getFileExtension(thumbnail.getImageName());
//如果目标路径不存在,即自动创建
makeDirPath(targetAddr);
//获取文件存储的相对路径(带文件名)
String relativeAddr = targetAddr + realFileName + extension;
logger.debug("current relativeAddr is:" + relativeAddr);
//获取文件要保存到的目标路径
File dest = new File(PathUtil.getImgBasePath()+relativeAddr);
logger.debug("current complete addr is:" + PathUtil.getImgBasePath()+relativeAddr);
//调用Thumbnails生成带有水印的图片
try {
Thumbnails.of(thumbnail.getImage()).size(200,200).watermark(Positions.BOTTOM_RIGHT,ImageIO.read(new File(basePath+"/newwater.png")),0.8f)
.outputQuality(0.8f).toFile(dest);
}catch (IOException e) {
logger.error(e.toString());
throw new RuntimeException("创建缩略图失败:"+e.toString());
}
return relativeAddr;
}
/**
* 根据输入流,生成详情图片,并返回新生成图片的相对地址
* @param thumbnail
* @param targetAddr
* @return
*/
public static String generateNormalImg(ImageHolder thumbnail ,String targetAddr) {
//获取不重复的随机名
String realFileName = getRandomFileName();
//获取文件的扩展名如png,jpg等
String extension = getFileExtension(thumbnail.getImageName());
//如果目标路径不存在,即自动创建
makeDirPath(targetAddr);
//获取文件存储的相对路径(带文件名)
String relativeAddr = targetAddr + realFileName + extension;
logger.debug("current relativeAddr is:" + relativeAddr);
//获取文件要保存到的目标路径
File dest = new File(PathUtil.getImgBasePath()+relativeAddr);
logger.debug("current complete addr is:" + PathUtil.getImgBasePath()+relativeAddr);
//调用Thumbnails生成带有水印的图片
try {
Thumbnails.of(thumbnail.getImage()).size(337,640).watermark(Positions.BOTTOM_RIGHT,ImageIO.read(new File(basePath+"/newwater.png")),0.8f)
.outputQuality(0.9f).toFile(dest);
}catch (IOException e) {
logger.error(e.toString());
throw new RuntimeException("创建详情图片失败:"+e.toString());
}
return relativeAddr;
}
/**
* 创建目标路径所设计的目录,即/a/b/c/xxx.jpg
* 那么a b c 这三个文件夹都得自动创建
* @param targetAddr
*/
private static void makeDirPath(String targetAddr) {
String realFileParentPath = PathUtil.getImgBasePath()+targetAddr;
File dirPath = new File(realFileParentPath);
if(!dirPath.exists()) {
dirPath.mkdirs();//上级目录不存在,也一并创建,同mkdir -p
}
}
/**
* 获取输入文件流的扩展名
* @param thumbnail
* @return
*/
private static String getFileExtension(String fileName) {
//输入的图片,只需或获取最后一个 . 后面的字符即可
return fileName.substring(fileName.lastIndexOf("."));
}
/**
* 生成随机文件名,当前年月日时分秒+5位随机数
* @param args
* @throws Exception
*/
public static String getRandomFileName() {
//获取随机的5位数:10000-99999
int rannum = r.nextInt(89999)+10000;
String nowTimeStr = sDateFormat.format(new Date());
return nowTimeStr + rannum;
}
public static void main(String[] args) throws Exception {
Thumbnails.of(new File(basePath + "/water.png")).size(30, 30).toFile(new File(basePath + "/newwater.png"));
// Thumbnails.of(new File("/Users/chenshanju/Downloads/cat.jpg")).size(200, 200)
// .watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "newwater.png")), 1.0f)
// .outputQuality(0.8).toFile("/Users/chenshanju/Downloads/newcat.jpg");
}
/**
* storePath是文件的路径还是目录的路径
* 如果storePath是文件路径则删除该文件;
* 如果storePath是目录路径则删除该目录下的所有文件
* @param storePath
*/
public static void deleteFileOfPath(String storePath) {
File fileOrPath = new File(PathUtil.getImgBasePath() + storePath);
if(fileOrPath.exists()) {
if(fileOrPath.isDirectory()) {
File[] files = fileOrPath.listFiles();
for(int i=0;i<files.length;i++) {
files[i].delete();
}
}
fileOrPath.delete();
}
}
}
6.service层单元测试
package com.csj2018.o2o.service;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.csj2018.o2o.BaseTest;
import com.csj2018.o2o.dto.ImageHolder;
import com.csj2018.o2o.dto.ProductExecution;
import com.csj2018.o2o.entity.Product;
import com.csj2018.o2o.entity.ProductCategory;
import com.csj2018.o2o.entity.Shop;
import com.csj2018.o2o.enums.ProductStateEnum;
import com.csj2018.o2o.exceptions.ProductOperationException;
public class ProductServiceTest extends BaseTest{
@Autowired
private ProductService productService;
@Test
public void testAddProduct() throws ProductOperationException,FileNotFoundException{
//创建shopID为1且productCategory为1的商品实例,并给其成员变量赋值
Product product = new Product();
Shop shop = new Shop();
shop.setShopId(1L);
ProductCategory pc = new ProductCategory();
pc.setProductCategoryId(10L);
product.setShop(shop);
product.setProductCategory(pc);
product.setProductName("测试商品1");
product.setProductDesc("测试商品1");
product.setPriority(20);
product.setCreateTime(new Date());
product.setEnableStatus(1);
//创建缩略图
File thumbnailFile = new File("/Users/chenshanju/Downloads/Y61.jpeg");
InputStream is = new FileInputStream(thumbnailFile);
ImageHolder thumbnail = new ImageHolder(thumbnailFile.getName(),is);
//创建2个详情图片
File productImg1 = new File("/Users/chenshanju/Downloads/Prado.jpg");
InputStream is1 = new FileInputStream(thumbnailFile);
File productImg2 = new File("/Users/chenshanju/Downloads/Pajero.jpg");
InputStream is2 = new FileInputStream(thumbnailFile);
List<ImageHolder> productImgList = new ArrayList<ImageHolder>();
productImgList.add(new ImageHolder(productImg1.getName(),is1));
productImgList.add(new ImageHolder(productImg2.getName(),is2));
ProductExecution pe = productService.addProduct(product, thumbnail, productImgList);
assertEquals(ProductStateEnum.SUCCESS.getState(),pe.getState());
}
}