mmal商城商品模块总结

学习目标

  • FTP服务器的对接
  • SpringMVC文件上传
  • 流读取properties配置文件
  • 抽象POJO、BO、VO对象之间的转换关系及解决思路
  • joda-time快速入门
  • 静态代码块
  • mybatis-pageHelper

商品模块分为前后台操作,前台功能接口有:搜索,分页显示,商品详情;后台管理模块有保存商品,修改商品在线状态,获取商品详情,分页显示,按照名称或者商品id搜索,上传商品图片,富文本格式上传商品。

获取商品详情信息
这个之前做的方法大差不差,都是通过商品id来获取需要在前端显示的信息,但是与之前不同的是这次引入了VO的概念;value object(值对象);它的作用就是当我们在dao层从数据库中获取的数据封装到pojo中,但是pojo中的数据还和前端显示的有所差别,这时我们再封装一个value object;由它返回到前端显示;总的来说value object是显示层的对象,通常是web向模板引擎渲染并传输的对象;
下面这段总结来源于阿里巴巴开发手册中对pojo,vo,bo,dto,的定义与区别

阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义

分层领域模型规约

  • DO( Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
  • DTO( Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
  • BO( Business Object):业务对象。 由Service层输出的封装业务逻辑的对象。
  • AO( Application Object):应用对象。 在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
  • VO( View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
  • POJO( Plain Ordinary Java Object):在本手册中, POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
  • Query:数据查询对象,各层接收上层的查询请求。 注意超过2个参数的查询封装,禁止使用Map类来传输。

领域模型命名规约

  • 数据对象:xxxDO,xxx即为数据表名。
  • 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
  • 展示对象:xxxVO,xxx一般为网页名称。
  • POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

具体实现逻辑就是:在id不为空的情况下,我们直接从数据库中查询这个商品的信息,封装到pojo的DO对象中,然后判断这个对象的在线状态是否是下架的,如果是我们就返回提示信息,说明该商品已经下架;否则我们就开始封装ProductDetailVo对象;
最后把这个对象返回到显示层;

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 || product.getStatus() != Const.productStatus.ON_SEAL.getCode()) {
            return ServerResponse.createByErrorMessage("此商品已下架或已删除");
        }
        //封装VO
        ProductDetailVo productDetailVo = assembleProductDetailVo(product);
        //返回到显示层
        return ServerResponse.createBySuccessData(productDetailVo);
    }

分页模块:
这里使用了MyBatis-Helper;分页插件;
这时候前端需要传的参数就多了,关键字,分类id,当前页,每页显示的商品数量,排序方式;
因为使用了分页插件所以到左后分页的事情我们就不需要管了,我们先来看一下SQL语句;

 List<Product> selectByNameAndCategoryIds(@Param("productName")String productName,@Param("categoryIds") List<Integer> categoryIds);

这时Mapper接口我们需要注意的是,分类id是一个集合因为之前在分类模块中说过,一个父级别的分类下面还有许多子分类,所以传过来是一个集合;
xml中的SQL语句如下

  <select id="selectByNameAndCategoryIds" resultMap="BaseResultMap" parameterType="map">
    select
    <include refid="Base_Column_List"/>
    from mmall_product
    where status = 1
    <if test="productName != null">
      and name like #{productName}
    </if>
    <if test="categoryIds != null">
      and category_id in
      <foreach collection="categoryIds" index="index" item="item" open="(" close=")" separator=",">
        #{item}
      </foreach>
    </if>
  </select>
  <resultMap id="BaseResultMap" type="cn.edu.mmall.pojo.Product" >
    <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" >
    id, category_id, name, subtitle, main_image, sub_images, detail, price, stock, status, 
    create_time, update_time
  </sql>

sql语句的拼写还是值得注意的,以及遍历集合时,使用的foreach循环语法,记得加上前缀,后缀,分隔符,遍历集合,以及单个元素的item;

这时我们再回来看处理分页的逻辑;首先在搜索关键字和分类id都没有的情况下也就是都是为null,我们就返回非法参数的提示;如果分类id不为空,但是我们根据id查不出来也即是查到的对象是null,我们不应返回错误,应该返回一个空集合,毕竟我们确实进行了数据库的查询操作;如果关键词不为空并且不是空字符串或者空格,我们就对他进行拼接处理,也就是前后都加上%,然后开始处理排序,在传过来的排序名称不为空的情况下,我们对他进行比较判断是不是按照价格进行的从大到小排序还是从小到大排序,最后在PageHelper中设置排序方式;最后调用我们刚才写的SQL接口方法返回list集合,再将集合中的product对象一个个封装成Vo对象。

最后具体代码如下

 public ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, int pageNum, int pageSize, Integer categoryId, String orderBy) {
        if (StringUtils.isBlank(keyword) && categoryId == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());
        }
        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> productListVos = Lists.newArrayList();
                PageInfo pageInfo = new PageInfo<>(productListVos);
                return ServerResponse.createBySuccessData(pageInfo);
            }
        }
        //处理关键字
        if (StringUtils.isNoneBlank(keyword)) {
            keyword = new StringBuffer().append("%").append(keyword).append("%").toString();
        }
        PageHelper.startPage(pageNum, pageSize);
        //处理排序
        if (StringUtils.isNoneBlank(orderBy)) {
            if (Const.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
                String[] s = orderBy.split("_");
                PageHelper.orderBy(s[0] + " " + s[1]);
            }
        }
        //categoryIdList先前已经new了出来所以不为空,因此需要判断一下如果里面没有元素就是空,keyword可能是空字符串或者空格那就赋值为空
        List<Product> productList = productMapper.selectByNameAndCategoryIds(
                StringUtils.isBlank(keyword) ? null : keyword,
                categoryIdList.size() == 0 ? null : categoryIdList);

        //将product对象一个个封装到vo中,返回list集合
        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.createBySuccessData(pageInfo);
    }

