【实战】7-2 商品管理模块开发测试

前言

找完工作以后感觉一段时间失去了学习的激情,再加上毕业论文的工作,懒散拖沓了好久才开始继续我的项目学习。其实这些内容吧,你说难那是一点也不难,重要的在于处理业务的经验,防患漏洞的经验,以及隐藏在项目背后的协调沟通能力,现在我是跟着老师视频在学,一个人设计全套,没有任何沟通协调问题,一旦到公司以后可能就会大不一样了···

好吧,2018新年开始第一天写一篇学习记录算是不错的开端了~

下面开始正文部分,商品部分分为前台顾客浏览的部分和后台商家管理的部分,这里的接口就按照上一章的来一 一实现,实际商城项目会复杂很多吧,废话少说,上代码,还是以代码中的注释为主要讲解。

商品前后台controller

对了,有部分代码是“几个月”前写的了,需要关注的重点就记不清了···

有一点,关于Integer和int的使用选择,Integer不赋值前是null,int是0,所以有时候要慎用int,不能分辨是不是没有值。

ProductController.java 前台没有实现太多功能,毕竟只是教学用。

package top.winxblast.happymall.controller.portal;

import com.github.pagehelper.PageInfo;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.vo.ProductDetailVo;

/**
 * 前台商品controller
 *
 * @author winxblast
 * @create 2017/11/13
 **/
@Controller
@RequestMapping(value = "/product/")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 返回商品详情,与后台不同的是,这里不需要验证管理员权限,而是要验证商品上下架状态
     * @param productId
     * @return
     */
    @RequestMapping(value = "detail.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<ProductDetailVo> detail(Integer productId) {
        return productService.getProductDetail(productId);
    }

    @RequestMapping(value = "list.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false) String keyword,
                                         @RequestParam(value = "categoryId",required = false)Integer categoryId,
                                         @RequestParam(value = "pageNum",defaultValue = "1")int pageNum,
                                         @RequestParam(value = "pageNum",defaultValue = "10")int pageSize,
                                         @RequestParam(value = "orderBy",defaultValue = "10")String orderBy){
        return productService.getProductByKeywordCategory(keyword, categoryId, pageNum, pageSize, orderBy);
    }
}

ProductManagerController.java 这里要吐槽一下老师关于各个类、方法的命名,总感觉风格不是很统一···
这里涉及到了文件上传的功能,之前我们用的Restlet Client工具对于这个文件上传的支持并不是很好,可以自己写一下jsp进行测试。

package top.winxblast.happymall.controller.backend;

import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ResponseCode;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.pojo.User;
import top.winxblast.happymall.service.FileService;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.service.UserService;
import top.winxblast.happymall.util.PropertiesUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * 商品后台管理
 *
 * @author winxblast
 * @create 2017/11/02
 **/
@Controller
@RequestMapping(value = "/manage/product")
public class ProductManageController {

    @Autowired
    private UserService userService;
    @Autowired
    private ProductService productService;
    @Autowired
    private FileService fileService;

    /**
     * 新增或更新商品
     * @param session
     * @param product
     * @return
     */
    @RequestMapping(value = "save.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse productSave(HttpSession session, Product product) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }
        if(userService.checkAdminRole(user).isSuccess()) {
            //填充我们增加产品的业务逻辑
            return productService.saveOrUpdateProduct(product);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 商品上下架
     * @param session
     * @param productId
     * @param status
     * @return
     */
    @RequestMapping(value = "set_sale_status.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse setSaleStatus(HttpSession session, Integer productId, Integer status) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }
        if(userService.checkAdminRole(user).isSuccess()) {
            //商品上下架业务逻辑
            return productService.setSaleStatus(productId, status);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 商品详情
     * @param session
     * @param productId
     * @return
     */
    @RequestMapping(value = "detail.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse getDetail(HttpSession session, Integer productId) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }

        if(userService.checkAdminRole(user).isSuccess()) {
            //填充业务
            return productService.manageProductDetail(productId);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 后台商品列表
     * @param session
     * @param pageNum
     * @param pageSize
     * @return
     */
    @RequestMapping(value = "list.do", method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse getList(HttpSession session, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageSize",defaultValue = "10")int pageSize) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }

        if(userService.checkAdminRole(user).isSuccess()) {
            //填充业务
            //分页的一些说明,使用pagehelper包辅助,使用AOP技术,监听我们自己的sql
            return productService.getProductList(pageNum, pageSize);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 通过商品名称和id来获取商品列表
     * @param session
     * @param productName
     * @param productId
     * @param pageNum
     * @param pageSize
     * @return
     */
    @RequestMapping(value = "search.do")
    @ResponseBody
    public ServerResponse productSearch(HttpSession session, String productName, Integer productId, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageSize",defaultValue = "10")int pageSize) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }

        if(userService.checkAdminRole(user).isSuccess()) {
            //填充业务
            return productService.searchProduct(productName, productId, pageNum, pageSize);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 文件上传
     * @param session
     * @param file
     * @param request
     * @return
     */
    @RequestMapping(value = "upload.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse upload(HttpSession session, @RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request) {
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");
        }

        if(userService.checkAdminRole(user).isSuccess()) {
            //填充业务
            String path = request.getSession().getServletContext().getRealPath("upload");
            String targetFileName = fileService.upload(file,path);
            //根据和前端的约定,需要放回完整的图片地址
            String url = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFileName;

            Map fileMap = Maps.newHashMap();
            fileMap.put("uri", targetFileName);
            fileMap.put("url", url);
            return ServerResponse.createBySuccess(fileMap);
        } else {
            return ServerResponse.createByErrorMessage("无权限操作");
        }
    }

    /**
     * 富文本上传文件(图片),这里是这个意思,这里保存的不是富文本,还是图片,只不过是富文本编辑器来调用这个接口
     * 把富文本编辑器接收到的图片传到我们的ftp服务器中
     * @param session
     * @param file
     * @param request
     * @return
     */
    @RequestMapping(value = "richtext_img_upload.do", method = RequestMethod.POST)
    @ResponseBody
    public Map richtextImgUpload(HttpSession session, @RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
        Map resultMap = Maps.newHashMap();
        User user = (User) session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            resultMap.put("success", false);
            resultMap.put("msg", "用户未登录,请登录管理员");
            return resultMap;
        }

        //富文本中对于返回值有自己的要求,我们使用的simditor,所以按照simditor要求返回
        //同时simditor还要求修改servletresponse
        //参考网站http://simditor.tower.im/docs/doc-config.html
//        {
//            "success": true/false,
//                "msg": "error message", # optional
//            "file_path": "[real file path]"
//        }
        if(userService.checkAdminRole(user).isSuccess()) {
            //填充业务
            String path = request.getSession().getServletContext().getRealPath("upload");
            String targetFileName = fileService.upload(file,path);
            if(StringUtils.isBlank(targetFileName)) {
                resultMap.put("success", false);
                resultMap.put("msg", "上传失败");
                return resultMap;
            } else {
                //根据和前端的约定,需要放回完整的图片地址
                String url = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFileName;
                resultMap.put("success", true);
                resultMap.put("msg", "上传成功");
                resultMap.put("file_path", url);
                //这就是修改response,算是和前端的约定
                response.addHeader("Access-Control-Allow-Headers", "X-File-Name");
                return resultMap;
            }
        } else {
            resultMap.put("success", false);
            resultMap.put("msg", "无权限操作");
            return resultMap;
        }
    }
}

商品service层及文件service层

到了service层就不用区分前后台了,这个只不过是controller中关于身份认证的问题,大部分service的方法,前后台都是可以通用的。

接口文件ProductService.java

package top.winxblast.happymall.service;

import com.github.pagehelper.PageInfo;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.vo.ProductDetailVo;

/**
 * 商品相关服务
 *
 * @author winxblast
 * @create 2017/11/02
 **/
public interface ProductService {

    ServerResponse saveOrUpdateProduct(Product product);

    ServerResponse<String> setSaleStatus(Integer productId, Integer status);

    ServerResponse<ProductDetailVo> manageProductDetail(Integer productId);

    ServerResponse<PageInfo> getProductList(int pageNum, int pageSize);

    ServerResponse<PageInfo> searchProduct(String productName, Integer productId, int pageNum, int pageSize);

    ServerResponse<ProductDetailVo> getProductDetail(Integer productId);

    ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, Integer categoryId, int pageNum, int pageSize, String orderBy);
}

service接口实现类,提到这个,到现在我还不能很好的理解依赖注入对于减少耦合的帮助,还有这里面尽然不需要考虑多线程的问题,难倒都被Spring把相关细节隐藏的了么,我需要好好学习啊~
ProductServiceImpl.java

package top.winxblast.happymall.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ResponseCode;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.dao.CategoryMapper;
import top.winxblast.happymall.dao.ProductMapper;
import top.winxblast.happymall.pojo.Category;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.service.CategoryService;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.util.DateTimeUtil;
import top.winxblast.happymall.util.PropertiesUtil;
import top.winxblast.happymall.vo.ProductDetailVo;
import top.winxblast.happymall.vo.ProductListVo;

import java.util.ArrayList;
import java.util.List;

/**
 * 商品服务接口实现类
 *
 * @author winxblast
 * @create 2017/11/02
 **/
@Service(value = "productService")
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private CategoryService categoryService;

    /**
     * 新增或者更新产品,这个在前端需要分开两个功能,在后端可以在一个方法中实现
     * @param product
     * @return
     */
    @Override
    public ServerResponse saveOrUpdateProduct(Product product) {
        if(product == null) {
            return ServerResponse.createByErrorMessage("新增或更新产品参数不正确");
        }

        //如果子图不为空,就取第一张图片作为主图
        if(StringUtils.isNotBlank(product.getSubImages())) {
            String[] subImageArray = product.getSubImages().split(",");
            if(subImageArray.length > 0) {
                product.setMainImage(subImageArray[0]);
            }
        }

        //有产品id表示是更新
        if(product.getId() != null) {
            //看到这里,感觉这里没有涉及事务管理啊,可能并发的考虑也不多,后期自己看看能不能加一些相关的内容
            int rowCount = productMapper.updateByPrimaryKey(product);
            if(rowCount > 0) {
                return ServerResponse.createBySuccess("更新产品成功");
            } else {
                return ServerResponse.createByErrorMessage("更新产品失败");
            }
        } else {
            //没有id就新增产品
            int rowCount = productMapper.insert(product);
            if(rowCount > 0) {
                return ServerResponse.createBySuccess("新增产品成功");
            } else {
                return ServerResponse.createByErrorMessage("新增产品失败");
            }
        }
    }

    /**
     * 商品上下架
     * @param productId
     * @param status
     * @return
     */
    @Override
    public ServerResponse<String> setSaleStatus(Integer productId, Integer status) {
        if(productId == null || status == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());
        }
        Product product = new Product();
        product.setId(productId);
        product.setStatus(status);
        int rowCount = productMapper.updateByPrimaryKeySelective(product);
        if(rowCount > 0) {
            return ServerResponse.createBySuccess("修改商品销售状态成功");
        } else {
            return ServerResponse.createByErrorMessage("修改商品销售状态失败");
        }
    }

    /**
     * 获得商品详情,后台
     * @param productId
     * @return
     */
    @Override
    public ServerResponse<ProductDetailVo> manageProductDetail(Integer productId) {
        if(productId == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());
        }
        Product product = productMapper.selectByPrimaryKey(productId);
        if(product == null) {
            return ServerResponse.createByErrorMessage("商品已下架或者删除");
        }
        //这里用vo对象--value object
        //业务更复杂:pojo-->bo(business object)-->vo(view object)

        //通过一个私有化的方法来组装这个对象
        ProductDetailVo productDetailVo = assembleProductDetailVo(product);
        return ServerResponse.createBySuccess(productDetailVo);

    }
    private ProductDetailVo assembleProductDetailVo(Product product) {
        ProductDetailVo productDetailVo = new ProductDetailVo();
        //下面我不能理解这么麻烦为什么不写到ProductDetailVo的一个构造函数中
        productDetailVo.setId(product.getId());
        productDetailVo.setCategoryId(product.getCategoryId());
        productDetailVo.setName(product.getName());
        productDetailVo.setSubtitle(product.getSubtitle());
        productDetailVo.setMainImage(product.getMainImage());
        productDetailVo.setSubImages(product.getSubImages());
        productDetailVo.setDetail(product.getDetail());
        productDetailVo.setPrice(product.getPrice());
        productDetailVo.setStock(product.getStock());
        productDetailVo.setStatus(product.getStatus());

        //imageHost,从配置文件中获取,为了配置和代码分离,为了热部署、配置中心等等
        productDetailVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.happymall.winxblast.top/"));

        //parentCategoryId
        Category category = categoryMapper.selectByPrimaryKey(product.getId());
        if(category == null) {
            //没有就默认根节点吧,这样也奇怪···先这么定
            productDetailVo.setParentCategoryId(0);
        } else {
            productDetailVo.setParentCategoryId(category.getParentId());
        }

        //createTime
        productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));
        //updateTime
        productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));

        return productDetailVo;
    }

    /**
     * 获得商品列表
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public ServerResponse<PageInfo> getProductList(int pageNum, int pageSize) {
        //startPage-start
        //填充自己的sql查询逻辑
        //pageHelper-收尾

        PageHelper.startPage(pageNum, pageSize);
        List<Product> productList = productMapper.selectList();
        //由于列表也不需要太多的信息,不用把查找到的商品详情全部返回,所以也创建一个VO
        //还有老师这里优化的可能也不是很够,既然不需要那么多信息,那么sql查询的时候就不要查这么多信息
        List<ProductListVo> productListVoList = Lists.newArrayList();
        for(Product productItem : productList) {
            ProductListVo productListVo = assembleProductListVo(productItem);
            productListVoList.add(productListVo);
        }
        /* 这是老师的写法,我感觉直接用productListVoList构造pageinfo就行了
        PageInfo pageResult = new PageInfo(productList);
        pageResult.setList(productListVoList);*/
        PageInfo pageResult = new PageInfo(productListVoList);
        return ServerResponse.createBySuccess(pageResult);

    }
    private ProductListVo assembleProductListVo(Product product) {
        ProductListVo productListVo = new ProductListVo();
        productListVo.setId(product.getId());
        productListVo.setCategoryId(product.getCategoryId());
        productListVo.setName(product.getName());
        productListVo.setSubtitle(product.getSubtitle());
        productListVo.setMainImage(product.getMainImage());
        productListVo.setPrice(product.getPrice());
        productListVo.setStatus(product.getStatus());
        productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.happymall.winxblast.top/"));
        return productListVo;
    }

    /**
     * 通过搜索名称或者id来返回商品列表
     * @param productName
     * @param productId
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public ServerResponse<PageInfo> searchProduct(String productName, Integer productId, int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        if(StringUtils.isNotBlank(productName)) {
            productName = new StringBuilder().append("%").append(productName).append("%").toString();
        }
        List<Product> productList = productMapper.selectByNameAndProductId(productName,productId);
        List<ProductListVo> productListVoList = Lists.newArrayList();
        for(Product productItem : productList) {
            ProductListVo productListVo = assembleProductListVo(productItem);
            productListVoList.add(productListVo);
        }
        PageInfo pageResult = new PageInfo(productList);
        pageResult.setList(productListVoList);
        return ServerResponse.createBySuccess(pageResult);
    }

    /**
     * 前台用户获取商品详情,大部分跟后台一致,只不过需要检查商品上下架状态
     * @param productId
     * @return
     */
    @Override
    public ServerResponse<ProductDetailVo> getProductDetail(Integer productId) {
        if(productId == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());
        }
        Product product = productMapper.selectByPrimaryKey(productId);
        if(product == null) {
            return ServerResponse.createByErrorMessage("商品已下架或者删除");
        }
        if(product.getStatus() != Const.ProductStatusEnum.ON_SALE.getCode()) {
            return ServerResponse.createByErrorMessage("商品已下架或者删除");
        }
        //这里用vo对象--value object
        //业务更复杂:pojo-->bo(business object)-->vo(view object)

