电商系统_day02

 

课程计划

1.1 目标

需求:完成商品添加业务。

第一步:理解商品模块的业务(通过ER图理解)

 考核的知识点,通过数据库结构快速生成ER图。同自己的理解画好关系。

 问题:为什么数据库表不建外键约束?

答:外键约束确保了数据的完整性,但是也约束数据的灵活性。如果将外键在数据里创建,不适合需求多变的项目。

 

第二步:查询商品类目(以树形结构显示,UI设计的要求)

 考核的知识点,就是如何封装一个树状的数据结构。

 

第三步:实现图片的上传(要求:上传到指定的FTP服务器)

  考核的知识点:

(1)Linux系统的使用

(2)tengine HTTPweb服务器

(3)SpringMVC的上传功能

(4)FTP的数据传到

第四步:设置类目的参数规格模板

  考核的知识点:JSON数据格式转换。

第五步:商品的保存

    考核的知识点:使用MybatisPlus插入数据

 

 

1.2 功能分析

1.2.1 相关数据表

说明:与商品模块有关的表,总共有5张。关系如下:

 

 

 

1.2.2 实现的思路

1)每个商品都有一个分类,所以要实现商品类目选择功能。

2)商品有一个图片属性,所以要实现图片上传的功能。

3)每个商品都有规格参数,所以要实现商品规格参数编辑功能。

4)将商品的规格参数、商品详情、商品信息分别保存到三张表中。

 

第一部分:实现商品类目选择功能

2.1 需求分析

在商品页面,点击选择类目按钮生成商品类目异步树

 

 

 

对应的数据库表为tb_item_cat,表结构为:

 

 

 

实现的思路:

业务理解:在加载树控件的时候,将所有顶级的类目显示出来。所有的子节点在展开的时候传入节点对应的类目编号(ID),查询对应的类目数据。

根据业务理解:

1)加载树控件。(本项目使用的是easyui-tree插件第一次传递的cid=0

2)确定异步树请求的参数及返回的节点结构。要构建easyui-tree对应的业务模型VOidtextstatus

3)请求数据库,生成树结构。(根据parent_id字段查询子节点实现。)

2.2 实现步骤

2.2.1 第一步:加载树控件

1)定义类目选择的按钮。(点击按钮,加载异步树控件)

 

 

 

2)加载异步树控件

 

 

 

查看EasyUIAPI文档我们知道url是请求路径。

 

 

 

2.2.2 第二步确定加载树请求的参数

查看API文档,我们知道请求的参数名是id,是当前节点的id值。

 

 

 

2.2.3 第三步:确定树节点结构

查看API文档,节点包括idtextstate三个基本属性。

 

 

 

2.2.4 第四步java代码实现异步树

2.2.4.1 Step1:代码结构

Controller负责从页面接收节点的id,返回该节点的所有子节点;

    Service:实现查询逻辑,根据父节点id,查询所有的子节点

Mapper:基于BASEMapper实现

 

2.2.4.2 Step2:请求响应格式

请求路径

/item/cat/list

请求参数

id=nodeId(首次加载生成一级目录时,默认id=0

响应格式

{“id”:”1”  “text”:”node1”  “state”:”open}

 

2.2.4.3 Step3:创建EUTreeNode

ego-base工程中创建。

//自定义异步树节点结构

public class EUTreeNode {

private long id;

private String text;

private String state;

     //补全getset方法

}

 

2.2.4.4 Step4:创建ItemCat

--ego-base中创建

@TableName(value="tb_item_cat")

public class ItemCat {

 

@TableId(value="id",type=IdType.AUTO)

private Long id;

 

@TableField(value="parent_id")

private Long parentId;

 

private String name;

 

private int status;

@TableField(value="sort_order")

private int sortOrder;

 

@TableField(value="is_parent")

private byte isParent;

 

private Date created;

 

private Date updated;

 

public ItemCat() {

super();

 

}

 

public Long getId() {

return id;

}

 

// 补全getset方法

 

}

 

 

2.2.4.5 Step5:创建ItemCatMapper接口

--ego-base中创建

package cn.gzsxt.base.mapper;

 

import com.baomidou.mybatisplus.mapper.BaseMapper;

 

import cn.gzsxt.base.pojo.ItemCat;

 

public interface ItemCatMapper extends BaseMapper<ItemCat>{

 

}

 

 

 

2.2.4.6 Step6:创建ItemCatService接口及实现类

ego-manager项目中创建

package cn.gzsxt.manager.service.impl;

 

import java.util.ArrayList;

import java.util.List;

 

import org.springframework.stereotype.Service;

 

import com.baomidou.mybatisplus.mapper.EntityWrapper;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

 

import cn.gzsxt.base.mapper.ItemCatMapper;

import cn.gzsxt.base.pojo.ItemCat;

import cn.gzsxt.base.vo.EUTreeNode;

import cn.gzsxt.manager.service.ItemCatService;

 

@Service

public class ItemCatServiceImpl extends ServiceImpl<ItemCatMapper, ItemCat> implements ItemCatService{

 

@Override

public List<EUTreeNode> getByParentId(Long parentId) {

 

List<EUTreeNode> nodes = new ArrayList<>();

 

EntityWrapper<ItemCat> ew = new EntityWrapper<>();

ew.eq("parent_id", parentId);

 

List<ItemCat> selectList = selectList(ew);

 

EUTreeNode node = null;

 

for (ItemCat itemCat : selectList) {

node = new EUTreeNode();

 

node.setId(itemCat.getId());

node.setText(itemCat.getName());

 

if(1==itemCat.getIsParent()){

 

node.setState("closed");

}else{

node.setState("open");

}

 

nodes.add(node);

}

 

return nodes;

}

}

 

2.2.4.7 Step7:创建ItemCatController

package cn.gzsxt.manager.controller;

 

import java.util.List;

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.RequestParam;

import org.springframework.web.bind.annotation.ResponseBody;

import cn.gzsxt.common.pojo.EUTreeNode;

import cn.gzsxt.manager.service.ItemCatService;

 

@Controller

@RequestMapping("/item/cat")

public class ItemCatController {

@Autowired

private ItemCatService catService;

 

@RequestMapping(value="/list")

@ResponseBody

public List<EUTreeNode> initTreeByParentId(@RequestParam(defaultValue="0")Long id){

List<EUTreeNode> list = catService.getByParantId(id);

 

return list;

}

}

 

2.3 保存类目id到页面表单

说明:当点击叶子节点时,将该节点的id值,保存到页面表单中。

 

 

 

类目id的值,保存在页面表单的位置:

 

 

 

第二部分实现商品图片上传功能

3.1 传统上传方式的问题

在传统上传方式中,在项目的跟目录下创建upload目录,将图片上传到tomcat服务器中。

 

 

 

但是在分布式环境下,是有多个Tomcat存在的,当把图片直接上传到Tomcat服务器时,容易出现图片丢失的问题。

 

 

3.2 分布式系统图片上传方案

3.2.1 思路分析

直接将图片上传到一个指定的目录,访问、下载图片都访问这个目录。

 

由于项目最终是要部署到Linux环境,所以直接将图片上传到Linux服务器。

 

 

问题:那如何将图片上传到Linux呢?

答:使用vsftpd组件,实现文件传输。

 

3.2.2 vsftpd简介

问题1vsftpd是什么?

答:ftpFile Transfer Protocol)文件传输协议。(实现不同操作系统之间文件的传输)

vsftpd是一个基于ftp协议的文件传输服务器软件。

 

问题2vsftpd作用是什么?

答:传输文件的文件服务器。(跨平台、跨操作系统)

 

问题3:如何使用?

答:服务端:在linux安装vsftpd软件,开启服务。

    客户端:通过FtpClient客户端建立和服务器的连接,向服务器发送请求。

 

3.3 实现步骤说明

1)在Linux上安装vsftpd服务。

2)根据图片的地址访问图片。(最终保存到数据库的是图片的路径)

3web工程中实现图片上传。

3.4 实现步骤

3.4.1 第一部分:在Linux上部署vsftpd服务

思路 :(1)安装软件

2)测试服务是否可用

 

3.4.1.1 第一步:安装vsftpd软件

[root@node0719 ~]# yum -y install vsftpd

 

3.4.1.2 第二步:关闭匿名访问

修改vsftpd配置文件   vim /etc/vsftpd/vsftpd.conf

 

 

 

3.4.1.3 第三步:添加一个FTP用户

创建一个用户,专门用来访问vsftpd服务。

[root@node0719 ~]# useradd ftpuser

[root@node0719 ~]# passwd ftpuser

 

3.4.1.4 第四步:设置防火墙

vsftpd服务默认端口号为21,修改防火墙,开放此端口,重启防火墙。

[root@node0719 ~]# vim /etc/sysconfig/iptables

[root@node0719 ~]# service iptables restart

 

3.4.1.5 第五步:修改selinuxLinux安全内核系统)

1)先查看selinux,默认是禁用了ftp访问的。

[root@bogon ~]# getsebool -a | grep ftp  

allow_ftpd_anon_write --> off

allow_ftpd_full_access --> off

allow_ftpd_use_cifs --> off

allow_ftpd_use_nfs --> off

ftp_home_dir --> off

ftpd_connect_db --> off

ftpd_use_passive_mode --> off

httpd_enable_ftp_server --> off

tftp_anon_write --> off

 

2)修改selinux,开放ftp访问权限

[root@bogon ~]# setsebool -P allow_ftpd_full_access on

[root@bogon ~]# setsebool -P ftp_home_dir on

 

3.4.1.6 第六步:启动vsftpd服务

[root@node0719 vsftpd]# service vsftpd start

vsftpd 启动 vsftpd:                                    [确定]

 

3.4.1.7 通过浏览器访问测试

访问地址:ftp://192.168.23.12:21发现无法访问

 

原因:被动模式下,数据传输服务被防火墙拦截了。

 

1)被动模式

第二次请求过程中,客户端跟服务端建立数据通道;

服务端被动将数据响应给客户端。

第二次请求数据传输,会随机生成一个服务端口。被防火墙禁用。

 

 

 

2)主动模式

服务端主动向客户端发送数据,会被客户端的防火墙禁掉。

多数客户端不支持主动模式,不安全。

 

 

 

 

3.4.1.8 第八步:配置被动模式

1)编辑/etc/vsftpd/vsftpd.conf文件

[root@bogon ~]# vim /etc/vsftpd/vsftpd.conf

 

2)添加防火墙范围设置(在文件尾部添加即可):

pasv_min_port=30000

pasv_max_port=30999

 

3)修改防火墙,开启30000:30999之间所有的端口。(30000:30999不行用30000-30999 tcp)

4)重启防火墙。 service iptables restart

5)重启vsftpd服务 service vsftpd restart

 

 

 

 

再次访问浏览器,发现可以正常连接了。

 

 

 

3.4.1.9 java代码测试上传功能

Java代码中,是通过FtpClient客户端建立和服务端的连接的。在ego-base工程中测试。

1)在ego-base中添加ftp服务的依赖。

<dependency>

<groupId>commons-net</groupId>

<artifactId>commons-net</artifactId>

</dependency>

 

2)创建测试类

说明:使用ftpuser用户上传。指定上从目录/home/ftpuser/ego/images

注意:为了保证ftpuser有这个目录下的写权限,我们要用ftpuser用户创建这个目录。

su命令:切换用户

[root@node0719 ~]#su ftpuser

[ftpuser@node0719 ~]#mkdir -p /home/ftpuser/ego/images

 

测试类TestFtp

package cn.gzsxt.manager.test;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.net.SocketException;

 

import org.apache.commons.net.ftp.FTP;

import org.apache.commons.net.ftp.FTPClient;

 

public class TestFtp {

 

static String baseUrl = "/home/ftpuser/ego/images";

public static void main(String[] args) {

//1、建立和服务端的连接

FTPClient client = new FTPClient();

try {

client.connect("192.168.23.12", 21);

//2、身份认证

client.login("ftpuser", "ftpuser");

//3、指定源文件

File file = new File("F:\\图片\\5b7a8115N89613314.jpg");

InputStream local = new FileInputStream(file);

//4、指定文件上传的方式   二进制字节码

client.setFileType(FTP.BINARY_FILE_TYPE);

//5、指定上传目录  默认是/home/ftpuser,即ftpuser用户的家目录

// 切换到ftpuser用户来创建目录。     /home/ftpuser/ego/images/

client.changeWorkingDirectory("/home/ftpuser/ego/images");

//6、设置文件上传的模式,指定为被动模式

client.enterLocalPassiveMode();

 

boolean flag = client.storeFile("test.jpg", local);

if(flag){

System.out.println("上传成功");

}else{

System.out.println("上传失败");

}

} catch (SocketException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

 

3.4.1.10 封装FTPUtils工具类

package cn.gzsxt.base.utils;

 

import java.io.IOException;

import java.io.InputStream;

 

import org.apache.commons.net.ftp.FTP;

import org.apache.commons.net.ftp.FTPClient;

 

public class FtpUtils {

 

FTPClient client = null;

 

/**

 * 文件上传

 * @param hostName   ftp主机名

 * @param port       ftp主机端口

 * @param username   上传用户名

 * @param password   上传用户密码

 * @param basePath   上传基础路径

 * @param filePath   文件存放路径

 * @param remoteFileName  上传后文件名称

 * @param in         文件输入流

 * @return

 */

public static boolean upload(String hostName,int port,String username,String password,String basePath,

String filePath,String remoteFileName,InputStream in){

 

//1、创建客户端

FTPClient client = new FTPClient();

 

try {

 

//2、建立和服务端的链接

client.connect(hostName, port);

 

//3、登陆服务端

client.login(username, password);

 

//4、指定图片上传的方式为二进制,即字节流

client.setFileType(FTP.BINARY_FILE_TYPE);

 

//5、指定上传的访问模式为被动模式    说明:大部分的操作系统,默认的都是被动模式,并且禁用了主动了模式

client.enterLocalPassiveMode();

 

//6、指定上传的目录     默认目录 是当前ftpuser用户的家目录   

boolean flag = client.changeWorkingDirectory(basePath+filePath);

 

//如果切换目录失败,则创建指定的目录

if(!flag){

 

//创建目录失败,则可能是存在没有创建的父目录

if(!client.makeDirectory(basePath+filePath)){

String tempPath = basePath;

 

String[] split = filePath.split("/");

for (String string : split) {

if(null!=string && !"".equals(string)){

tempPath = tempPath+"/"+string;

 

//先判断第一层路径是否存在,如果不存在,则创建

if(!client.changeWorkingDirectory(tempPath)){

//如果创建第一层路径成功,则判断是否能切换到这一层路径

if(client.makeDirectory(tempPath)){

//切换失败,则返回false

if(!client.changeWorkingDirectory(tempPath)){

return false;

}

//如果创建第一层路径失败,则直接返回false

}else{

 

return false;

}

}

 

//如果有空路径,则直接跳过

}else{

continue;

}

}

}else{

//创建成功,则直接切换到指定的目录

if(!client.changeWorkingDirectory(basePath+filePath)){

return false;

}

}

 

}

 

//8、上传

boolean result = client.storeFile(remoteFileName, in);

 

return result;

 

 

} catch (Exception e) {

 

e.printStackTrace();

 

return false;

}finally {

//9,退出登录,并关闭连接

try {

if(client.logout()){

client.disconnect();

 

}

} catch (IOException e) {

e.printStackTrace();

}

}

 

}

}

 

 

 

3.4.2 第二部分:搭建图片服务器访问图片

我们知道,图片等静态资源需要服务器加载,才能被访问到。

这里我们选择Tengine做服务器,来加载图片。

 

问题1Tengine是什么?

答:Tengineweb服务器。

 

问题2web服务器常用种类?

答:apacheIISnginx

 

问题3web服务器和web应用服务器的区别?

答:web应用服务器,是用来处理动态请求,常见的以tomcatjettyservlet容器为代表。可以用来部署应用。

web服务器,只能处理静态资源请求。

如果要处理动态请求,需要通过其动态代理功能实现。

 

问题3:为什么不用Tomcat呢?

答:(1Tomcatservlet容器,处理静态资源的速度远低于Tengine

2Tomcat的并发连接数,远远低于Tengine

 

所以,这里我们选择Tengine做图片服务器。

 

搭建步骤说明:

1)安装Tengine。(源码安装)

2)配置图片服务。

3.4.2.1 第一步:上传、解压

[root@node0719 ~]# tar -zxvf tengine-2.1.0.tar.gz

 

3.4.2.2 第二步:预编译

预编译作用:检查编译过程中所需要的依赖、环境。

依次安装预编译过程中,所需要的环境。(根据个人虚拟机安装所缺环境)

[root@node07192 ~]# cd tengine-2.1.0

[root@node07192 tengine-2.1.0]# ./configure

 

1)缺少c编译环境

 

[root@node07192 tengine-2.1.0]# yum -y install gcc-c++

2)缺少pcre环境

 

