学习目标

  • 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/

mmal商城商品模块总结的更多相关文章

  1. ssh整合问题总结--在添加商品模块实现图片(文件)的上传

    今天在做毕设(基于SSH的网上商城项目)中碰到了一个文件上传的需求,就是在后台管理员的商品模块中,有一个添加商品,需要将磁盘上的图片上传到tomcat保存图片的指定目录中: 完成这个功能需要两个步,第 ...

  2. SSH网上商城---商品详情页的制作

    在前面的博文中,小编分别简单的介绍了邮件的发送以及邮件的激活,逛淘宝的小伙伴都有这样的体会,比如在搜索框中输入连衣裙这个商品的时候,会出现多种多样各种款式的连衣裙,连衣裙的信息包括价格,多少人购买,商 ...

  3. Mvp快速搭建商城购物车模块

    代码地址如下:http://www.demodashi.com/demo/12834.html 前言: 说到MVP的时候其实大家都不陌生,但是涉及到实际项目中使用,还是有些无从下手.因此这里小编带着大 ...

  4. Scrapy实战篇(八)之Scrapy对接selenium爬取京东商城商品数据

    本篇目标:我们以爬取京东商城商品数据为例,展示Scrapy框架对接selenium爬取京东商城商品数据. 背景: 京东商城页面为js动态加载页面,直接使用request请求,无法得到我们想要的商品数据 ...

  5. ASP.NET MVC中商品模块小样

    在前面的几篇文章中,已经在控制台和界面实现了属性值的笛卡尔乘积,这是商品模块中的一个难点.本篇就来实现在ASP.NET MVC4下商品模块的一个小样.与本篇相关的文章包括: 1.ASP.NET MVC ...

  6. 电子商务(电销)平台中商品模块(Product)数据库设计明细

    以下是自己在电子商务系统设计中的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 商品表 (product)|-- 自动编号 (product_id)|-- 商品名称 (produc ...

  7. 电子商务(电销)平台中商品模块(Product)数据库设计明细(转载)

    电子商务(电销)平台中商品模块(Product)数据库设计明细 以下是自己在电子商务系统设计中的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 商品表 (product)|-- 自 ...

  8. Python网络爬虫——京东商城商品列表

    Python_网络爬虫--京东商城商品列表 最近在拓展自己知识面,想学习一下其他的编程语言,处于多方的考虑最终选择了Python,Python从发布之初就以庞大的用户集群占据了编程的一席之地,pyth ...

  9. MySQL-THINKPHP 商城系统一 商品模块的设计

    在此之前,先了解下关于SPU及SKU的知识 SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗点讲,属性值.特性相同的商品就可以称为一个SPU. ...

随机推荐

  1. CentOS7 扩容磁盘非根分区

    CentOS7扩展磁盘分区 ​ 因为业务所在分区空间不足,需要扩容.分配磁盘大小为1T但实际使用过程中发现仅使用了500G左右空间,本次计划在原有磁盘上扩容1T,加上之前500G空闲直接扩容1.5TB ...

  2. HTTP/2做错了什么?刚刚辉煌2年就要被弃用了!?

    GitHub 19k Star 的Java工程师成神之路,不来了解一下吗! GitHub 19k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 19k Star 的Java工 ...

  3. Sense Sense (Beta)安装及解决部分Chrome插件安装时程序包无效:"CRX_HEADER_INVALID"

    下载地址  http://down.cnplugins.com/down/down.aspx?fn=1412/www.cnplugins.com_lhjgkmllcaadmopgmanpapmpjgm ...

  4. html 03-初识HTML

    03-初识HTML #本文主要内容 头标签 排版标签:<p>. <div>. <span>.<br> . <hr> . <center ...

  5. sychronized的实现原理和应用

    一.synchronized的使用 1.1修饰方法 public synchronized void method() { // todo } 1.2修饰代码块 public void run() { ...

  6. 某毒霸网站存在信息泄露和sql注入

    通过钟馗之眼搜索目标网站的证书使用者找到信息泄露的网站, 点击上图中的edit会跳转 手工注入sql发现有效 that's all

  7. 我们是如何实现DevOps的

    一.DevOps的理解 DevOps的概念理解 DevOps 的概念在软件开发行业中逐渐流行起来.越来越多的团队希望实现产品的敏捷开发,DevOps 使一切成为可能.有了 DevOps ,团队可以定期 ...

  8. ADF 第七篇:控制流

    Azure Data Factory 系列博客: ADF 第一篇:Azure Data Factory介绍 ADF 第二篇:使用UI创建数据工厂 ADF 第三篇:Integration runtime ...

  9. 容器编排系统K8s之PV、PVC、SC资源

    前文我们聊到了k8s中给Pod添加存储卷相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14180752.html:今天我们来聊一下持久存储卷相关话题 ...

  10. 实用干货!Java开发企业级权限管理系统视频教程

    全程手把手带你运用Java技术栈,打造一套基于最流行的RBAC拓展模型的,分布式的,有界面的,高灵活性,高拓展性的企业级权限管理系统.学完本课程你将可以轻松应对绝大多数企业开发中与权限管理及后台系统相 ...