如何做一个电商系统(二)

1.如何做一个电商系统

1.1.目标

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

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

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

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

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

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

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

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

考核的知识点:

1. Linux系统的使用

2. tengine 纯HTTP的web服务器

3. SpringMVC的上传功能

4. FTP的数据传到

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

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

第五步:商品的保存

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

1.2.功能分析

1.2.1.相关数据表

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

image.png

1.2.2.实现的思路

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

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

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

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

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

2.1.需求分析

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

image.png

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

image.png

实现的思路:

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

根据业务理解:

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

(2)确定异步树请求的参数及返回的节点结构。(要构建easyui-tree对应的业务模型VO,id、text、status)

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

2.2.实现步骤

2.2.1.第一步:加载树控件

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

image.png

(2)加载异步树控件

image.png

查看EasyUI的API文档,我们知道:url是请求路径。

image.png

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

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

image.png

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

查看API文档,节点包括id、text、state三个基本属性。

image.png

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;

     //补全get、set方法

}

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;

    }

    // 补全get、set方法

}

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{

}

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 implements ItemCatService{

    @Override

    public List getByParentId(Long parentId) {

       List nodes = new ArrayList<>();

       EntityWrapper ew = new EntityWrapper<>();

       ew.eq("parent_id", parentId);

       List 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      initTreeByParentId(@RequestParam(defaultValue="0")Long id){

       List list = catService.getByParantId(id);

       return list;

    }

}

2.3.保存类目id到页面表单

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

image.png

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

image.png

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

3.1.传统上传方式的问题

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

image.png

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

image.png

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

3.2.1.思路分析

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

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

image.png

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

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

3.2.2.vsftpd简介

问题1:vsftpd是什么?

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

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

问题2:vsftpd作用是什么?

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

问题3:如何使用?

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

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

3.3.实现步骤说明

(1)在Linux上安装vsftpd服务。

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

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

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

image.png

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
        12.第五步:修改selinux(Linux安全内核系统)

(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
        13.第六步:启动vsftpd服务
[root@node0719 vsftpd]# service vsftpd start

为 vsftpd 启动 vsftpd: [确定]

        14.第七步:通过浏览器访问测试

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

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

(1)被动模式

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

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

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

image.png

(2)主动模式

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

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

image.png

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之间所有的端口。

(4)重启防火墙。

(5)重启vsftpd服务

image.png

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

image.png

3.4.1.9.第九步:java代码测试上传功能

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

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

     

       commons-net

       commons-net


(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做服务器,来加载图片。

问题1:Tengine是什么?

答:Tengine是web服务器。

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

答:apache、IIS、nginx

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

答:web应用服务器,是用来处理动态请求,常见的以tomcat、jetty等servlet容器为代表????????????????????

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

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

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

答:(1)Tomcat是servlet容器,处理静态资源的速度远低于Tengine。

(2)Tomcat的并发连接数,远远低于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编译环境

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

(2)缺少pcre环境

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

(3)缺少openssl环境

image.png
[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
        23.第六步:访问测试

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

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

[root@node07192 conf]# vim nginx.conf
image.png

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

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

[root@node07192 conf]# service iptables restart

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

image.png

3.4.2.7.第七步:配置图片服务

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

image.png

(2)重启tengine服务器

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

(3)浏览器访问图片

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

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

image.png

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

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

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

查看/html/index.html的权限

image.png
image.png

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

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

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

image.png

图片访问路径说明:
图片真实目录时 /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,初始化上传组件

image.png

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

image.png

上传组件在common.js中定义

image.png

上传组件的初始化方法init

image.png

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官方文档要求的返回格式类型

image.png
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();

    }

//补充get、set方法

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

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

     



       

       

       

       


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

     



       commons-fileupload

       commons-fileupload


(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_USERNAME}")

    private String FTP_USERNAME;

    @Value("${FTP_PASSWORD}")

    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.测试结果,上传成功!!!
image.png

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

image.png
image.png

页面效果

image.png

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

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

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

image.png

5.第四部分:商品规格参数

image.png

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.设计思路

image.png

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

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

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

5.4.实现流程

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

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

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

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

(1)js实现

image.png

(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();

    }

//补全get、set方法

}

(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{

}

(6)Service层实现

--创建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 ew = new EntityWrapper<>();

       ew.eq("item_cat_id", catId);

       List selectList = itemParamMapper.selectList(ew);

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

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

       }

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

    }

}

(4)Controller层实现

--创建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实现

image.png
image.png

(2)后台java代码实现

请求路径      /item/param/save/{cid}
请求方式      POST
请求参数      /{cid}  (类目id)  ;paramData  (json格式)
响应结果      EgoResult

(3)Service层实现

--修改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, "保存失败");

       }

    }

(4)Controller层实现

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

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

    @ResponseBody

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

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

    }

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

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

(1)前端js实现

image.png
image.png

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();

    }

6.第五部分:保存商品

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

6.1.前端js实现

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

image.png

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

image.png

6.1.4.提交保存商品请求

image.png

6.2.后台java实现

6.2.1.请求响应格式

请求路径      /item/save
请求方式      POST
请求参数      TbItem、desc、itemParams
响应格式      {“status”:200   data:data}   参考http响应格式

6.2.2.代码结构

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

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

Mapper:Mybatis-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();

    }

    // 补全get、set方法

}

(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();

    }

    // 补全get、set方法

}

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{

}

(2)创建ItemDescMapper接口

package cn.gzsxt.base.mapper;     

import com.baomidou.mybatisplus.mapper.BaseMapper;

import cn.gzsxt.base.pojo.ItemDesc;

public interface ItemDescMapper extends BaseMapper{

}

6.2.5.Service代码实现

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

--注意:注入ItemDescMapper、ItemParamItemMapper

@Service

public class ItemServiceImpl extends ServiceImpl      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.商品规格参数列表实现

7.1.思路

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

image.png

7.2.前端js实现

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

7.3.后台代码实现

7.3.1.确定请求响应格式

请求路径      /item/param/list
请求方式      Get
请求参数      page、rows(分页)
返回值类型      EUDataGridResult类型

7.3.2.Mapper实现

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

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

public interface ItemParamMapper extends BaseMapper{     

    @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> listAndPage(@Param("start")int      start,@Param("pageSize")int pageSize);

}

7.3.3.Service层实现

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

@Override     

    public EUDataGridResult listAndPage(int curPage, int pageSize) {

       List> 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.访问测试

规格参数列表实现!!!

image.png

你可能感兴趣的:(如何做一个电商系统(二))