[root@node07192 tengine-2.1.0]# yum -y install pcre-devel

3)缺少openssl环境

 

 

[root@node07192 tengine-2.1.0]# yum install -y openssl openssl-devel

4)缺少zlib环境

[root@node07192 tengine-2.1.0]# yum install -y zlib zlib-devel

 

3.4.2.3 第三步:编译

[root@node07192 tengine-2.1.0]# make

 

3.4.2.4 第四步:安装

默认安装路径/usr/local/nginx/

[root@node07192 tengine-2.1.0]# make install

 

3.4.2.5 第五步:启动Tengine服务器

[root@node07192 tengine-2.1.0]# cd /usr/local/nginx/sbin/

[root@node07192 sbin]# ./nginx

 

3.4.2.6 第六步访问测试

1)查看配置文件。默认服务端口是80

[root@node07192 sbin]# cd ../conf

[root@node07192 conf]# vim nginx.conf

 

 

2)修改防火墙,开发80端口。重启防火墙

[root@node07192 conf]# vim /etc/sysconfig/iptables

[root@node07192 conf]# service iptables restart

 

3)浏览器访问地址 http://192.168.23.12:80

 

 

3.4.2.7 第七步:配置图片服务

1)修改/conf/nginx.conf文件。指定图片根路径和服务端口

 

 

 

2)重启tengine服务器

[root@node07192 sbin]# ./nginx -s reload

 

3)浏览器访问图片

注意:服务器加载的根路径是/home/ftpuser/ego

所以浏览器中访问图片的目录为/images/+图片名称.jpg

 

 

 

4)解决访问图片的权限问题

在第六步中,我们访问的页面是/html/index.html

所以:我们只需要将图片的权限修改为index.html一致即可。

 

查看/html/index.html的权限

 

 

 

修改ftpuser目录的权限为可读、可执行

[root@node07192 nginx]# chmod 705 /home/ftpuser

5)重新访问图片,成功!!!

 

图片访问路径说明:

图片真实目录时  /home/ftpuser/ego/images

Tengine中,设置得图片资源的根目录为  /home/ftpuser/ego

 

意味着,我们每次请求图片的时候,是直接到/home/ftpuser/ego这个目录下,找图片的。因此图片的访问路径中,/home/ftpuser/ego这个路径是要省掉的。

 

 

 

3.4.3 第三部分:SpringMVC实现上传

3.4.3.1 思路

1)使用Springmvc上传组件,从页面表单接收图片

2)使用vsftpd组件,将图片上传到Linux服务器。

     a)、服务端:在Linux上安装ftp服务端vsftpd软件,并开启服务。

     b)、客户端:在java代码中使用FtpClient客户端建立与服务器的连接

3)返回值:返回图片上传之后的访问路径。

为什么?

因为保存图片到数据库的时候,保存的就是图片的访问路径。

 

3.4.3.2 前端js实现

前端使用kindeditor初始化上传组件

 

 

 

调用上传组件的初始化方法:

 

 

 

上传组件在common.js中定义

 

 

 

上传组件的初始化方法init

 

 

 

3.4.3.3 后台java实现
3.4.3.3.1 代码结构

Controller从表单接收图片返回图片的回调地址

Service:创建FtpClient客户端,将图片直接上传到Linux服务器

 

3.4.3.3.2 请求响应格式

请求路径

/pic/upload

请求方式

Post

请求参数

uploadFile

返回值结构

参考Kindeditor官方文档(http://kindeditor.net/docs/upload.html)

 

Kindeditor官方文档要求的返回格式类型

 

 

 

3.4.3.3.3 定义返回值类型

ego-base工程中定义。

package cn.gzsxt.base.pojo;

 

/**

 * KindEditer文件上传返回格式

 * @author ccnulyq

 *

 */

public class UploadResult {

 

private int error;   //0 表示成功   1表示失败

 

private String url;   //成功时,图片的访问地址

 

private String message;  //失败时,错误信息

 

public PictureResult() {

super();

}

//补充getset方法

}

 

3.4.3.3.4 ego-manager工程中添加Springmvc上传组件及Pom依赖

1)、修改spring-mvc.xml,添加上传组件

<!-- 定义文件上传解析器 -->

<bean name="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

<!-- 设定默认编码 -->

<property name="defaultEncoding" value="UTF-8"></property>

<!-- 设定文件上传的最大值5MB5*1024*1024 -->

<property name="maxUploadSize" value="5242880"></property>

</bean>

 

2)、修改pom.xml,添加上传依赖common-fileupload.jar

<!-- 文件上传组件 -->

<dependency>

<groupId>commons-fileupload</groupId>

