概述
实战SSM_O2O商铺_10【商铺注册】Service层的实现 实现之后,接下来编写控制层的代码
用到了jackson-databind https://github.com/FasterXML/jackson-databind ,将前台传递过来的JSON对象转换为POJO类
结构
Maven依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.7</version> </dependency>
封装工具类HttPServletRequestUtil
Controller层与View层密不可分,需要接受View层传递过来的信息,我们使用SSM框架的话,传递的请求信息都存在HttpServletRequest中。 因此需要先封装一个工具类来获取HttPServletRequest中的值。
Controller层负责具体的业务模块流程的控制,在此层里面要调用Serice层的接口来控制业务流程。
package com.artisan.o2o.util; import javax.servlet.http.HttpServletRequest; /** * * * @ClassName: HTTPServletRequestUtil * * @Description: 获取前端请求HttpServletRequest中参数的工具类 * * @author: Mr.Yang * * @date: 2018年5月21日 下午10:56:16 */ public class HttPServletRequestUtil { public static int getInt(HttpServletRequest request, String name) { try { return Integer.decode(request.getParameter(name)); } catch (Exception e) { return -1; } } public static long getLong(HttpServletRequest request, String name) { try { return Long.valueOf(request.getParameter(name)); } catch (Exception e) { return -1; } } public static Double getDouble(HttpServletRequest request, String name) { try { return Double.valueOf(request.getParameter(name)); } catch (Exception e) { return -1d; } } public static Boolean getBoolean(HttpServletRequest request, String name) { try { return Boolean.valueOf(request.getParameter(name)); } catch (Exception e) { return false; } } public static String getString(HttpServletRequest request, String name) { try { String result = request.getParameter(name); if (result != null) { result = result.trim(); } if ("".equals(result)) result = null; return result; } catch (Exception e) { return null; } } }
Controller控制层编写
package com.artisan.o2o.web.shopadmin; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.commons.CommonsMultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import com.artisan.o2o.dto.ShopExecution; import com.artisan.o2o.entity.PersonInfo; import com.artisan.o2o.entity.Shop; import com.artisan.o2o.enums.ShopStateEnum; import com.artisan.o2o.service.ShopService; import com.artisan.o2o.util.HttPServletRequestUtil; import com.fasterxml.jackson.databind.ObjectMapper; @Controller @RequestMapping("/shopadmin") public class ShopController { @Autowired private ShopService shopService; /** * * * @Title: registerShop * * @Description: * * @param request * 因为是接收前端的请求,而前端的信息都封装在HttpServletRequest中, * 所以需要解析HttpServletRequest,获取必要的参数 * * 1. 接收并转换相应的参数,包括shop信息和图片信息 2. 注册店铺 3. 返回结果给前台 * @return * * @return: Map<String,Object> */ @RequestMapping(value = "/registshop", method = RequestMethod.POST) @ResponseBody public Map<String, Object> registerShop(HttpServletRequest request) { Map<String, Object> modelMap = new HashMap<String, Object>(); // 1. 接收并转换相应的参数,包括shop信息和图片信息 // 1.1 shop信息 // shopStr 是和前端约定好的参数值,后端从request中获取request这个值来获取shop的信息 String shopStr = HttPServletRequestUtil.getString(request, "shopStr"); // 使用jackson-databind 将json转换为pojo ObjectMapper mapper = new ObjectMapper(); Shop shop = null; try { // 将json转换为pojo shop = mapper.readValue(shopStr, Shop.class); } catch (Exception e) { e.printStackTrace(); // 将错误信息返回给前台 modelMap.put("success", false); modelMap.put("errMsg", e.getMessage()); } // 1.2 图片信息 基于Apache Commons FileUpload的文件上传 // Spring MVC中的 图片存在CommonsMultipartFile中 CommonsMultipartFile shopImg = null; // 从request的本次会话中的上线文中获取图片的相关内容 CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); if (commonsMultipartResolver.isMultipart(request)) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; // shopImg是和前端约定好的变量名 shopImg = (CommonsMultipartFile) multipartRequest.getFile("shopImg"); } else { // 将错误信息返回给前台 modelMap.put("success", false); modelMap.put("errMsg", "图片不能为空"); } // 2. 注册店铺 if (shop != null && shopImg != null) { // Session TODO // 店主persionInfo的信息,肯定要登录才能注册店铺。 // 所以这部分信息我们从session中获取,尽量不依赖前端,这里暂时时不具备条件,后续改造,先硬编码,方便单元测试 PersonInfo personInfo = new PersonInfo(); personInfo.setUserId(1L); shop.setOwner(personInfo); // 注册店铺 // se = shopService.addShop(shop, shopImg); 改造前的调用方式 // 这个时候,我们从前端获取到的shopImg是CommonsMultipartFile类型的,如何将CommonsMultipartFile转换为file. // 网上也有将CommonsMultipartFile转换为File的方法,并通过maxInMemorySize的设置尽量不产生临时文件 // 这里我们换个思路,因为CommonsMultipartFile可以获取InputStream,Thumbnailator又可以直接处理输入流 // 因为InputStream中我们无法得到文件的名称,而thumbnail中需要根据文件名来获取扩展名,所以还要再加一个参数String类型的fileName // 既然第二个和第三个参数都是通过shopImg获取的,为什么不直接传入一个shopImg呢? // 主要是为了service层单元测测试的方便,因为service层很难实例化出一个CommonsMultipartFile类型的实例 ShopExecution se = null;; try { se = shopService.addShop(shop, shopImg.getInputStream(), shopImg.getOriginalFilename()); if (se.getState() == ShopStateEnum.CHECK.getState()) { modelMap.put("success", true); modelMap.put("errMsg", "注册成功"); } else { modelMap.put("success", false); modelMap.put("errMsg", se.getStateInfo()); } } catch (IOException e) { e.printStackTrace(); modelMap.put("success", false); modelMap.put("errMsg", "addShop Error"); } } else { // 将错误信息返回给前台 modelMap.put("success", false); modelMap.put("errMsg", "请输入店铺信息"); } return modelMap; } }
在Controller层,处理图片的环节,接收前端传递过来的图片类型为CommonsMultipartFile,而我们之前为了方便Service层的单元测试,addShop接口的定义如下
ShopExecution addShop(Shop shop, File shopFile);
所以,Controller在调用Servie层的时候,需要将CommonsMultipartFile转换为File,这里我们换个思路,因为CommonsMultipartFile可以获取InputStream,Thumbnailator又可以直接处理输入流,因为InputStream中我们无法得到文件的名称,而thumbnail中需要根据文件名来获取扩展名,所以还要再加一个参数String类型的fileName。
既然第二个和第三个参数都是通过shopImg获取的,为什么不直接传入一个shopImg呢?主要是为了service层单元测测试的方便,因为service层很难实例化出一个CommonsMultipartFile类型的实例
Service层的改造
package com.artisan.o2o.service; import java.io.InputStream; import com.artisan.o2o.dto.ShopExecution; import com.artisan.o2o.entity.Shop; public interface ShopService { // 修改入参,将File类型的入参修改为InputStream,同时增加String类型的文件名称 ShopExecution addShop(Shop shop, InputStream shopFileInputStream, String fileName); }
ShopServiceImpl实现类中:
// 修改入参,将File类型的入参修改为InputStream,同时增加String类型的文件名称 @Override @Transactional public ShopExecution addShop(Shop shop, InputStream shopImgInputStream, String fileName) { // 非空判断 (这里先判断shop是否为空,严格意义上讲shop中的are的属性也需要判断) if (shop == null) { return new ShopExecution(ShopStateEnum.NULL_SHOP_INFO); } // 关键步骤1. 设置基本信息,插入tb_shop // 初始状态: 审核中 shop.setEnableStatus(0); shop.setCreateTime(new Date()); shop.setLastEditTime(new Date()); int effectedNum = shopDao.insertShop(shop); if (effectedNum <= 0) { throw new ShopOperationException("店铺创建失败"); } else { // 关键步骤2. 添加成功,则继续处理文件,获取shopid,用于创建图片存放的目录 if (shopImgInputStream != null) { try { // 需要根据shopId来创建目录,所以也需要shop这个入参 addShopImg(shop, shopImgInputStream, fileName); } catch (Exception e) { logger.error("addShopImg error {} ", e.toString()); throw new ShopOperationException("addShopImg error:" + e.getMessage()); } // 关键步骤3. 更新tb_shop中 shop_img字段 effectedNum = shopDao.updateShop(shop); if (effectedNum <= 0) { logger.error("updateShop error {} ", "更新店铺失败"); throw new ShopOperationException("updateShop error"); } } } // 返回店铺的状态:审核中,以及店铺信息 return new ShopExecution(ShopStateEnum.CHECK, shop); } /** * * * @Title: addShopImg * * @Description: 根据shopId创建目录,并生成水印图片 * * @param shop * @param shopImg * * @return: void */ private void addShopImg(Shop shop, InputStream shopImgInputStream, String fileName) { String imgPath = FileUtil.getShopImagePath(shop.getShopId()); // 生成图片的水印图 String relativeAddr = ImageUtil.generateThumbnails(shopImgInputStream, imgPath, fileName); // 将相对路径设置个shop,用于更新数据库 shop.setShopImg(relativeAddr); }
ImageUitl工具类的改造
// 增加fileName参数 public static String generateThumbnails(InputStream ins, String destPath, String fileName) { // 拼接后的新文件的相对路径 String relativeAddr = null; try { // 1.为了防止图片的重名,不采用用户上传的文件名,系统内部采用随机命名的方式 String randomFileName = generateRandomFileName(); // 2.获取用户上传的文件的扩展名,用于拼接新的文件名 String fileExtensionName = getFileExtensionName(fileName); // 3.校验目标目录是否存在,不存在创建目录 validateDestPath(destPath); // 4.拼接新的文件名 relativeAddr = destPath + randomFileName + fileExtensionName; logger.info("图片相对路径 {}", relativeAddr); // 绝对路径的形式创建文件 String basePath = FileUtil.getImgBasePath(); File destFile = new File(basePath + relativeAddr); logger.info("图片完整路径 {}", destFile.getAbsolutePath()); // 5.给源文件加水印后输出到目标文件 Thumbnails.of(ins).size(500, 500).watermark(Positions.BOTTOM_RIGHT, ImageIO.read(FileUtil.getWaterMarkFile()), 0.25f).outputQuality(0.8).toFile(destFile); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("创建水印图片失败:" + e.toString()); } return relativeAddr; } // 修改入参File类型,直接使用String类型 private static String getFileExtensionName(String fileName) { String extension = fileName.substring(fileName.lastIndexOf(".")); logger.debug("extension: {}", extension); return extension; }
单元测试
我们改造了Service层的代码,这里我们重新来进行单元测试
package com.artisan.o2o.service; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Date; 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.ShopExecution; import com.artisan.o2o.entity.Area; import com.artisan.o2o.entity.PersonInfo; import com.artisan.o2o.entity.Shop; import com.artisan.o2o.entity.ShopCategory; import com.artisan.o2o.enums.ShopStateEnum; public class ShopServiceTest extends BaseTest { @Autowired ShopService shopService; @Test public void testAddShop() { Shop shop = new Shop(); PersonInfo personInfo = new PersonInfo(); Area area = new Area(); ShopCategory shopCategory = new ShopCategory(); personInfo.setUserId(1L); area.setAreaId(1); shopCategory.setShopCategoryId(1L); shop.setOwner(personInfo); shop.setArea(area); shop.setShopCategory(shopCategory); shop.setShopName("咖啡店Improve"); shop.setShopDesc("小工匠的咖啡店Improve"); shop.setShopAddr("NanJing-Improve"); shop.setPhone("9876553"); shop.setPriority(99); shop.setCreateTime(new Date()); shop.setLastEditTime(new Date()); shop.setEnableStatus(ShopStateEnum.CHECK.getState()); shop.setAdvice("审核中Improve"); File shopFile = new File("D:/o2o/artisan.jpg"); ShopExecution se = null; InputStream ins = null; try { ins = new FileInputStream(shopFile); se = shopService.addShop(shop, ins, shopFile.getName()); } catch (FileNotFoundException e) { e.printStackTrace(); } Assert.assertEquals(ShopStateEnum.CHECK.getState(), se.getState()); } }
可以在addShop实现方法中增加断点,通过debug的方式来逐步调试。
数据库表:
图片: