实战SSM_O2O商铺_10【商铺注册】Service层的实现 实现之后,接下来编写控制层的代码
用到了jackson-databind https://github.com/FasterXML/jackson-databind ,将前台传递过来的JSON对象转换为POJO类
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.8.7version>
dependency>
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;
}
}
}
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
*/
@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类型的实例
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的方式来逐步调试。
代码地址: https://github.com/yangshangwei/o2o