<artifactId>commons-fileupload</artifactId>

</dependency>

 

3)将vsftpd服务端请求参数写到properties配置文件中

#图片上传基本配置

FTP_HOST=192.168.4.253

FTP_PORT=21

FTP_USER=ftpuser

FTP_PASSWD=ftpuser

FTP_BASE_URL=/home/ftpuser/ego/images

PICTURE_BASE_URL=http://192.168.4.253/images

 

 

3.4.3.3.5 Service层代码实现

--创建UploadService接口及其实现类

package cn.gzsxt.manager.service.impl;

 

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

 

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Service;

import org.springframework.web.multipart.MultipartFile;

 

import cn.gzsxt.base.utils.FtpUtils;

import cn.gzsxt.base.utils.IDUtils;

import cn.gzsxt.base.vo.UploadResult;

import cn.gzsxt.manager.service.UploadService;

 

@Service

public class UploadServiceImpl implements UploadService{

 

/*

 * FTP_HOST=192.168.4.253

FTP_PORT=21

FTP_USERNAME=ftpuser

FTP_PASSWORD=ftpuser

FTP_BASE_URL=/home/ftpuser/ego/images

PICTURE_BASE_URL=http://192.168.4.253/images

 */

@Value("${FTP_HOST}")

private String FTP_HOST;

 

@Value("${FTP_PORT}")

private Integer FTP_PORT;

 

@Value("${FTP_USER}")

private String FTP_USERNAME;

 

@Value("${FTP_PASSWD}")

private String FTP_PASSWORD;

 

@Value("${FTP_BASE_URL}")

private String FTP_BASE_URL;

 

@Value("${PICTURE_BASE_URL}")

private String PICTURE_BASE_URL;

 

@Override

public UploadResult upload(MultipartFile file) {

 

UploadResult result = new UploadResult();

 

//需求:将上传的图片按日期来分类    /2019/02/25/1.jpg    

 

Date date = new Date();

 

//获取日期的目录格式

String filePath = "/"+ new SimpleDateFormat("yyyy").format(date)+

      "/"+new SimpleDateFormat("MM").format(date)+

      "/"+new SimpleDateFormat("dd").format(date);

 

//获取图片的类型   .jpg   .png

String originalFilename = file.getOriginalFilename();

 

String filtType = originalFilename.substring(originalFilename.lastIndexOf("."));

 

String remoteFileName = IDUtils.getImageName()+filtType;

 

try {

boolean upload = FtpUtils.upload(FTP_HOST, FTP_PORT, FTP_USERNAME, FTP_PASSWORD, FTP_BASE_URL, filePath, remoteFileName, file.getInputStream());

 

if(upload){

result.setError(0);

//   192.168.4.253/images     /2019/02/25    /   111111.jpg

result.setUrl(PICTURE_BASE_URL+filePath+"/"+remoteFileName);

}else{

result.setError(1);

result.setMessage("上传失败,请稍后再试!");

}

 

} catch (IOException e) {

 

e.printStackTrace();

 

result.setError(1);

result.setMessage("上传失败,请稍后再试!");

}

 

return result;

}

 

}

 

3.4.3.3.6 ID生成工具类

package org.ranger.base.utils;

 

import java.util.Random;

 

/**

 * 各种id生成策略

 */

public class IDUtils {

 

/**

 * 图片名生成

 */

public static String getImageName() {

//取当前时间的长整形值包含毫秒

long millis = System.currentTimeMillis();

//long millis = System.nanoTime();

//加上三位随机数

Random random = new Random();

int end3 = random.nextInt(999);

//如果不足三位前面补0

String str = millis + String.format("%03d", end3);

 

return str;

}

 

/**

 * 商品id生成

 */

public static long getItemId() {

//取当前时间的长整形值包含毫秒

long millis = System.currentTimeMillis();

//long millis = System.nanoTime();

//加上两位随机数

Random random = new Random();

int end2 = random.nextInt(99);

//如果不足两位前面补0

String str = millis + String.format("%02d", end2);

long id = new Long(str);

return id;

}

}

 

 

3.4.3.3.7 Controller层代码实现

--创建UploadController

package cn.gzsxt.manager.controller;

 

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.ResponseBody;

import org.springframework.web.multipart.MultipartFile;

 

import cn.gzsxt.base.vo.UploadResult;

import cn.gzsxt.manager.service.UploadService;

 

@Controller

public class UploadController {

 

@Autowired

private UploadService uploadService;

 

@RequestMapping(value="/pic/upload",method=RequestMethod.POST)

@ResponseBody

public UploadResult upload(MultipartFile uploadFile){

UploadResult result = uploadService.upload(uploadFile);

 

return result;

}

}

 

 

3.4.3.3.8 测试结果,上传成功!!!

 

 

 

3.4.3.4 将上传结果保存到页面表单域

 

 

页面效果

 

 

 

 

第三部分:kindEditor编辑商品属性

纯前端js实现不需要java后台代码支持

原理:内置了一个HTML编辑器,将HTML页面转换成文本类型,将值传给指定的元素。

 

 

 

 

第四部分:商品规格参数

 

 

 

 

5.1 格式

规格分组1

  |-规格项1:规格值1

|-规格项2:规格值2

|-规格项n:规格值n

 

规格分组2

  |-规格项11:规格值11

|-规格项22:规格值22

|-规格项nn:规格值nn

 

规格分组3

  |-规格项112:规格值112

|-规格项222:规格值222

|-规格项nnn:规格值nnn

 

5.2 特点