        //通过一个私有化的方法来组装这个对象
        ProductDetailVo productDetailVo = assembleProductDetailVo(product);
        return ServerResponse.createBySuccess(productDetailVo);
    }

    /**
     * 通过关键字和商品分类获取商品列表分页
     * @param keyword
     * @param categoryId
     * @param pageNum
     * @param pageSize
     * @param orderBy
     * @return
     */
    @Override
    public ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, Integer categoryId,
                                                                int pageNum, int pageSize, String orderBy) {
        if(StringUtils.isBlank(keyword) && categoryId == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());
        }
        //这个是为了在categoryId是一个大的父分类的时候,可以把下面的小分类全部找出来放进去
        List<Integer> categoryIdList = new ArrayList<>();

        if(categoryId != null) {
            Category category = categoryMapper.selectByPrimaryKey(categoryId);
            if(category == null && StringUtils.isBlank(keyword)) {
                //没有该分类,并且还没有关键字,这个时候返回一个空的结果集,不报错,这个是和前端的商量结果,不一定的···
                PageHelper.startPage(pageNum,pageSize);
                List<ProductListVo> productListVoList = Lists.newArrayList();
                PageInfo pageInfo = new PageInfo(productListVoList);
                return ServerResponse.createBySuccess(pageInfo);
            }
            categoryIdList = categoryService.selectCategoryAndChildrenById(category.getId()).getData();
        }

        if(StringUtils.isNoneBlank(keyword)) {
            keyword = new StringBuilder().append("%").append(keyword).append("%").toString();
        }

        PageHelper.startPage(pageNum,pageSize);
        //排序处理
        if(StringUtils.isNotBlank(orderBy)) {
           if(Const.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
               String[] orderByArray = orderBy.split("_");
               PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]);
           }
        }
        List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword,
                categoryIdList.size()==0?null:categoryIdList);

        List<ProductListVo> productListVoList = Lists.newArrayList();
        for(Product product : productList) {
            ProductListVo productListVo = assembleProductListVo(product);
            productListVoList.add(productListVo);
        }

        //我感觉下面两句就可以合成一句
        PageInfo pageInfo = new PageInfo(productList);
        pageInfo.setList(productListVoList);

        return ServerResponse.createBySuccess(pageInfo);
    }

}

