实战SSM_O2O商铺_11【商铺注册】Controller层的实现

简介: 实战SSM_O2O商铺_11【商铺注册】Controller层的实现

2019120810383186.png

概述


实战SSM_O2O商铺_10【商铺注册】Service层的实现 实现之后,接下来编写控制层的代码


用到了jackson-databind https://github.com/FasterXML/jackson-databind ,将前台传递过来的JSON对象转换为POJO类


20180523032949585.png


结构


20180523034029453.png


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的方式来逐步调试。

数据库表:


20180523041014535.png

图片:


20180523041044318.png


Github地址


代码地址: https://github.com/yangshangwei/o2o

相关文章
|
14小时前
|
SQL 测试技术
实战SSM_O2O商铺_32【商品】商品编辑之Dao层的实现
实战SSM_O2O商铺_32【商品】商品编辑之Dao层的实现
27 0
|
14小时前
|
前端开发 数据库
实战SSM_O2O商铺_31【商品】商品添加之View层的实现
实战SSM_O2O商铺_31【商品】商品添加之View层的实现
23 0
|
14小时前
|
前端开发 fastjson 测试技术
实战SSM_O2O商铺_30【商品】商品添加之Controller层的实现
实战SSM_O2O商铺_30【商品】商品添加之Controller层的实现
27 0
|
14小时前
|
前端开发 Java 数据库连接
实战SSM_O2O商铺_29【商品】商品添加之Service层的实现及重构
实战SSM_O2O商铺_29【商品】商品添加之Service层的实现及重构
24 0
|
14小时前
|
测试技术
实战SSM_O2O商铺_28【商品】商品添加之Dao层的实现
实战SSM_O2O商铺_28【商品】商品添加之Dao层的实现
34 0
|
14小时前
|
SQL 前端开发 测试技术
实战SSM_O2O商铺_27【商品类别】删除商品类别从Dao到View层的开发
实战SSM_O2O商铺_27【商品类别】删除商品类别从Dao到View层的开发
31 0
|
14小时前
|
SQL 前端开发 测试技术
实战SSM_O2O商铺_26【商品类别】批量新增商品类别从Dao到View层的开发
实战SSM_O2O商铺_26【商品类别】批量新增商品类别从Dao到View层的开发
32 0
|
14小时前
|
JSON 测试技术 应用服务中间件
实战SSM_O2O商铺_25【商品类别】商品类别列表展示从Dao到View层的开发
实战SSM_O2O商铺_25【商品类别】商品类别列表展示从Dao到View层的开发
32 0
|
14小时前
|
缓存 前端开发 安全
实战SSM_O2O商铺_24【商铺列表】View层开发
实战SSM_O2O商铺_24【商铺列表】View层开发
38 0
|
14小时前
|
测试技术
实战SSM_O2O商铺_23【商铺列表】Controller层开发
实战SSM_O2O商铺_23【商铺列表】Controller层开发
29 0