1)每一类商品的规格分组是相同的。

2)每一个规格分组对应多个规格项。

3)每一个商品的规格值不同。

5.3 设计思路

 

 

 

1)给商品的每一个分类创建一个规格参数模板。(tb_item_param

2)添加商品的时候,根据该类商品的参数模板,填写规格值。

3)将页面填写的规格值,保存到数据库。(tb_item_param_item

5.4 实现流程

1)添加商品规格参数模板

2)根据规格参数模板生成规格值

 

5.4.1 第一部分:创建规格参数模板

5.4.1.1 第一步:判断是否已经添加规格参数模板

1js实现

 

 

 

2)请求响应格式

请求路径

/item/param/query/itemcatid/{itemCatId}

请求方式

GET

请求参数

/{itemCatId} 路径变量,商品类目id

响应结果

{status:200 data:data}

 

3)创建ItemParam

package cn.gzsxt.base.pojo;

 

import java.util.Date;

 

import com.baomidou.mybatisplus.annotations.TableField;

import com.baomidou.mybatisplus.annotations.TableId;

import com.baomidou.mybatisplus.annotations.TableName;

import com.baomidou.mybatisplus.enums.IdType;

 

@TableName(value="tb_item_param")

public class ItemParam {

 

 

@TableId(value="id",type=IdType.AUTO)

private Long id;

 

@TableField(value="item_cat_id")

private long itemCatId;

 

@TableField(value="param_data")

private String paramData;

 

private Date created;

 

private Date updated;

 

public ItemParam() {

super();

}

    

//补全getset方法

}

 

 

4)创建EgoResult返回值类

--说明:在ego-base中定义,并修改pom文件,添加json依赖

package cn.gzsxt.base.vo;

 

import java.util.List;

 

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.ObjectMapper;

 

/**

 * 好易购商城自定义响应结构

 */

public class EgoResult {

 

    // 定义jackson对象

    private static final ObjectMapper MAPPER = new ObjectMapper();

 

    // 响应业务状态

    private Integer status;

 

    // 响应消息

    private String msg;

 

    // 响应中的数据

    private Object data;

 

    public static EgoResult build(Integer status, String msg, Object data) {

        return new EgoResult(status, msg, data);

    }

 

    public static EgoResult ok(Object data) {

        return new EgoResult(data);

    }

 

    public static EgoResult ok() {

        return new EgoResult(null);

    }

 

    public EgoResult() {

 

    }

 

    public static EgoResult build(Integer status, String msg) {

        return new EgoResult(status, msg, null);

    }

 

    public EgoResult(Integer status, String msg, Object data) {

        this.status = status;

        this.msg = msg;

        this.data = data;

    }

 

    public EgoResult(Object data) {

        this.status = 200;

        this.msg = "OK";

        this.data = data;

    }

 

//    public Boolean isOK() {

//        return this.status == 200;

//    }

 

    public Integer getStatus() {

        return status;

    }

 

    public void setStatus(Integer status) {

        this.status = status;

    }

 

    public String getMsg() {

        return msg;

    }

 

    public void setMsg(String msg) {

        this.msg = msg;

    }

 

    public Object getData() {

        return data;

    }

 

    public void setData(Object data) {

        this.data = data;

    }

 

    /**

     * json结果集转化为EgoResult对象

     *

     * @param jsonData json数据

     * @param clazz EgoResult中的object类型

     * @return

     */

    public static EgoResult formatToPojo(String jsonData, Class<?> clazz) {

        try {

            if (clazz == null) {

                return MAPPER.readValue(jsonData, EgoResult.class);

            }

            JsonNode jsonNode = MAPPER.readTree(jsonData);

            JsonNode data = jsonNode.get("data");

            Object obj = null;

            if (clazz != null) {

                if (data.isObject()) {

                    obj = MAPPER.readValue(data.traverse(), clazz);

                } else if (data.isTextual()) {

                    obj = MAPPER.readValue(data.asText(), clazz);

                }

            }

            return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);

        } catch (Exception e) {

            return null;

        }

    }

 

    /**

     * 没有object对象的转化

     *

     * @param json

     * @return

     */

    public static EgoResult format(String json) {

        try {

            return MAPPER.readValue(json, EgoResult.class);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;

    }

 

    /**

     * Object是集合转化

     *

     * @param jsonData json数据

     * @param clazz 集合中的类型

     * @return

     */

    public static EgoResult formatToList(String jsonData, Class<?> clazz) {

        try {

            JsonNode jsonNode = MAPPER.readTree(jsonData);

            JsonNode data = jsonNode.get("data");

            Object obj = null;

            if (data.isArray() && data.size() > 0) {

                obj = MAPPER.readValue(data.traverse(),

                        MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));

            }

            return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);

        } catch (Exception e) {

            return null;

        }

    }

}

 

 

5)创建ItemParamMapper接口

--说明:在ego-base中创建

package cn.gzsxt.base.mapper;

 

import java.util.List;

import java.util.Map;

 

import org.apache.ibatis.annotations.Param;

import org.apache.ibatis.annotations.Select;

 

import com.baomidou.mybatisplus.mapper.BaseMapper;

 

import cn.gzsxt.base.pojo.ItemParam;

 

public interface ItemParamMapper extends BaseMapper<ItemParam>{

 

 

}

 

 

6Service层实现

--创建ItemParamService接口及其实现类

package cn.gzsxt.manager.service.impl;

 

import java.util.Date;

import java.util.List;

import java.util.Map;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

 