后台管理模块需要多一项判断,判断当前用户是不是管理员角色;如果不是的情况下是没有权限进行操作的。后台管理基本上也是增加商品,修改商品状态,获取商品详情,列表显示这些与前台代码相差无几,这里主要说一下文件上传的实现逻辑。

controller中我们可以从请求中获取路径
(项目在容器中的实际发布运行的根路径)

调用FileService的upload方法返回上传文件的文件名,最终将URL与文件名一起返回到前台。

FTPUtil(连接ftp服务器,上传文件)

public class FTPUtil {

    private static 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");

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

    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 uploadFile(List<File> fileList) throws IOException {
        FTPUtil ftpUtil = new FTPUtil(ftpIp, 21, ftpUser, ftpPass);
        logger.info("开始连接FTP服务器");
        boolean result = ftpUtil.uploadFile("img", fileList);
        logger.info("结束上传,上传结果是:{}",result);
        return result;
    }

    private boolean uploadFile(String remotePath, List<File> fileList) throws IOException {
        boolean upload = true;
        FileInputStream fis = null;
        //连接ftp服务器
        if (connectFTPServer(this.ip, this.port, this.user, this.pwd)) {
            //如果连接成功,开始上传
            //1. 首先改变工作目录
            try {
                ftpClient.changeWorkingDirectory(remotePath);
                ftpClient.setBufferSize(1024);
                ftpClient.setControlEncoding("UTF-8");
                //设置文件类型是二进制文件
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

                ftpClient.enterLocalPassiveMode();

                for (File fileItem : fileList) {
                    fis = new FileInputStream(fileItem);
                    //储存文件
                    ftpClient.storeUniqueFile(fileItem.getName(), fis);
                }
            } catch (IOException e) {
                logger.error("上传文件异常");
                upload = false;
                e.printStackTrace();
            }finally {
                fis.close();
                ftpClient.disconnect();
            }

        }
            return upload;
    }

    private boolean connectFTPServer(String ip, int port, String user, String pwd) {
        boolean isSuccess = false;
        ftpClient = new FTPClient();
        try {
            ftpClient.connect(ip);
            isSuccess = ftpClient.login(user, pwd);

        } catch (IOException e) {
            logger.error("连接ftp服务器异常", e);
        }
        return isSuccess;
    }
    //省略getter/setter方法
public String upload(MultipartFile file, String path) {
        //全路径加上文件名
        String fileName = file.getName();
        //获取文件扩展名
        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);
            //todo 将target文件上传到FTP服务器上
            FTPUtil.uploadFile(Lists.newArrayList(targetFile));

            //todo 删除本地上传过的文件
            targetFile.delete();

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

ftp配置文件

ftp.server.ip=192.168.245.1
ftp.user=mmallftp
ftp.pass=ftppassword
ftp.server.http.prefix=http://img.happymmall.com/

原文地址:https://www.cnblogs.com/itjiangpo/p/14181418.html