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/
mmal商城商品模块总结的更多相关文章
- ssh整合问题总结--在添加商品模块实现图片(文件)的上传
今天在做毕设(基于SSH的网上商城项目)中碰到了一个文件上传的需求,就是在后台管理员的商品模块中,有一个添加商品,需要将磁盘上的图片上传到tomcat保存图片的指定目录中: 完成这个功能需要两个步,第 ...
- SSH网上商城---商品详情页的制作
在前面的博文中,小编分别简单的介绍了邮件的发送以及邮件的激活,逛淘宝的小伙伴都有这样的体会,比如在搜索框中输入连衣裙这个商品的时候,会出现多种多样各种款式的连衣裙,连衣裙的信息包括价格,多少人购买,商 ...
- Mvp快速搭建商城购物车模块
代码地址如下:http://www.demodashi.com/demo/12834.html 前言: 说到MVP的时候其实大家都不陌生,但是涉及到实际项目中使用,还是有些无从下手.因此这里小编带着大 ...
- Scrapy实战篇(八)之Scrapy对接selenium爬取京东商城商品数据
本篇目标:我们以爬取京东商城商品数据为例,展示Scrapy框架对接selenium爬取京东商城商品数据. 背景: 京东商城页面为js动态加载页面,直接使用request请求,无法得到我们想要的商品数据 ...
- ASP.NET MVC中商品模块小样
在前面的几篇文章中,已经在控制台和界面实现了属性值的笛卡尔乘积,这是商品模块中的一个难点.本篇就来实现在ASP.NET MVC4下商品模块的一个小样.与本篇相关的文章包括: 1.ASP.NET MVC ...
- 电子商务(电销)平台中商品模块(Product)数据库设计明细
以下是自己在电子商务系统设计中的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 商品表 (product)|-- 自动编号 (product_id)|-- 商品名称 (produc ...
- 电子商务(电销)平台中商品模块(Product)数据库设计明细(转载)
电子商务(电销)平台中商品模块(Product)数据库设计明细 以下是自己在电子商务系统设计中的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 商品表 (product)|-- 自 ...
- Python网络爬虫——京东商城商品列表
Python_网络爬虫--京东商城商品列表 最近在拓展自己知识面,想学习一下其他的编程语言,处于多方的考虑最终选择了Python,Python从发布之初就以庞大的用户集群占据了编程的一席之地,pyth ...
- MySQL-THINKPHP 商城系统一 商品模块的设计
在此之前,先了解下关于SPU及SKU的知识 SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗点讲,属性值.特性相同的商品就可以称为一个SPU. ...
随机推荐
- nginx 静态化合集(去掉index.php目录)
访问某域名时,去掉index.php目录时达到效果一样 如:www.test1/index.php/test2跟www.test1/test2效果一致 在vhosts.conf中加重写就可以了 loc ...
- IO 的五种模型是什么
目录 前言 用户空间和内核空间 IO 五种模型 阻塞型 IO 非阻塞 IO IO 多路复用 信号驱动 IO 异步 IO 总结 阻塞和非阻塞 同步与异步 前言 我们经常看到阻塞/非阻塞,同步/异步这两组 ...
- vue+ springboot 分页(两种方式:sql分页 & PageHelper 分页)
方法一:sql分页 思路:使用数据库进行分页 前端使用element-ui的分页组件,往后台传第几页的起始行offest 以及每页多少行pageSize,后台根据起始行数和每页的行数可以算出该页的 ...
- 用DirectX12实现Blinn Phong
这次我们来用DirectX12实现一下基本的Blinn Phong光照模型.让我们再把这个光照模型的概念过一遍:一个物体的颜色由三个因素决定:ambient, diffuse, specular.am ...
- 工具-Git与GitHub-GitHub使用(99.5.3)
@ 目录 1.在github中添加公钥 2.克隆项目 3.在本地工作区新建分支,修改文件并提交 4.推送到远程仓库 5.从远程分支上拉取代码 关于作者 1.在github中添加公钥 首次使用git必须 ...
- 跟我一起学Redis之高可用从主从复制开始
前言 现在遇到高并发场景时,缓存技术应该算是性能优化的第一步,缓解数据库压力的同时还能提高访问效率,而Redis应该是绝大多数应用场景的首选.但是尽快Redis性能再优秀,在当今高并发场景下,一台服务 ...
- Java基础进阶:内部类lambda重点摘要,详细讲解成员内部类,局部内部类,匿名内部类,Lambda表达式,Lambda表达式和匿名内部类的区别,附重难点,代码实现源码,课堂笔记,课后扩展及答案
内部类lambda重点摘要 内部类特点: 内部类可以直接访问外部类,包括私有 外部类访问内部类必须创建对象 创建内部对象格式: 外部类.内部类 对象名=new外部类().new内部类(); 静态内部类 ...
- 解决uiautomator截取不到手机App界面信息
今天在使用uiautomatorviewer进行安卓app控件定位的时候,出现以下异常,(用的是真机测试Android版本是10,据说是Android 8以后sdk自带的uiautomator直接打开 ...
- 分布式事务MSDTC使用时,需要的配置
服务器最终配置 DTC服务 组件 防火墙 这里,跟下面的解决方案有点差异,在添加2个规则之后,原本就有分布式相关的规则,也给开启了. 网上的解决办法 在服务里打开 Distributed Transa ...
- Java“微服务”还能这么玩!
"微服务"加个引号是因为这不是传统定义的微服务架构,顶多算是"小服务"架构,因为服务实例由集群节点统一加载,非独立部署.下面以图说明一下服务调用流程. 一. ...