import com.baomidou.mybatisplus.mapper.EntityWrapper;

 

import cn.gzsxt.base.mapper.ItemParamMapper;

import cn.gzsxt.base.pojo.ItemParam;

import cn.gzsxt.base.vo.EUDataGridResult;

import cn.gzsxt.base.vo.EgoResult;

import cn.gzsxt.manager.service.ItemParamService;

 

@Service

public class ItemParamServiceImpl implements ItemParamService{

 

@Autowired

private ItemParamMapper itemParamMapper;

 

@Override

public EgoResult getByItemCatId(long catId) {

 

EntityWrapper<ItemParam> ew = new EntityWrapper<>();

 

ew.eq("item_cat_id", catId);

 

List<ItemParam> selectList = itemParamMapper.selectList(ew);

 

if(null!=selectList && selectList.size()>0){

 

return EgoResult.ok(selectList.get(0));

}

 

return EgoResult.build(400, "没有查到该类商品的模板");

}

}

 

4Controller层实现

--创建ItemParamController

package cn.gzsxt.manager.controller;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import cn.gzsxt.base.vo.EUDataGridResult;

import cn.gzsxt.base.vo.EgoResult;

import cn.gzsxt.manager.service.ItemParamService;

 

@Controller

@RequestMapping("/item/param")

public class ItemParamController {

 

@Autowired

private ItemParamService service;

 

@RequestMapping("/query/itemcatid/{itemcatid}")

@ResponseBody

public EgoResult selectByCatId(@PathVariable("itemcatid")Long itemCatId){

EgoResult result = service.getByItemCatId(itemCatId);

 

return result;

}

}

 

5.4.1.2 第二步:添加规格参数模板

1)前端js实现

 

 

 

 

 

 

2)后台java代码实现

请求路径

/item/param/save/{cid}

请求方式

POST

请求参数

/{cid}  (类目id)  paramData  (json格式)

响应结果

EgoResult

 

3Service层实现

--修改ItemParamService接口及其实现类,添加保存方法

//只有配置了rollbackFor = Exception.class,在service对异常进行处理时,才会有回滚

@Transactional(rollbackFor = Exception.class)

@Override

public EgoResult save(Long itemCatId, String paramData) {

 

try {

ItemParam entity = new ItemParam();

entity.setItemCatId(itemCatId);

entity.setParamData(paramData);

entity.setCreated(new Date());

entity.setUpdated(entity.getCreated());

 

itemParamMapper.insert(entity);

 

return EgoResult.ok();

} catch (Exception e) {

 

e.printStackTrace();

 

return EgoResult.build(400, "保存失败");

}

}

 

4Controller层实现

--修改ItemParamController,添加保存方法

@RequestMapping("/save/{cid}")

@ResponseBody

public EgoResult saveItemParam(@PathVariable Long cid, String paramData) {

EgoResult result = itemParamService.saveItemParam(cid, paramData);

}

 

5.4.2 第二部分:根据参数模板生成商品规格参数值表单

新增商品 --> 选择类目 --> 查找类目所对应的模板 --> 生成表单

 

1)前端js实现

 

 

 

 

 

(2)java后台(已实现)

5.4.2.1 第一步:修改Controller代码

@RequestMapping("/save/{catId}")

@ResponseBody

public EgoResult save(@PathVariable("catId")Long catId,String paramData){

EgoResult result = service.save(catId, paramData);

 

return result;

}

5.4.2.2 第二步:修改Service代码

@Override

public EgoResult save(Long catId, String paramData) {

 

ItemParam param = new ItemParam();

param.setItemCatId(catId);

param.setParamData(paramData);

param.setCreated(new Date());

param.setUpdated(param.getCreated());

 

mapper.insert(param);

 

return EgoResult.ok();

}

 

 

 

 

 

第五部分:保存商品

保存商品,需要同时保存商品信息、商品的描述信息和商品的规格参数,分别对应表tb_itemtb_item_desctb_item_param_item三张表。

 

6.1 前端js实现

6.1.1 使用KindEditor富文本编辑器,编辑商品描述信息

 

 

 

6.1.2 将商品规格参数表单数据,转换成json格式

 

 

 

6.1.3 提交保存商品请求

 

 

 

6.2 后台java实现

6.2.1 请求响应格式

请求路径

/item/save

请求方式

POST

请求参数

TbItemdescitemParams

响应格式

{“status”:200   data:data}   参考http响应格式

6.2.2 代码结构

Controller从表单接收数据封装到JavaBean

Service:实现保存逻辑,防止事务一致性问题。

MapperMybatis-plus实现 

 

6.2.3 创建pojo

--ego-base工程中创建

 

1)创建ItemDesc

package cn.gzsxt.base.pojo;

 

import java.util.Date;

 

import com.baomidou.mybatisplus.annotations.TableField;

import com.baomidou.mybatisplus.annotations.TableId;

import com.baomidou.mybatisplus.annotations.TableName;

import com.baomidou.mybatisplus.enums.IdType;

 

@TableName(value="tb_item_desc")

public class ItemDesc {

 

@TableId(value="item_id",type=IdType.INPUT)

private Long itemId;

 

@TableField(value="item_desc")

private String itemDesc;

 

private Date created;

 

private Date updated;

 

public ItemDesc() {

super();

}

 

// 补全getset方法

}

 

2)创建ItemParamItem

package cn.gzsxt.base.pojo;

 

import java.util.Date;

 

import com.baomidou.mybatisplus.annotations.TableField;