上面这个service中有不少新的东西啊,包括使用了vo,view Object, 如果业务复杂的话可能还需要bo business object,这里我是这么理解vo和product类的区别的,因为数据库中product记录了这个商品的所有细节,却不一定要全部展示出来,比如在列表页只要几个关键信息就行了,商品详情页也不会把商品的一些只需要商家知道的信息放进去,所以有了vo后可以这样理清需要的内容。

这里面使用了pagehelper来帮助我们实现分页操作,功能强大,但是细节我们自己最好也要掌握。

fileService.java这个是图片上传的服务,看着简单,还是使用了别人成熟的包(ftp),需要自己好好体会。这里我就直接把实现类放出来了,接口大家改一改就有了

package top.winxblast.happymall.service.impl;

import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import top.winxblast.happymall.service.FileService;
import top.winxblast.happymall.util.FTPUtil;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * 文件服务接口实现类
 *
 * @author winxblast
 * @create 2017/11/12
 **/
@Service(value = "fileService")
public class FileServiceImpl implements FileService{

    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    /**
     * 文件上传
     * @param file
     * @param path
     * @return 保存时的文件名,异常是返回null
     */
    @Override
    public String upload(MultipartFile file, String path) {
        String fileName = file.getOriginalFilename();
        //扩展名
        //abc.jpg
        String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
        String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName;
        logger.info("开始上传文件,上传文件的文件名:{},上传路径:{},新文件名:{}",fileName,path,uploadFileName);

        File fileDir = new File(path);
        if(!fileDir.exists()) {
            fileDir.setWritable(true);
            fileDir.mkdirs();
        }
        File targetFile = new File(path, uploadFileName);

        try {
            file.transferTo(targetFile);
            //至此文件已经上传成功了

            //将targetFile上传到我们的ftp服务器上
            FTPUtil.upload(Lists.newArrayList(targetFile));

            //上传完成之后删除upload下面的文件
            targetFile.delete();

        } catch (IOException e) {
            logger.error("上传文件异常", e);
            return null;
        }

        return targetFile.getName();
    }

}

