实战SSM_O2O商铺_29【商品】商品添加之Service层的实现及重构
文章目錄
- 概述
- DTO類
- 自定義異常
- ProductService接口
- 重構(gòu)
- 重構(gòu)后的接口方法
- 接口實現(xiàn)類ProductServiceImpl
- 單元測試
- Github地址
概述
步驟如下:
-
1.處理商品的縮略圖,獲取相對路徑,為了調(diào)用dao層的時候?qū)懭?tb_product中的 img_addr字段有值
-
2.寫入tb_product ,得到product_id(Mybatis自動映射進去的)
-
3.集合product_id 批量處理商品詳情圖片
-
4.將商品詳情圖片 批量更新到 tb_proudct_img表
DTO類
我們知道,我們在操作Product的時候,需要給前端返回狀態(tài)信息等,單純的domain類無法滿足,這里我們使用DTO包裝一下,就如同前面操作Shop和ProductCategory一樣。
package com.artisan.o2o.dto;import java.util.List;import com.artisan.o2o.entity.Product; import com.artisan.o2o.enums.ProductStateEnum;/*** * * @ClassName: ProductExecution* * @Description: 操作Product返回的DTO* * @author: Mr.Yang* * @date: 2018年6月25日 上午1:25:21*/public class ProductExecution {/*** 操作返回的狀態(tài)信息*/private int state;/*** 操作返回的狀態(tài)信息描述*/private String stateInfo;/*** 操作成功的總量*/private int count;/*** 批量操作(查詢商品列表)返回的Product集合*/private List<Product> productList;/*** 增刪改的操作返回的商品信息*/private Product product;/*** * * @Title:ProductExecution* * @Description:默認構(gòu)造函數(shù)*/public ProductExecution() {}/*** * * @Title:ProductExecution* * @Description:批量操作成功的時候返回的ProductExecution* * @param productStateEnum* @param productList*/public ProductExecution(ProductStateEnum productStateEnum, List<Product> productList, int count) {this.state = productStateEnum.getState();this.stateInfo = productStateEnum.getStateInfo();this.productList = productList;this.count = count;}/*** * * @Title:ProductExecution* * @Description:單個操作成功時返回的ProductExecution* * @param productStateEnum* @param product*/public ProductExecution(ProductStateEnum productStateEnum, Product product) {this.state = productStateEnum.getState();this.stateInfo = productStateEnum.getStateInfo();this.product = product;}/*** * * @Title:ProductExecution* * @Description:操作失敗的時候返回的ProductExecution,僅返回狀態(tài)信息即可* * @param productStateEnum*/public ProductExecution(ProductStateEnum productStateEnum) {this.state = productStateEnum.getState();this.stateInfo = productStateEnum.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 int getCount() {return count;}public void setCount(int count) {this.count = count;}public List<Product> getProductList() {return productList;}public void setProductList(List<Product> productList) {this.productList = productList;}public Product getProduct() {return product;}public void setProduct(Product product) {this.product = product;}}這里我們對狀態(tài)和狀態(tài)信息使用ProductStateEnum 進行了封裝,代碼如下
package com.artisan.o2o.enums;/*** * * @ClassName: ProductStateEnum* * @Description: 使用枚舉表述常量數(shù)據(jù)字典* * @author: Mr.Yang* * @date: 2018年6月25日 上午1:32:23*/ public enum ProductStateEnum {SUCCESS(1, "操作成功"), INNER_ERROR(-1001, "操作失敗"), NULL_PARAMETER(-1002, "缺少參數(shù)");private int state;private String stateInfo;/*** * * @Title:ProductStateEnum* * @Description:私有構(gòu)造函數(shù),禁止外部初始化改變定義的常量* * @param state* @param stateInfo*/private ProductStateEnum(int state, String stateInfo) {this.state = state;this.stateInfo = stateInfo;}/*** * * @Title: getState* * @Description: 僅設(shè)置get方法,禁用set* * @return* * @return: int*/public int getState() {return state;}public String getStateInfo() {return stateInfo;}/*** * * @Title: stateOf* * @Description: 定義換成pulic static 暴漏給外部,通過state獲取ShopStateEnum* * values()獲取全部的enum常量* * @param state* * @return: ShopStateEnum*/public static ProductStateEnum stateOf(int state) {for (ProductStateEnum stateEnum : values()) {if(stateEnum.getState() == state){return stateEnum;}}return null;}}自定義異常
操作Product 同時還要 操作商品詳情的圖片信息,所以必須在一個事務(wù)中,只有繼承RuntimeException ,這樣在標注了@Transactional事務(wù)的方法中,出現(xiàn)了異常,才會回滾數(shù)據(jù)。
默認情況下,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務(wù);除此之外,Spring 不會回滾事務(wù)。
package com.artisan.o2o.exception;/*** * * @ClassName: ProductOperationException* * @Description: 繼承自RuntimeException ,這樣在標注了@Transactional事務(wù)的方法中,出現(xiàn)了異常,才會回滾數(shù)據(jù)。* * 默認情況下,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring* 將回滾事務(wù);除此之外,Spring 不會回滾事務(wù)。* * @author: Mr.Yang* * @date: 2018年6月25日 下午1:46:23*/ public class ProductOperationException extends RuntimeException {private static final long serialVersionUID = -6981952073033881834L;public ProductOperationException(String message) {super(message);}}ProductService接口
邏輯基本和 addShop相同,我們?nèi)タ聪耡ddShop接口中的入?yún)ⅰ?/p> /*** * * @Title: addShop* * @Description: 新增商鋪* * @param shop* @param shopFileInputStream* @param fileName* @return* * @return: ShopExecution*/ShopExecution addShop(Shop shop, InputStream shopFileInputStream, String fileName) throws ShopOperationException;
這里 商品處理,我們不僅需要處理商品的縮略圖信息,還要處理商品詳情中的多個圖片信息,因此,定義如下
ProductExecution addProduct(Product product, InputStream prodImgIns, String prodImgName, List<InputStream> prodImgDetailInsList, List<String> prodImgDetailNameList)throws ProductOperationException;重構(gòu)
5個參數(shù)??? 是不是不方便Controller的調(diào)用。 這里我們大膽的重構(gòu)一下,否則后面重構(gòu)的話成本越來越高。
我們將 InputStream prodImgIns和 String prodImgName 封裝到一個類中,取名為ImageHolder ,提供構(gòu)造函數(shù)用于初始化以及setter/getter方法 。
package com.artisan.o2o.dto;import java.io.InputStream;public class ImageHolder {private InputStream ins ;private String fileName;/*** * * @Title:ImageHolder* * @Description:構(gòu)造函數(shù)* * @param ins* @param fileName*/public ImageHolder(InputStream ins, String fileName) {this.ins = ins;this.fileName = fileName;}public InputStream getIns() {return ins;}public void setIns(InputStream ins) {this.ins = ins;}public String getFileName() {return fileName;}public void setFileName(String fileName) {this.fileName = fileName;}}之前addShop 和 modifyShop 以及 工具類中封裝的方法都需要整改,涉及部分較多, 不一一列舉了。
重構(gòu)完成后,驗證通過,詳見GithuHub中工程代碼。
重構(gòu)后的接口方法
package com.artisan.o2o.service;import java.io.InputStream; import java.util.List;import com.artisan.o2o.dto.ImageHolder; import com.artisan.o2o.dto.ProductExecution; import com.artisan.o2o.entity.Product; import com.artisan.o2o.exception.ProductOperationException;/*** * * @ClassName: ProductService* * @Description: ProductService* * @author: Mr.Yang* * @date: 2018年6月25日 上午1:59:40*/ public interface ProductService {/*** * * @Title: addProductDep 廢棄的方法* * @Description: 新增商品 。 因為無法從InputStream中獲取文件的名稱,所以需要指定文件名* * 需要傳入的參數(shù)太多,我們將InputStream 和 ImgName封裝到一個實體類中,便于調(diào)用。* * 及早進行優(yōu)化整合,避免后續(xù)改造成本太大* * @param product* 商品信息* @param prodImgIns* 商品縮略圖輸入流* @param prodImgName* 商品縮略圖名稱* @param prodImgDetailIns* 商品詳情圖片的輸入流* @param prodImgDetailName* 商品詳情圖片的名稱* @return* @throws ProductOperationException* * @return: ProductExecution*/@DeprecatedProductExecution addProductDep(Product product, InputStream prodImgIns, String prodImgName, List<InputStream> prodImgDetailInsList, List<String> prodImgDetailNameList)throws ProductOperationException;/*** * * @Title: addProduct* * @Description: 重構(gòu)后的addProduct* * @param product* 產(chǎn)品信息* @param imageHolder* 產(chǎn)品縮略圖的封裝信息* @param prodImgDetailList* 產(chǎn)品詳情圖片的封裝信息* @return* @throws ProductOperationException* * @return: ProductExecution*/ProductExecution addProduct(Product product, ImageHolder imageHolder, List<ImageHolder> prodImgDetailList) throws ProductOperationException; }接口實現(xiàn)類ProductServiceImpl
package com.artisan.o2o.service.impl;import java.io.InputStream; 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.artisan.o2o.dao.ProductDao; import com.artisan.o2o.dao.ProductImgDao; import com.artisan.o2o.dto.ImageHolder; import com.artisan.o2o.dto.ProductExecution; import com.artisan.o2o.entity.Product; import com.artisan.o2o.entity.ProductImg; import com.artisan.o2o.enums.ProductStateEnum; import com.artisan.o2o.exception.ProductOperationException; import com.artisan.o2o.service.ProductService; import com.artisan.o2o.util.FileUtil; import com.artisan.o2o.util.ImageUtil;/*** * * @ClassName: ProductServiceImpl* * @Description: @Service 標識的服務(wù)層* * @author: Mr.Yang* * @date: 2018年6月25日 上午3:59:56*/@Service public class ProductServiceImpl implements ProductService {@AutowiredProductDao productDao;@AutowiredProductImgDao productImgDao;@Deprecated@Overridepublic ProductExecution addProductDep(Product product, InputStream prodImgIns, String prodImgName, List<InputStream> prodImgDetailInsList, List<String> prodImgDetailNameList)throws ProductOperationException {// 廢棄的方法return null;}/*** 注意事務(wù)控制@Transactional* * * 步驟如下:* * 1.處理商品的縮略圖,獲取相對路徑,為了調(diào)用dao層的時候?qū)懭?tb_product中的 img_addr字段有值* * 2.寫入tb_product ,獲取product_id* * 3.集合product_id 批量處理商品詳情圖片* * 4.將商品詳情圖片 批量更新到 tb_proudct_img表* */@Override@Transactionalpublic ProductExecution addProduct(Product product, ImageHolder imageHolder, List<ImageHolder> prodImgDetailList) throws ProductOperationException {if (product != null && product.getShop() != null && product.getShop().getShopId() != null && product.getProductCategory().getProductCategoryId() != null) {// 設(shè)置默認的屬性 1 展示product.setCreateTime(new Date());product.setLastEditTime(new Date());product.setEnableStatus(1);// 如果文件的輸入流和文件名不為空,添加文件到特定目錄,并且將相對路徑設(shè)置給product,這樣product就有了ImgAddr,為下一步的插入tb_product提供了數(shù)據(jù)來源if (imageHolder != null) {addProductImg(product, imageHolder);}try {// 寫入tb_productint effectNum = productDao.insertProduct(product);if (effectNum <= 0 ) {throw new ProductOperationException("商品創(chuàng)建失敗");}// 如果添加商品成功,繼續(xù)處理商品詳情圖片,并寫入tb_product_imgif (prodImgDetailList != null && prodImgDetailList.size() > 0) {addProductDetailImgs(product, prodImgDetailList);}return new ProductExecution(ProductStateEnum.SUCCESS, product);} catch (Exception e) {throw new ProductOperationException("商品創(chuàng)建失敗:" + e.getMessage());}} else {return new ProductExecution(ProductStateEnum.NULL_PARAMETER);}}/*** * * @Title: addProductImg* * @Description: 將商品的縮略圖寫到特定的shopId目錄,并將imgAddr屬性設(shè)置給product* * @param product* @param imageHolder* * @return: void*/private void addProductImg(Product product, ImageHolder imageHolder) {// 根據(jù)shopId獲取圖片存儲的相對路徑String relativePath = FileUtil.getShopImagePath(product.getShop().getShopId());// 添加圖片到指定的目錄String relativeAddr = ImageUtil.generateThumbnails(imageHolder, relativePath);// 將relativeAddr設(shè)置給productproduct.setImgAddr(relativeAddr);}/*** * * @Title: addProductDetailImgs* * @Description: 處理商品詳情圖片,并寫入tb_product_img* * @param product* @param prodImgDetailList* * @return: void*/private void addProductDetailImgs(Product product, List<ImageHolder> prodImgDetailList) {String relativePath = FileUtil.getShopImagePath(product.getShop().getShopId());// 生成圖片詳情的圖片,大一些,并且不添加水印,所以另外寫了一個方法,基本和generateThumbnails相似List<String> imgAddrList = ImageUtil.generateNormalImgs(prodImgDetailList, relativePath);if (imgAddrList != null && imgAddrList.size() > 0) {List<ProductImg> productImgList = new ArrayList<ProductImg>();for (String imgAddr : imgAddrList) {ProductImg productImg = new ProductImg();productImg.setImgAddr(imgAddr);productImg.setProductId(product.getProductId());productImg.setCreateTime(new Date());productImgList.add(productImg);}try {int effectedNum = productImgDao.batchInsertProductImg(productImgList);if (effectedNum <= 0) {throw new ProductOperationException("創(chuàng)建商品詳情圖片失敗");}} catch (Exception e) {throw new ProductOperationException("創(chuàng)建商品詳情圖片失敗:" + e.toString());}}}}ImageUtil#generateNormalImgs方法
/*** * * @Title: generateNormalImgs* * @Description: 生成商品詳情的圖片* * @param prodImgDetailList* @param relativePath* @return* * @return: List<String>*/public static List<String> generateNormalImgs(List<ImageHolder> prodImgDetailList, String relativePath) {int count = 0;List<String> relativeAddrList = new ArrayList<String>();if (prodImgDetailList != null && prodImgDetailList.size() > 0) {validateDestPath(relativePath);for (ImageHolder imgeHolder : prodImgDetailList) {// 1.為了防止圖片的重名,不采用用戶上傳的文件名,系統(tǒng)內(nèi)部采用隨機命名的方式String randomFileName = generateRandomFileName();// 2.獲取用戶上傳的文件的擴展名,用于拼接新的文件名String fileExtensionName = getFileExtensionName(imgeHolder.getFileName());// 3.拼接新的文件名 :相對路徑+隨機文件名+i+文件擴展名String relativeAddr = relativePath + randomFileName + count + fileExtensionName;logger.info("圖片相對路徑 {}", relativeAddr);count++;// 4.絕對路徑的形式創(chuàng)建文件String basePath = FileUtil.getImgBasePath();File destFile = new File(basePath + relativeAddr);logger.info("圖片完整路徑 {}", destFile.getAbsolutePath());try {// 5. 不加水印 設(shè)置為比縮略圖大一點的圖片(因為是商品詳情圖片),生成圖片Thumbnails.of(imgeHolder.getIns()).size(600, 300).outputQuality(0.5).toFile(destFile);} catch (IOException e) {e.printStackTrace();throw new RuntimeException("創(chuàng)建圖片失敗:" + e.toString());}// 將圖片的相對路徑名稱添加到list中relativeAddrList.add(relativeAddr);}}return relativeAddrList;}單元測試
package com.artisan.o2o.service;import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.List;import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired;import com.artisan.o2o.BaseTest; import com.artisan.o2o.dto.ImageHolder; import com.artisan.o2o.dto.ProductExecution; import com.artisan.o2o.entity.Product; import com.artisan.o2o.entity.ProductCategory; import com.artisan.o2o.entity.Shop; import com.artisan.o2o.enums.ProductStateEnum;public class ProductServiceTest extends BaseTest {@Autowiredprivate ProductService productService;@Testpublic void testAddProduct() throws Exception {// 注意表中的外鍵關(guān)系,確保這些數(shù)據(jù)在對應(yīng)的表中的存在ProductCategory productCategory = new ProductCategory();productCategory.setProductCategoryId(36L);// 注意表中的外鍵關(guān)系,確保這些數(shù)據(jù)在對應(yīng)的表中的存在Shop shop = new Shop();shop.setShopId(5L);// 構(gòu)造ProductProduct product = new Product();product.setProductName("test_product");product.setProductDesc("product desc");product.setNormalPrice("10");product.setPromotionPrice("8");product.setPriority(66);product.setCreateTime(new Date());product.setLastEditTime(new Date());product.setEnableStatus(1);product.setProductCategory(productCategory);product.setShop(shop);// 構(gòu)造 商品圖片File productFile = new File("D:/o2o/artisan.jpg");InputStream ins = new FileInputStream(productFile);ImageHolder imageHolder = new ImageHolder(ins, productFile.getName());// 構(gòu)造商品詳情圖片List<ImageHolder> prodImgDetailList = new ArrayList<ImageHolder>();File productDetailFile1 = new File("D:/o2o/1.jpg");InputStream ins1 = new FileInputStream(productDetailFile1);ImageHolder imageHolder1 = new ImageHolder(ins1, productDetailFile1.getName());File productDetailFile2 = new File("D:/o2o/2.jpg");InputStream ins2 = new FileInputStream(productDetailFile2);ImageHolder imageHolder2 = new ImageHolder(ins2, productDetailFile2.getName());prodImgDetailList.add(imageHolder1);prodImgDetailList.add(imageHolder2);// 調(diào)用服務(wù)ProductExecution pe = productService.addProduct(product, imageHolder, prodImgDetailList);Assert.assertEquals(ProductStateEnum.SUCCESS.getState(), pe.getState());} }日志:
Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d9f7a80] JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@61f05988] will be managed by Spring ==> Preparing: 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( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ==> Parameters: test_product(String), product desc(String), \upload\item\shopImage\5\2018062516132272045.jpg(String), 10(String), 8(String), 66(Integer), 2018-06-25 16:13:22.184(Timestamp), 2018-06-25 16:13:22.184(Timestamp), 1(Integer), 36(Long), 5(Long) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d9f7a80] Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d9f7a80] from current transaction ==> Preparing: INSERT INTO tb_product_img ( img_addr, img_desc, priority, create_time, product_id ) VALUES ( ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ? ) ==> Parameters: \upload\item\shopImage\5\20180625161322338880.jpg(String), null, null, 2018-06-25 16:13:22.999(Timestamp), 6(Long), \upload\item\shopImage\5\20180625161322506811.jpg(String), null, null, 2018-06-25 16:13:22.999(Timestamp), 6(Long) <== Updates: 2 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d9f7a80]可以通過debug的方式一步步的檢查參數(shù),然后去查看數(shù)據(jù)庫表中的記錄和 對應(yīng)的圖片是正確生成。
庫表數(shù)據(jù)也OK。 單元測試通過。
Github地址
代碼地址: https://github.com/yangshangwei/o2o
總結(jié)
以上是生活随笔為你收集整理的实战SSM_O2O商铺_29【商品】商品添加之Service层的实现及重构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实战SSM_O2O商铺_28【商品】商品
- 下一篇: 实战SSM_O2O商铺_30【商品】商品