import com.baomidou.mybatisplus.annotations.TableId;

import com.baomidou.mybatisplus.annotations.TableName;

 

import com.baomidou.mybatisplus.enums.IdType;

 

/**商品规格参数值表

 *

 * 商品的规格参数(商品的描述信息)

 *    做了水平拆表的处理。

 *   

 *    好处:减小商品的表的体积,让商品表查询效率更高

 *    

 * 什么情况下需要做水平拆表?

 * 1)大文本的字段。

 * 2)这个大文本的字段不常用

 *

 * @author ccnulyq

 *

 */

 

@TableName(value="tb_item_param_item")

public class ItemParamItem {

 

@TableId(value="id",type=IdType.AUTO)

private Long id;

 

@TableField(value="item_id")

private long itemId;

 

@TableField(value="param_data")

private String paramData;

 

private Date created;

 

private Date updated;

 

public ItemParamItem() {

super();

 

}

 

// 补全getset方法

}

 

 

6.2.4 创建对应的Mapper

--说明:在ego-base工程中创建

 

1)创建ItemParamItemMapper接口

package cn.gzsxt.base.mapper;

 

import com.baomidou.mybatisplus.mapper.BaseMapper;

 

import cn.gzsxt.base.pojo.ItemParamItem;

 

public interface ItemParamItemMapper extends BaseMapper<ItemParamItem>{

 

}

 

2)创建ItemDescMapper接口

package cn.gzsxt.base.mapper;

 

import com.baomidou.mybatisplus.mapper.BaseMapper;

 

import cn.gzsxt.base.pojo.ItemDesc;

 

public interface ItemDescMapper extends BaseMapper<ItemDesc>{

 

}

 

 

6.2.5 Service代码实现

--修改ItemService接口及其实现类,新增save方法

 

--注意:注入ItemDescMapperItemParamItemMapper

 

 

@Service

public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements ItemService{

 

@Autowired

private ItemDescMapper descMapper;

 

@Autowired

private ItemParamItemMapper itemParamMapper;

 

@Transactional(rollbackFor=Exception.class)

@Override

public EgoResult save(Item item, String desc, String paramData) {

 

try {

 

long itemId = IDUtils.getItemId();

 

item.setStatus((byte) 1);

item.setId(itemId);

item.setCreated(new Date());

item.setUpdated(item.getCreated());

 

this.baseMapper.insert(item);

 

//保存商品的描述信息

ItemDesc itemDesc = new ItemDesc();

itemDesc.setItemId(itemId);

itemDesc.setItemDesc(desc);

itemDesc.setCreated(item.getCreated());

itemDesc.setUpdated(item.getUpdated());

descMapper.insert(itemDesc);

 

//保存商品的规格参数值

ItemParamItem paramItem = new ItemParamItem();

paramItem.setItemId(itemId);

paramItem.setParamData(paramData);

paramItem.setCreated(item.getCreated());

paramItem.setUpdated(item.getCreated());

 

itemParamMapper.insert(paramItem);

 

return EgoResult.ok();

 

} catch (Exception e) {

e.printStackTrace();

}

 

return EgoResult.build(400, "保存失败,请稍后再试");

}

 

}

 

 

6.2.6 Controller代码实现

--修改ItemController类,新增save方法

@RequestMapping(value="/save",method=RequestMethod.POST)

@ResponseBody

public EgoResult save(Item item,String desc,String itemParams){

EgoResult result = itemService.save(item, desc, itemParams);

 

return result;

}

 

商品规格参数列表实现

7.1 思路

商品规格参数列表的数据,分别存在了tb_item_paramtb_item_cat两张表中,因此在mapper层,需要自定义查询方法,并分页

 

 

 

7.2 前端js实现

使用的是easyu-datagrid插件,使用方法参考商品列表实现(第一天内容)。

 

7.3 后台代码实现

7.3.1 确定请求响应格式

请求路径

/item/param/list

请求方式

Get

请求参数

pagerows(分页)

返回值类型

EUDataGridResult类型

 

7.3.2 Mapper实现

--说明:连表查询下,需要自定义查询方法,基于注解实现

 

--修改ItemParamMapper接口,新增查询方法

public interface ItemParamMapper extends BaseMapper<ItemParam>{

 

@Select(value="select p.id,p.item_cat_id as itemCatId,t.name as itemCatName,p.param_data as paramData,p.created,p.updated "

+ "from tb_item_param p left join tb_item_cat t on p.item_cat_id = t.id "

+ "limit ${start},${pageSize}")

List<Map<String, Object>> listAndPage(@Param("start")int start,@Param("pageSize")int pageSize);

}

 

7.3.3 Service层实现

--修改ItemParamService接口及其实现类

@Override

public EUDataGridResult listAndPage(int curPage, int pageSize) {

 

List<Map<String, Object>> list = itemParamMapper.listAndPage((curPage-1)*pageSize, pageSize);

 

Integer count = itemParamMapper.selectCount(null);

 

EUDataGridResult result = new EUDataGridResult();

 

result.setRows(list);

result.setTotal(count);

 

return result;

}

 

 

7.3.4 Controller层实现

--修改ItemParamController接口

@RequestMapping("/list")

@ResponseBody

public EUDataGridResult listAndPage(Integer page,Integer rows){

EUDataGridResult result = service.listAndPage(page, rows);

 

return result;

}

 

7.4 访问测试

 

 

 

规格参数列表实现!!!

转载于:https://www.cnblogs.com/aknife/p/11255120.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值