学习目标

  • 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. 题解 CF1437G Death DBMS

    这题感觉不是很难,但是既然放在 \(\texttt{EDU}\) 的 \(\texttt{G}\) 题,那么还是写写题解吧. \(\texttt{Solution}\) 首先看到 "子串&q ...

  2. Springboot — 用更优雅的方式发HTTP请求:RestTemplate

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  3. 戴尔iDRAC+Ubuntu 18.04系统安装

    Ubuntu镜像下载链接:http://mirrors.aliyun.com/ubuntu-releases/18.04/ 1.登录戴尔管理口 2.点击虚拟控制台 3.选择镜像 4.挂载镜像 5.选择 ...

  4. Ionic学习记录

    1.跨域问题 浏览器中的运行 当你运行 ionic serve 时发生了什么呢? 启动了一个本地 web 服务器 你的浏览器打开并定位到本地服务器地址 这让你看着你的应用加载到你电脑上一个浏览器里,地 ...

  5. qq获取验证码接口

    测试 获取验证码 import smtplib from email.mime.text import MIMEText from email.utils import formataddr #定义参 ...

  6. JavaSE18-字节缓冲流&字符流

    1.字节缓冲流 1.1 字节缓冲流构造方法 字节缓冲流介绍 BufferOutputStream:该类实现缓冲输出流. 通过设置这样的输出流,应用程序可以向底层输出流写 入字节,而不必为写入的每个字节 ...

  7. vue 表单基本 表单修饰符

    表单的基础 利用v-model进行双向数据绑定: 1.在下拉列表中,将v-model写在select中 2.单选框和复选框需要每个按钮都需要写上v-model 3.v-model在输入框中获取得是输入 ...

  8. SQL注入基本知识点总结

    SQL注入基本知识 information_schema    MySQL 中存储所有数据库名.所有表名.所有字段名的系统数据库叫 information_schema ,这是在 MySQL 数据库初 ...

  9. 会Python了不起吗?是的,简直开挂

    前段时间听说了一件事,彻底刷新了我对"黑科技"的认知. 有一个小学弟,大学4年混得风生水起,恋爱.赚钱.写论文.找工作,样样都很顺利,简直是妥妥的人生赢家. 问他凭什么?张口就是: ...

  10. 【实时渲染】实时3D渲染如何加速汽车线上体验应用推广

    在过去,一支优秀的广告片足以让消费者对一辆汽车产生兴趣.完美的底盘线条或引擎的轰鸣声便会让潜在买家跑到经销商那里试驾.现在,广告还是和往常一样,并没有失去其特性,但86%的买家在与销售交流之前会在网上 ...