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

文章目录

  • 概述
  • 结构
  • Maven依赖
  • 封装工具类HttPServletRequestUtil
  • Controller控制层编写
  • Service层的改造
  • 单元测试
  • Github地址

实战SSM_O2O商铺_11【商铺注册】Controller层的实现_第1张图片

概述

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

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

实战SSM_O2O商铺_11【商铺注册】Controller层的实现_第2张图片


结构

实战SSM_O2O商铺_11【商铺注册】Controller层的实现_第3张图片


Maven依赖

<dependency>
	<groupId>com.fasterxml.jackson.coregroupId>
	<artifactId>jackson-databindartifactId>
	<version>2.8.7version>
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
	 */

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

数据库表:
实战SSM_O2O商铺_11【商铺注册】Controller层的实现_第4张图片

图片:
实战SSM_O2O商铺_11【商铺注册】Controller层的实现_第5张图片


Github地址

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

你可能感兴趣的:(【实战-SSM,In,Action】,实战系列-SSM实战,ssm实战)