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

概述

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

数据库表:
这里写图片描述

图片:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/yangshangwei/article/details/80414172