ftp上传工具类和配置读取工具类

FTPUtil.java

package top.winxblast.happymall.util;

import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

/**
 * FTP服务器工具类
 *
 * @author winxblast
 * @create 2017/11/12
 **/
public class FTPUtil {

    public static final Logger logger = LoggerFactory.getLogger(FTPUtil.class);

    private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
    private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
    private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");

    public FTPUtil(String ip, int port, String user, String pwd) {
        this.ip = ip;
        this.port = port;
        this.user = user;
        this.pwd = pwd;
    }

    public static boolean upload(List<File> fileList) throws IOException {
        FTPUtil ftpUtil = new FTPUtil(ftpIp,21,ftpUser,ftpPass);
        logger.info("开始连接FTP服务器");
        //异常抛出,这样在服务层能够有相应的处理
        //老师在这里remotePath加了img实际上不需要这样,不然要在图片服务器前缀那里也修改一下,我这里就不加了
        boolean result = ftpUtil.upload("", fileList);
        logger.info("开始连接FTP服务器,结束上传,上传结果:{}", result);
        return result;
    }

    private boolean upload(String remotePath, List<File> fileList) throws IOException {
        boolean uploaded = true;
        FileInputStream fis = null;
        //连接FTP服务器
        if(connectServer(this.ip, this.port, this.user, this.pwd)) {
            try {
                //这样可以修改文件目录
                ftpClient.changeWorkingDirectory(remotePath);
                //设置缓冲区
                ftpClient.setBufferSize(1024);
                ftpClient.setControlEncoding("UTF-8");
                //设置为二进制编码形式可以防止乱码
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                //打开本地被动模式,详见vsftp安装
                ftpClient.enterLocalPassiveMode();
                //然后可以开始上传
                for(File fileItem : fileList) {
                    fis = new FileInputStream(fileItem);
                    ftpClient.storeFile(fileItem.getName(), fis);
                }

            } catch (IOException e) {
                logger.error("上传文件异常",e);
                uploaded = false;
            } finally {
                //要释放连接,不然会占用资源,时间长了会有问题
                fis.close();
                ftpClient.disconnect();
            }
        }
        return uploaded;
    }

    private boolean connectServer(String ip, int port, String user, String pwd) {
        ftpClient = new FTPClient();
        boolean isSuccess = false;
        try {
            ftpClient.connect(ip);
            isSuccess = ftpClient.login(user, pwd);
        } catch (IOException e) {
            logger.error("连接FTP服务器异常",e);
        }
        return isSuccess;
    }

    private String ip;
    private int port;
    private String user;
    private String pwd;
    private FTPClient ftpClient;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public FTPClient getFtpClient() {
        return ftpClient;
    }

    public void setFtpClient(FTPClient ftpClient) {
        this.ftpClient = ftpClient;
    }
}

PropertiesUtil.java

package top.winxblast.happymall.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

/**
 * Created by geely
 * 读取配置文件的工具类,当然在这里限制了读happymall.properties这个文件
 */
public class PropertiesUtil {

    private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);

    private static Properties props;

    //写一点老生长谈的基础内容,一个类加载,静态块优于普通代码块,优于构造函数
    //且静态块只加载一次
    static {
        String fileName = "happymall.properties";
        props = new Properties();
        try {
            props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
        } catch (IOException e) {
            logger.error("配置文件读取异常",e);
        }
    }

    public static String getProperty(String key){
        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            return null;
        }
        return value.trim();
    }

    public static String getProperty(String key,String defaultValue){

        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            value = defaultValue;
        }
        return value.trim();
    }
}

ok,看了这么些别人写的工具类,感觉自己还有差距,可能我写出来也能用,但是肯定不够安全或考虑全面。

dao层

最后就是和数据库交互的部分了,mapper接口比较简单,xml文件注意学习if、where、foreach等标签的使用方法
ProductMapper.java

package top.winxblast.happymall.dao;

import org.apache.ibatis.annotations.Param;
import top.winxblast.happymall.pojo.Product;

import java.util.List;

public interface ProductMapper {
    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int deleteByPrimaryKey(Integer id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int insert(Product record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int insertSelective(Product record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    Product selectByPrimaryKey(Integer id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int updateByPrimaryKeySelective(Product record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_product
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int updateByPrimaryKey(Product record);

    /**
     * 商品列表,没有任何搜索选项,分页靠插件完成
     * @return
     */
    List<Product> selectList();

    /**
     * 通过商品名称或者ID查询商品列表
     * @param productName
     * @param productId
     * @return
     */
    List<Product> selectByNameAndProductId(@Param("productName")String productName, @Param("productId")Integer productId);

    /**
     * 通过商品名称和产品分类来查询商品列表
     * @param productName
     * @param categoryIdList
     * @return
     */
    List<Product> selectByNameAndCategoryIds(@Param("productName")String productName, @Param("categoryIdList")List<Integer> categoryIdList);
}

ProductMapper.xml 前面是mybatisplugin插件生成的,后面一些是自己添加的。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.winxblast.happymall.dao.ProductMapper" >
  <resultMap id="BaseResultMap" type="top.winxblast.happymall.pojo.Product" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    <constructor >
      <idArg column="id" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="category_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="name" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="subtitle" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="main_image" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="sub_images" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="detail" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="price" jdbcType="DECIMAL" javaType="java.math.BigDecimal" />
      <arg column="stock" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="status" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="create_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
      <arg column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
    </constructor>
  </resultMap>
  <sql id="Base_Column_List" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    id, category_id, name, subtitle, main_image, sub_images, detail, price, stock, status, 
    create_time, update_time
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    select 
    <include refid="Base_Column_List" />
    from happymall_product
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    delete from happymall_product
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="top.winxblast.happymall.pojo.Product" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    insert into happymall_product (id, category_id, name, 
      subtitle, main_image, sub_images, 
      detail, price, stock, 
      status, create_time, update_time
      )
    values (#{id,jdbcType=INTEGER}, #{categoryId,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, 
      #{subtitle,jdbcType=VARCHAR}, #{mainImage,jdbcType=VARCHAR}, #{subImages,jdbcType=VARCHAR}, 
      #{detail,jdbcType=VARCHAR}, #{price,jdbcType=DECIMAL}, #{stock,jdbcType=INTEGER}, 
      #{status,jdbcType=INTEGER}, now(), now()
      )
  </insert>
  <insert id="insertSelective" parameterType="top.winxblast.happymall.pojo.Product" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    insert into happymall_product
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="id != null" >
        id,
      </if>
      <if test="categoryId != null" >
        category_id,
      </if>
      <if test="name != null" >
        name,
      </if>
      <if test="subtitle != null" >
        subtitle,
      </if>
      <if test="mainImage != null" >
        main_image,
      </if>
      <if test="subImages != null" >
        sub_images,
      </if>
      <if test="detail != null" >
        detail,
      </if>
      <if test="price != null" >
        price,
      </if>
      <if test="stock != null" >
        stock,
      </if>
      <if test="status != null" >
        status,
      </if>
      <if test="createTime != null" >
        create_time,
      </if>
      <if test="updateTime != null" >
        update_time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="id != null" >
        #{id,jdbcType=INTEGER},
      </if>
      <if test="categoryId != null" >
        #{categoryId,jdbcType=INTEGER},
      </if>
      <if test="name != null" >
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="subtitle != null" >
        #{subtitle,jdbcType=VARCHAR},
      </if>
      <if test="mainImage != null" >
        #{mainImage,jdbcType=VARCHAR},
      </if>
      <if test="subImages != null" >
        #{subImages,jdbcType=VARCHAR},
      </if>
      <if test="detail != null" >
        #{detail,jdbcType=VARCHAR},
      </if>
      <if test="price != null" >
        #{price,jdbcType=DECIMAL},
      </if>
      <if test="stock != null" >
        #{stock,jdbcType=INTEGER},
      </if>
      <if test="status != null" >
        #{status,jdbcType=INTEGER},
      </if>
      <if test="createTime != null" >
        now(),
      </if>
      <if test="updateTime != null" >
        now(),
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="top.winxblast.happymall.pojo.Product" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    update happymall_product
    <set >
      <if test="categoryId != null" >
        category_id = #{categoryId,jdbcType=INTEGER},
      </if>
      <if test="name != null" >
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="subtitle != null" >
        subtitle = #{subtitle,jdbcType=VARCHAR},
      </if>
      <if test="mainImage != null" >
        main_image = #{mainImage,jdbcType=VARCHAR},
      </if>
      <if test="subImages != null" >
        sub_images = #{subImages,jdbcType=VARCHAR},
      </if>
      <if test="detail != null" >
        detail = #{detail,jdbcType=VARCHAR},
      </if>
      <if test="price != null" >
        price = #{price,jdbcType=DECIMAL},
      </if>
      <if test="stock != null" >
        stock = #{stock,jdbcType=INTEGER},
      </if>
      <if test="status != null" >
        status = #{status,jdbcType=INTEGER},
      </if>
      <if test="createTime != null" >
        create_time = #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updateTime != null" >
        update_time = now(),
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="top.winxblast.happymall.pojo.Product" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    update happymall_product
    set category_id = #{categoryId,jdbcType=INTEGER},
      name = #{name,jdbcType=VARCHAR},
      subtitle = #{subtitle,jdbcType=VARCHAR},
      main_image = #{mainImage,jdbcType=VARCHAR},
      sub_images = #{subImages,jdbcType=VARCHAR},
      detail = #{detail,jdbcType=VARCHAR},
      price = #{price,jdbcType=DECIMAL},
      stock = #{stock,jdbcType=INTEGER},
      status = #{status,jdbcType=INTEGER},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      update_time = now()
    where id = #{id,jdbcType=INTEGER}
  </update>

  <select id="selectList" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List"/>
    FROM happymall_product
    ORDER BY id ASC
  </select>

  <select id="selectByNameAndProductId" resultMap="BaseResultMap" parameterType="map">
    SELECT
    <include refid="Base_Column_List"/>
    FROM happymall_product
    <where>
      <if test="productName != null">
        AND name LIKE #{productName}
      </if>
      <if test="productId != null">
        AND id = #{productId}
      </if>
    </where>
  </select>

  <select id="selectByNameAndCategoryIds" resultMap="BaseResultMap" parameterType="map">
    SELECT
    <include refid="Base_Column_List"></include>
    FROM happymall_product
    WHERE status = 1
    <if test="productName != null">
      AND name LIKE  #{productName}
    </if>
    <if test="categoryIdList != null">
      AND category_id IN
      <foreach item="item" index="index" open="(" separator="," close=")" collection="categoryIdList">
        #{item}
      </foreach>
    </if>

  </select>
</mapper>

测试接口

最后就是我们的测试了,这个之前也做过好几次了,没有太多的区别了,这里给出测试图片上传的jsp
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h2>Hello World!</h2>


springMVC上传文件
<form name="form1" action="/manage/product/upload.do" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file">
    <input type="submit" value="SpringMVC上传文件">
</form>
<br>
<br>
富文本图片上传
<form name="form1" action="/manage/product/richtext_img_upload.do" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file">
    <input type="submit" value="富文本图片上传">
</form>

</body>
</html>

下面给出几个测试的截图,这里访问的地址还可以进一步优化,向着更restful的模式改进,有些接口的get或者post方法就要修改了。

product有这些接口测试
这里写图片描述

search.do
这里写图片描述

list.do
这里写图片描述

接口结果都是按照我们预想的来实现的,就不一一列出了。

一点插曲

中间想升级一下IDEA的版本,结果就 石皮 解 失败了,只好退回上一个版本2017.2···这么贵,没有公司买给我可怎么玩···然后就遇到了IDEA报错,编译时各种类找不到,但是明明都在的啊,网上查解决方案,最后我这是清除maven打包解决的(mvn clean package);也有通过清除IDEA缓存解决的,在IDEA中file–>invalidate caches / Restart。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值