谷粒商城项目笔记之分布式基础

谷粒商城项目之分布式基础

目录

前言

我们在这一阶段正式迈入实战项目---谷粒商城。接下来我们迈入分布式基础之项目环境搭建吧。


1 项目简介

1.1 项目背景

1.1.1 电商模式

在这里插入图片描述

1.1.2 谷粒商城

谷粒商城是一个B2C 模式的电商平台,销售自营商品给客户。

1.2 项目架构图

1.2.1 项目微服务架构图

在这里插入图片描述

1.2.2 微服务划分图

在这里插入图片描述

1.2.3 项目技术&特色

在这里插入图片描述

1.2.4 项目前置要求

在这里插入图片描述


2 分布式基础概念

2.1 微服务

在这里插入图片描述
在这里插入图片描述

2.2 集群&分布式&节点

在这里插入图片描述
在这里插入图片描述

2.3 远程调用

在这里插入图片描述

2.4 负载均衡

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5 服务注册/发现&注册中心

在这里插入图片描述

2.6 配置中心

在这里插入图片描述

2.7 服务熔断&服务降级

在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

在这里插入图片描述
在这里插入图片描述

2.8 API 网关

在这里插入图片描述
在这里插入图片描述


3 环境搭建

3.1 安装Linux虚拟机

3.1.1 软件下载

  1. 我们下载 virtual box 这个虚拟机软件 ,官网地址.
    在这里插入图片描述
    傻瓜式安装。
    在这里插入图片描述
    注意设置全局虚拟机路径。因为这个到时候虚拟机默认在c盘当前用户下。当然你c盘很大那无需设置。

  2. 下载vagrant 这个软件,官网地址,这个软件可以帮助我们自动安装虚拟机和其配置流程。

在这里插入图片描述
傻瓜式安装,注意这个软件安装之后需要重启电脑。

在这里插入图片描述
vagrant镜像仓库:https://app.vagrantup.com/boxes/search。
这个就是vagrant搭配的第三方centos7镜像。

3.1.2 电脑开启CPU虚拟化

在这里插入图片描述
在这里插入图片描述
虚拟机软件一般都要开启这个。

3.1.3 安装

  1. 打开 window cmd 窗口,运行 Vagrant init centos/7,即可初始化一个 centos7 系统
  2. 运行 vagrant up 即可启动虚拟机。系统 root 用户的密码是 vagrant
  3. vagrant 其他常用命令:
    - vagrant ssh:自动使用 vagrant 用户连接虚拟机。
    - vagrant upload source [destination] [name|id]:上传文件
    - https://www.vagrantup.com/docs/cli/init.html Vagrant 命令行
    在这里插入图片描述

3.1.4 修改ip

默认虚拟机的 ip 地址不是固定 ip,开发不方便.

  • 修改 Vagrantfile
    在这里插入图片描述
    windows下cmd命令行中输入 ifconfig查看 virtual box的ip网段。
    在这里插入图片描述
    config.vm.network "private_network", ip: "192.168.56.10" 将文件这一行注释掉,改为这行代码。
    在这里插入图片描述
    注意:改完之后在命令行输入 vagrant reload 才行,否则会不生效(大坑,否则一直是2个,不会是3个有固定Ip地址的)。

  • 重新使用vagrant up 启动机器即可。然后再vagrant ssh 连接机器。

3.1.5 使用第三方工具登录

在这里插入图片描述
课件上面,修改 PasswordAuthentication yes 这个选项。
网上找到另一种方法来使用第三方ssh登录工具。
在这里插入图片描述
在这里插入图片描述

成功。

3.1.6 从vagrant用户切换为管理员用户

不知道有没有发现。前面我们登录之后命令符是$,这个是普通用户。但是对于我们开发测试学习来说,普通用户权限太低,对于一些配置文件的修改有很大的限制,所以我们可以使用命令切换到root用户。
在这里插入图片描述

3.1.7 修改yum源(下载安装更快)

注意要在root用户下。

  1. 备份原yum源
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
  2. 使用新yum 源
    curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
  3. 生成缓存
    yum makecache
    在这里插入图片描述

3.2 安装docker

sudo ---让普通用户可以拥有超级管理员权限。
Docker 安装文档:https://docs.docker.com/install/linux/docker-ce/centos/

3.2.1 卸载系统之前的docker

sudo yum remove docker \
				docker-client \
				docker-client-latest \
				docker-common \
				docker-latest \
				docker-latest-logrotate \
				docker-logrotate \
				docker-engine

3.2.2 安装Docker-CE

  1. 安装必须的依赖
sudo yum install -y yum-utils \
		device-mapper-persistent-data \
		lvm2
  1. 设置docker repo 的yum 位置
sudo yum-config-manager \
		--add-repo \
		https://download.docker.com/linux/centos/docker-ce.repo
  1. 安装docker,以及docker-cli
sudo yum install docker-ce docker-ce-cli containerd.io

3.3.3 启动docker

sudo systemctl start docker

3.3.4 设置docker 开机自启

sudo systemctl enable docker

**注意:docker 要在root用户下进行测试。

3.3.6 配置docker 镜像加速 --- 阿里云镜像加速服务

在这里插入图片描述
在这里插入图片描述

1. mkdir -p /etc/docker

2. tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://6xvuqzlo.mirror.aliyuncs.com"]
}
EOF
  • sudo systemctl daemon-reload #重启docker后台线程
  • sudo systemctl restart docker

3.3 docker 安装MySQL

在这里插入图片描述

3.3.1 下载镜像文件

docker pull mysql:5.7

3.3.2 创建实例并启动

docker run -p 3306:3306 --name mysql \
						-v /mydata/mysql/log:/var/log/mysql \
						-v /mydata/mysql/data:/var/lib/mysql \
						-v /mydata/mysql/conf:/etc/mysql \
						-e MYSQL_ROOT_PASSWORD=root \
						-d mysql:5.7
参数说明
-p 3306:3306:将容器的3306 端口映射到主机的3306 端口
-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
-e MYSQL_ROOT_PASSWORD=root:初始化root 用户的密码

3.3.3 配置MYSQL

主要是配置编码方式,因为默认有些不是utf-8.
在这里插入图片描述
所以我们需要进行一些配置,使得变为utf-8,支持中文编码方式,不会乱码。

vi /mydata/mysql/conf/my.cnf
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve

注意:解决MySQL 连接慢的问题
在配置文件中加入如下,并重启mysql
[mysqld]
skip-name-resolve
解释:
skip-name-resolve:跳过域名解析

3.3.4 通过容器的mysql 命令行工具连接

docker exec -it mysql mysql -uroot -proot

3.3.5 设置root 远程访问

grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
flush privileges;

3.4 docker 安装redis

3.4.1 下载镜像文件

docker pull redis

3.4.2 创建实例并启动

1. mkdir -p /mydata/redis/conf   # 提前创建好redis挂载文件目录,因为不提前设置,mydata/redis/conf/redis.conf 这个redis.conf会被认为是一个文件夹,不会文件

2. touch /mydata/redis/conf/redis.conf     # 创建redis 启动的配置文件


3. docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

在reids.conf中设置开启数据持久化。
在这里插入图片描述
redis 自描述文件:
https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf

如果还需要设置其他的可以去上面这个文件中看。

3.4.3 使用redis 镜像执行redis-cli 命令连接

docker exec -it redis redis-cli

3.4.4 Redis 图形化界面

在这里插入图片描述
连接自己的redis。
在这里插入图片描述

3.5 开发环境统一

在这里插入图片描述
因为我们创建了Mysql 和redis的容器,所以设置自启来避免每次都要重新去运行。

3.5.1 Maven

在maven的配置文件中修改镜像源和jdk版本。

配置阿里云镜像
<mirrors>
		<mirror>
				<id>nexus-aliyun</id>
				<mirrorOf>central</mirrorOf>
				<name>Nexus aliyun</name>
				<url>http://maven.aliyun.com/nexus/content/groups/public</url>
		</mirror>
</mirrors>
配置jdk1.8 编译项目
<profiles>
		<profile>
				<id>jdk-1.8</id>
				<activation>
				<activeByDefault>true</activeByDefault>
				<jdk>1.8</jdk>
				</activation>
				<properties>
				<maven.compiler.source>1.8</maven.compiler.source>
				<maven.compiler.target>1.8</maven.compiler.target>
				<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
				</properties>
		</profile>
</profiles>

3.5.2 Idea&VsCode

  1. idea 安装lombok、mybatisx 插件
    2.下载 vs code软件----前端必备 https://code.visualstudio.com/
  • Vscode 安装开发必备插件
  • Vetur —— 语法高亮、智能感知、Emmet 等,包含格式化功能, Alt+Shift+F (格式化全文),Ctrl+K Ctrl+F(格式化选中代码,两个Ctrl需要同时按着)
  • EsLint —— 语法纠错
  • Auto Close Tag —— 自动闭合HTML/XML 标签
  • Auto Rename Tag —— 自动完成另一侧标签的同步修改
  • JavaScript(ES6) code snippets — — ES6 语法智能提示以及快速输入, 除js 外还支持.ts,.jsx,.tsx,.html,.vue,省去了配置其支持各种包含js 代码文件的时间
  • HTML CSS Support —— 让html 标签上写class 智能提示当前项目所支持的样式
  • HTML Snippets —— html 快速自动补全
  • Open in browser —— 浏览器快速打开
  • Live Server —— 以内嵌服务器方式打开
  • Chinese (Simplified) Language Pack for Visual Studio Code —— 中文语言包

在这里插入图片描述

3.5.3 安装配置git ---- 配合码云

  1. 下载git;https://git-scm.com
  2. 配置git,进入git bash
# 配置用户名
git config --global user.name "username" //(名字)
# 配置邮箱
git config --global user.email "username@email.com" //(注册账号时用的邮箱)
  1. 配置ssh 免密登录----配合码云
    可以参考码云给出的方法。 https://gitee.com/help/articles/4181#article-header0
    进入git bash;使用:ssh-keygen -t rsa -C "xxxxx@xxxxx.com"命令。连续三次回车。
    一般用户目录下会有
    在这里插入图片描述
    或者cat ~/.ssh/id_rsa.pub
    登录进入gitee,在设置里面找到SSH KEY 将.pub 文件的内容粘贴进去
    使用ssh -T git@gitee.com 测试是否成功即可

Git+码云教程 https://gitee.com/help/articles/4104

3.5.4 创建谷粒商城基础项目仓库----以码云为例

在这里插入图片描述
远程仓库创建之后在idea中进行clone下载到本地项目。
在这里插入图片描述

3.3.6 创建微服务

老师课件
在这里插入图片描述
在这里插入图片描述
同理创建其他微服务。
Group:com.atguigu.gulimall
Artifact: gulimall-product gulimall-order gulimall-ware gulimall-member gulimall-coupon

每个微服务必备: web openfeign
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
项目架构:
在这里插入图片描述
在这里插入图片描述
在父工程的.gitignore文件中设置我们要忽略的文件。
在这里插入图片描述
在这里插入图片描述

3.3.7 数据库 准备

创建数据库。
在这里插入图片描述
对于数据库设计,这些在后面会讲到。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

每一个微服务对应的数据库,进行分库分表。
注意不要设置外键,因为几十万条数据,设置外键,修改,检索需要同时修改。浪费资源。
在这里插入图片描述
在这里插入图片描述

drop table if exists oms_order;

drop table if exists oms_order_item;

drop table if exists oms_order_operate_history;

drop table if exists oms_order_return_apply;

drop table if exists oms_order_return_reason;

drop table if exists oms_order_setting;

drop table if exists oms_payment_info;

drop table if exists oms_refund_info;

/*==============================================================*/
/* Table: oms_order                                             */
/*==============================================================*/
create table oms_order
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   order_sn             char(32) comment '订单号',
   coupon_id            bigint comment '使用的优惠券',
   create_time          datetime comment 'create_time',
   member_username      varchar(200) comment '用户名',
   total_amount         decimal(18,4) comment '订单总额',
   pay_amount           decimal(18,4) comment '应付总额',
   freight_amount       decimal(18,4) comment '运费金额',
   promotion_amount     decimal(18,4) comment '促销优化金额(促销价、满减、阶梯价)',
   integration_amount   decimal(18,4) comment '积分抵扣金额',
   coupon_amount        decimal(18,4) comment '优惠券抵扣金额',
   discount_amount      decimal(18,4) comment '后台调整订单使用的折扣金额',
   pay_type             tinyint comment '支付方式【1->支付宝;2->微信;3->银联; 4->货到付款;】',
   source_type          tinyint comment '订单来源[0->PC订单;1->app订单]',
   status               tinyint comment '订单状态【0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单】',
   delivery_company     varchar(64) comment '物流公司(配送方式)',
   delivery_sn          varchar(64) comment '物流单号',
   auto_confirm_day     int comment '自动确认时间(天)',
   integration          int comment '可以获得的积分',
   growth               int comment '可以获得的成长值',
   bill_type            tinyint comment '发票类型[0->不开发票;1->电子发票;2->纸质发票]',
   bill_header          varchar(255) comment '发票抬头',
   bill_content         varchar(255) comment '发票内容',
   bill_receiver_phone  varchar(32) comment '收票人电话',
   bill_receiver_email  varchar(64) comment '收票人邮箱',
   receiver_name        varchar(100) comment '收货人姓名',
   receiver_phone       varchar(32) comment '收货人电话',
   receiver_post_code   varchar(32) comment '收货人邮编',
   receiver_province    varchar(32) comment '省份/直辖市',
   receiver_city        varchar(32) comment '城市',
   receiver_region      varchar(32) comment '区',
   receiver_detail_address varchar(200) comment '详细地址',
   note                 varchar(500) comment '订单备注',
   confirm_status       tinyint comment '确认收货状态[0->未确认;1->已确认]',
   delete_status        tinyint comment '删除状态【0->未删除;1->已删除】',
   use_integration      int comment '下单时使用的积分',
   payment_time         datetime comment '支付时间',
   delivery_time        datetime comment '发货时间',
   receive_time         datetime comment '确认收货时间',
   comment_time         datetime comment '评价时间',
   modify_time          datetime comment '修改时间',
   primary key (id)
);

alter table oms_order comment '订单';

/*==============================================================*/
/* Table: oms_order_item                                        */
/*==============================================================*/
create table oms_order_item
(
   id                   bigint not null auto_increment comment 'id',
   order_id             bigint comment 'order_id',
   order_sn             char(32) comment 'order_sn',
   spu_id               bigint comment 'spu_id',
   spu_name             varchar(255) comment 'spu_name',
   spu_pic              varchar(500) comment 'spu_pic',
   spu_brand            varchar(200) comment '品牌',
   category_id          bigint comment '商品分类id',
   sku_id               bigint comment '商品sku编号',
   sku_name             varchar(255) comment '商品sku名字',
   sku_pic              varchar(500) comment '商品sku图片',
   sku_price            decimal(18,4) comment '商品sku价格',
   sku_quantity         int comment '商品购买的数量',
   sku_attrs_vals       varchar(500) comment '商品销售属性组合(JSON)',
   promotion_amount     decimal(18,4) comment '商品促销分解金额',
   coupon_amount        decimal(18,4) comment '优惠券优惠分解金额',
   integration_amount   decimal(18,4) comment '积分优惠分解金额',
   real_amount          decimal(18,4) comment '该商品经过优惠后的分解金额',
   gift_integration     int comment '赠送积分',
   gift_growth          int comment '赠送成长值',
   primary key (id)
);

alter table oms_order_item comment '订单项信息';

/*==============================================================*/
/* Table: oms_order_operate_history                             */
/*==============================================================*/
create table oms_order_operate_history
(
   id                   bigint not null auto_increment comment 'id',
   order_id             bigint comment '订单id',
   operate_man          varchar(100) comment '操作人[用户;系统;后台管理员]',
   create_time          datetime comment '操作时间',
   order_status         tinyint comment '订单状态【0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单】',
   note                 varchar(500) comment '备注',
   primary key (id)
);

alter table oms_order_operate_history comment '订单操作历史记录';

/*==============================================================*/
/* Table: oms_order_return_apply                                */
/*==============================================================*/
create table oms_order_return_apply
(
   id                   bigint not null auto_increment comment 'id',
   order_id             bigint comment 'order_id',
   sku_id               bigint comment '退货商品id',
   order_sn             char(32) comment '订单编号',
   create_time          datetime comment '申请时间',
   member_username      varchar(64) comment '会员用户名',
   return_amount        decimal(18,4) comment '退款金额',
   return_name          varchar(100) comment '退货人姓名',
   return_phone         varchar(20) comment '退货人电话',
   status               tinyint(1) comment '申请状态[0->待处理;1->退货中;2->已完成;3->已拒绝]',
   handle_time          datetime comment '处理时间',
   sku_img              varchar(500) comment '商品图片',
   sku_name             varchar(200) comment '商品名称',
   sku_brand            varchar(200) comment '商品品牌',
   sku_attrs_vals       varchar(500) comment '商品销售属性(JSON)',
   sku_count            int comment '退货数量',
   sku_price            decimal(18,4) comment '商品单价',
   sku_real_price       decimal(18,4) comment '商品实际支付单价',
   reason               varchar(200) comment '原因',
   description述         varchar(500) comment '描述',
   desc_pics            varchar(2000) comment '凭证图片,以逗号隔开',
   handle_note          varchar(500) comment '处理备注',
   handle_man           varchar(200) comment '处理人员',
   receive_man          varchar(100) comment '收货人',
   receive_time         datetime comment '收货时间',
   receive_note         varchar(500) comment '收货备注',
   receive_phone        varchar(20) comment '收货电话',
   company_address      varchar(500) comment '公司收货地址',
   primary key (id)
);

alter table oms_order_return_apply comment '订单退货申请';

/*==============================================================*/
/* Table: oms_order_return_reason                               */
/*==============================================================*/
create table oms_order_return_reason
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(200) comment '退货原因名',
   sort                 int comment '排序',
   status               tinyint(1) comment '启用状态',
   create_time          datetime comment 'create_time',
   primary key (id)
);

alter table oms_order_return_reason comment '退货原因';

/*==============================================================*/
/* Table: oms_order_setting                                     */
/*==============================================================*/
create table oms_order_setting
(
   id                   bigint not null auto_increment comment 'id',
   flash_order_overtime int comment '秒杀订单超时关闭时间(分)',
   normal_order_overtime int comment '正常订单超时时间(分)',
   confirm_overtime     int comment '发货后自动确认收货时间(天)',
   finish_overtime      int comment '自动完成交易时间,不能申请退货(天)',
   comment_overtime     int comment '订单完成后自动好评时间(天)',
   member_level         tinyint(2) comment '会员等级【0-不限会员等级,全部通用;其他-对应的其他会员等级】',
   primary key (id)
);

alter table oms_order_setting comment '订单配置信息';

/*==============================================================*/
/* Table: oms_payment_info                                      */
/*==============================================================*/
create table oms_payment_info
(
   id                   bigint not null auto_increment comment 'id',
   order_sn             char(32) comment '订单号(对外业务号)',
   order_id             bigint comment '订单id',
   alipay_trade_no      varchar(50) comment '支付宝交易流水号',
   total_amount         decimal(18,4) comment '支付总金额',
   subject              varchar(200) comment '交易内容',
   payment_status       varchar(20) comment '支付状态',
   create_time          datetime comment '创建时间',
   confirm_time         datetime comment '确认时间',
   callback_content     varchar(4000) comment '回调内容',
   callback_time        datetime comment '回调时间',
   primary key (id)
);

alter table oms_payment_info comment '支付信息表';

/*==============================================================*/
/* Table: oms_refund_info                                       */
/*==============================================================*/
create table oms_refund_info
(
   id                   bigint not null auto_increment comment 'id',
   order_return_id      bigint comment '退款的订单',
   refund               decimal(18,4) comment '退款金额',
   refund_sn            varchar(64) comment '退款交易流水号',
   refund_status        tinyint(1) comment '退款状态',
   refund_channel       tinyint comment '退款渠道[1-支付宝,2-微信,3-银联,4-汇款]',
   refund_content       varchar(5000),
   primary key (id)
);

alter table oms_refund_info comment '退款信息';

在这里插入图片描述

drop table if exists pms_attr;

drop table if exists pms_attr_attrgroup_relation;

drop table if exists pms_attr_group;

drop table if exists pms_brand;

drop table if exists pms_category;

drop table if exists pms_category_brand_relation;

drop table if exists pms_comment_replay;

drop table if exists pms_product_attr_value;

drop table if exists pms_sku_images;

drop table if exists pms_sku_info;

drop table if exists pms_sku_sale_attr_value;

drop table if exists pms_spu_comment;

drop table if exists pms_spu_images;

drop table if exists pms_spu_info;

drop table if exists pms_spu_info_desc;

/*==============================================================*/
/* Table: pms_attr                                              */
/*==============================================================*/
create table pms_attr
(
   attr_id              bigint not null auto_increment comment '属性id',
   attr_name            char(30) comment '属性名',
   search_type          tinyint comment '是否需要检索[0-不需要,1-需要]',
  `value_type` tinyint(4) DEFAULT NULL COMMENT '值类型[0-为单个值,1-可以选择多个值]',
  `icon` varchar(255) DEFAULT NULL COMMENT '属性图标',
  `value_select` char(255) DEFAULT NULL COMMENT '可选值列表[用逗号分隔]',
  `attr_type` tinyint(4) DEFAULT NULL COMMENT '属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]',
   enable               bigint comment '启用状态[0 - 禁用,1 - 启用]',
   catelog_id           bigint comment '所属分类',
   show_desc            tinyint comment '快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整',
   primary key (attr_id)
);

alter table pms_attr comment '商品属性';

/*==============================================================*/
/* Table: pms_attr_attrgroup_relation                           */
/*==============================================================*/
create table pms_attr_attrgroup_relation
(
   id                   bigint not null auto_increment comment 'id',
   attr_id              bigint comment '属性id',
   attr_group_id        bigint comment '属性分组id',
   attr_sort            int comment '属性组内排序',
   primary key (id)
);

alter table pms_attr_attrgroup_relation comment '属性&属性分组关联';

/*==============================================================*/
/* Table: pms_attr_group                                        */
/*==============================================================*/
create table pms_attr_group
(
   attr_group_id        bigint not null auto_increment comment '分组id',
   attr_group_name      char(20) comment '组名',
   sort                 int comment '排序',
   descript             varchar(255) comment '描述',
   icon                 varchar(255) comment '组图标',
   catelog_id           bigint comment '所属分类id',
   primary key (attr_group_id)
);

alter table pms_attr_group comment '属性分组';

/*==============================================================*/
/* Table: pms_brand                                             */
/*==============================================================*/
create table pms_brand
(
   brand_id             bigint not null auto_increment comment '品牌id',
   name                 char(50) comment '品牌名',
   logo                 varchar(2000) comment '品牌logo地址',
   descript             longtext comment '介绍',
   show_status          tinyint comment '显示状态[0-不显示;1-显示]',
   first_letter         char(1) comment '检索首字母',
   sort                 int comment '排序',
   primary key (brand_id)
);

alter table pms_brand comment '品牌';

/*==============================================================*/
/* Table: pms_category                                          */
/*==============================================================*/
create table pms_category
(
   cat_id               bigint not null auto_increment comment '分类id',
   name                 char(50) comment '分类名称',
   parent_cid           bigint comment '父分类id',
   cat_level            int comment '层级',
   show_status          tinyint comment '是否显示[0-不显示,1显示]',
   sort                 int comment '排序',
   icon                 char(255) comment '图标地址',
   product_unit         char(50) comment '计量单位',
   product_count        int comment '商品数量',
   primary key (cat_id)
);

alter table pms_category comment '商品三级分类';

/*==============================================================*/
/* Table: pms_category_brand_relation                           */
/*==============================================================*/
create table pms_category_brand_relation
(
   id                   bigint not null auto_increment,
   brand_id             bigint comment '品牌id',
   catelog_id           bigint comment '分类id',
   brand_name           varchar(255),
   catelog_name         varchar(255),
   primary key (id)
);

alter table pms_category_brand_relation comment '品牌分类关联';

/*==============================================================*/
/* Table: pms_comment_replay                                    */
/*==============================================================*/
create table pms_comment_replay
(
   id                   bigint not null auto_increment comment 'id',
   comment_id           bigint comment '评论id',
   reply_id             bigint comment '回复id',
   primary key (id)
);

alter table pms_comment_replay comment '商品评价回复关系';

/*==============================================================*/
/* Table: pms_product_attr_value                                */
/*==============================================================*/
create table pms_product_attr_value
(
   id                   bigint not null auto_increment comment 'id',
   spu_id               bigint comment '商品id',
   attr_id              bigint comment '属性id',
   attr_name            varchar(200) comment '属性名',
   attr_value           varchar(200) comment '属性值',
   attr_sort            int comment '顺序',
   quick_show           tinyint comment '快速展示【是否展示在介绍上;0-否 1-是】',
   primary key (id)
);

alter table pms_product_attr_value comment 'spu属性值';

/*==============================================================*/
/* Table: pms_sku_images                                        */
/*==============================================================*/
create table pms_sku_images
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   img_url              varchar(255) comment '图片地址',
   img_sort             int comment '排序',
   default_img          int comment '默认图[0 - 不是默认图,1 - 是默认图]',
   primary key (id)
);

alter table pms_sku_images comment 'sku图片';

/*==============================================================*/
/* Table: pms_sku_info                                          */
/*==============================================================*/
create table pms_sku_info
(
   sku_id               bigint not null auto_increment comment 'skuId',
   spu_id               bigint comment 'spuId',
   sku_name             varchar(255) comment 'sku名称',
   sku_desc             varchar(2000) comment 'sku介绍描述',
   catalog_id           bigint comment '所属分类id',
   brand_id             bigint comment '品牌id',
   sku_default_img      varchar(255) comment '默认图片',
   sku_title            varchar(255) comment '标题',
   sku_subtitle         varchar(2000) comment '副标题',
   price                decimal(18,4) comment '价格',
   sale_count           bigint comment '销量',
   primary key (sku_id)
);

alter table pms_sku_info comment 'sku信息';

/*==============================================================*/
/* Table: pms_sku_sale_attr_value                               */
/*==============================================================*/
create table pms_sku_sale_attr_value
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   attr_id              bigint comment 'attr_id',
   attr_name            varchar(200) comment '销售属性名',
   attr_value           varchar(200) comment '销售属性值',
   attr_sort            int comment '顺序',
   primary key (id)
);

alter table pms_sku_sale_attr_value comment 'sku销售属性&值';

/*==============================================================*/
/* Table: pms_spu_comment                                       */
/*==============================================================*/
create table pms_spu_comment
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   spu_id               bigint comment 'spu_id',
   spu_name             varchar(255) comment '商品名字',
   member_nick_name     varchar(255) comment '会员昵称',
   star                 tinyint(1) comment '星级',
   member_ip            varchar(64) comment '会员ip',
   create_time          datetime comment '创建时间',
   show_status          tinyint(1) comment '显示状态[0-不显示,1-显示]',
   spu_attributes       varchar(255) comment '购买时属性组合',
   likes_count          int comment '点赞数',
   reply_count          int comment '回复数',
   resources            varchar(1000) comment '评论图片/视频[json数据;[{type:文件类型,url:资源路径}]]',
   content              text comment '内容',
   member_icon          varchar(255) comment '用户头像',
   comment_type         tinyint comment '评论类型[0 - 对商品的直接评论,1 - 对评论的回复]',
   primary key (id)
);

alter table pms_spu_comment comment '商品评价';

/*==============================================================*/
/* Table: pms_spu_images                                        */
/*==============================================================*/
create table pms_spu_images
(
   id                   bigint not null auto_increment comment 'id',
   spu_id               bigint comment 'spu_id',
   img_name             varchar(200) comment '图片名',
   img_url              varchar(255) comment '图片地址',
   img_sort             int comment '顺序',
   default_img          tinyint comment '是否默认图',
   primary key (id)
);

alter table pms_spu_images comment 'spu图片';

/*==============================================================*/
/* Table: pms_spu_info                                          */
/*==============================================================*/
create table pms_spu_info
(
   id                   bigint not null auto_increment comment '商品id',
   spu_name             varchar(200) comment '商品名称',
   spu_description      varchar(1000) comment '商品描述',
   catalog_id           bigint comment '所属分类id',
   brand_id             bigint comment '品牌id',
   weight               decimal(18,4),
   publish_status       tinyint comment '上架状态[0 - 下架,1 - 上架]',
   create_time          datetime,
   update_time          datetime,
   primary key (id)
);

alter table pms_spu_info comment 'spu信息';

/*==============================================================*/
/* Table: pms_spu_info_desc                                     */
/*==============================================================*/
create table pms_spu_info_desc
(
   spu_id               bigint not null comment '商品id',
   decript              longtext comment '商品介绍',
   primary key (spu_id)
);

alter table pms_spu_info_desc comment 'spu信息介绍';

在这里插入图片描述

drop table if exists sms_coupon;

drop table if exists sms_coupon_history;

drop table if exists sms_coupon_spu_category_relation;

drop table if exists sms_coupon_spu_relation;

drop table if exists sms_home_adv;

drop table if exists sms_home_subject;

drop table if exists sms_home_subject_spu;

drop table if exists sms_member_price;

drop table if exists sms_seckill_promotion;

drop table if exists sms_seckill_session;

drop table if exists sms_seckill_sku_notice;

drop table if exists sms_seckill_sku_relation;

drop table if exists sms_sku_full_reduction;

drop table if exists sms_sku_ladder;

drop table if exists sms_spu_bounds;

/*==============================================================*/
/* Table: sms_coupon                                            */
/*==============================================================*/
create table sms_coupon
(
   id                   bigint not null auto_increment comment 'id',
   coupon_type          tinyint(1) comment '优惠卷类型[0->全场赠券;1->会员赠券;2->购物赠券;3->注册赠券]',
   coupon_img           varchar(2000) comment '优惠券图片',
   coupon_name          varchar(100) comment '优惠卷名字',
   num                  int comment '数量',
   amount               decimal(18,4) comment '金额',
   per_limit            int comment '每人限领张数',
   min_point            decimal(18,4) comment '使用门槛',
   start_time           datetime comment '开始时间',
   end_time             datetime comment '结束时间',
   use_type             tinyint(1) comment '使用类型[0->全场通用;1->指定分类;2->指定商品]',
   note                 varchar(200) comment '备注',
   publish_count        int(11) comment '发行数量',
   use_count            int(11) comment '已使用数量',
   receive_count        int(11) comment '领取数量',
   enable_start_time    datetime comment '可以领取的开始日期',
   enable_end_time      datetime comment '可以领取的结束日期',
   code                 varchar(64) comment '优惠码',
   member_level         tinyint(1) comment '可以领取的会员等级[0->不限等级,其他-对应等级]',
   publish              tinyint(1) comment '发布状态[0-未发布,1-已发布]',
   primary key (id)
);

alter table sms_coupon comment '优惠券信息';

/*==============================================================*/
/* Table: sms_coupon_history                                    */
/*==============================================================*/
create table sms_coupon_history
(
   id                   bigint not null auto_increment comment 'id',
   coupon_id            bigint comment '优惠券id',
   member_id            bigint comment '会员id',
   member_nick_name     varchar(64) comment '会员名字',
   get_type             tinyint(1) comment '获取方式[0->后台赠送;1->主动领取]',
   create_time          datetime comment '创建时间',
   use_type             tinyint(1) comment '使用状态[0->未使用;1->已使用;2->已过期]',
   use_time             datetime comment '使用时间',
   order_id             bigint comment '订单id',
   order_sn             bigint comment '订单号',
   primary key (id)
);

alter table sms_coupon_history comment '优惠券领取历史记录';

/*==============================================================*/
/* Table: sms_coupon_spu_category_relation                      */
/*==============================================================*/
create table sms_coupon_spu_category_relation
(
   id                   bigint not null auto_increment comment 'id',
   coupon_id            bigint comment '优惠券id',
   category_id          bigint comment '产品分类id',
   category_name        varchar(64) comment '产品分类名称',
   primary key (id)
);

alter table sms_coupon_spu_category_relation comment '优惠券分类关联';

/*==============================================================*/
/* Table: sms_coupon_spu_relation                               */
/*==============================================================*/
create table sms_coupon_spu_relation
(
   id                   bigint not null auto_increment comment 'id',
   coupon_id            bigint comment '优惠券id',
   spu_id               bigint comment 'spu_id',
   spu_name             varchar(255) comment 'spu_name',
   primary key (id)
);

alter table sms_coupon_spu_relation comment '优惠券与产品关联';

/*==============================================================*/
/* Table: sms_home_adv                                          */
/*==============================================================*/
create table sms_home_adv
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(100) comment '名字',
   pic                  varchar(500) comment '图片地址',
   start_time           datetime comment '开始时间',
   end_time             datetime comment '结束时间',
   status               tinyint(1) comment '状态',
   click_count          int comment '点击数',
   url                  varchar(500) comment '广告详情连接地址',
   note                 varchar(500) comment '备注',
   sort                 int comment '排序',
   publisher_id         bigint comment '发布者',
   auth_id              bigint comment '审核者',
   primary key (id)
);

alter table sms_home_adv comment '首页轮播广告';

/*==============================================================*/
/* Table: sms_home_subject                                      */
/*==============================================================*/
create table sms_home_subject
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(200) comment '专题名字',
   title                varchar(255) comment '专题标题',
   sub_title            varchar(255) comment '专题副标题',
   status               tinyint(1) comment '显示状态',
   url                  varchar(500) comment '详情连接',
   sort                 int comment '排序',
   img                  varchar(500) comment '专题图片地址',
   primary key (id)
);

alter table sms_home_subject comment '首页专题表【jd首页下面很多专题,每个专题链接新的页面,展示专题商品信息】';

/*==============================================================*/
/* Table: sms_home_subject_spu                                  */
/*==============================================================*/
create table sms_home_subject_spu
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(200) comment '专题名字',
   subject_id           bigint comment '专题id',
   spu_id               bigint comment 'spu_id',
   sort                 int comment '排序',
   primary key (id)
);

alter table sms_home_subject_spu comment '专题商品';

/*==============================================================*/
/* Table: sms_member_price                                      */
/*==============================================================*/
create table sms_member_price
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   member_level_id      bigint comment '会员等级id',
   member_level_name    varchar(100) comment '会员等级名',
   member_price         decimal(18,4) comment '会员对应价格',
   add_other            tinyint(1) comment '可否叠加其他优惠[0-不可叠加优惠,1-可叠加]',
   primary key (id)
);

alter table sms_member_price comment '商品会员价格';

/*==============================================================*/
/* Table: sms_seckill_promotion                                 */
/*==============================================================*/
create table sms_seckill_promotion
(
   id                   bigint not null auto_increment comment 'id',
   title                varchar(255) comment '活动标题',
   start_time           datetime comment '开始日期',
   end_time             datetime comment '结束日期',
   status               tinyint comment '上下线状态',
   create_time          datetime comment '创建时间',
   user_id              bigint comment '创建人',
   primary key (id)
);

alter table sms_seckill_promotion comment '秒杀活动';

/*==============================================================*/
/* Table: sms_seckill_session                                   */
/*==============================================================*/
create table sms_seckill_session
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(200) comment '场次名称',
   start_time           datetime comment '每日开始时间',
   end_time             datetime comment '每日结束时间',
   status               tinyint(1) comment '启用状态',
   create_time          datetime comment '创建时间',
   primary key (id)
);

alter table sms_seckill_session comment '秒杀活动场次';

/*==============================================================*/
/* Table: sms_seckill_sku_notice                                */
/*==============================================================*/
create table sms_seckill_sku_notice
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   sku_id               bigint comment 'sku_id',
   session_id           bigint comment '活动场次id',
   subcribe_time        datetime comment '订阅时间',
   send_time            datetime comment '发送时间',
   notice_type          tinyint(1) comment '通知方式[0-短信,1-邮件]',
   primary key (id)
);

alter table sms_seckill_sku_notice comment '秒杀商品通知订阅';

/*==============================================================*/
/* Table: sms_seckill_sku_relation                              */
/*==============================================================*/
create table sms_seckill_sku_relation
(
   id                   bigint not null auto_increment comment 'id',
   promotion_id         bigint comment '活动id',
   promotion_session_id bigint comment '活动场次id',
   sku_id               bigint comment '商品id',
   seckill_price        decimal comment '秒杀价格',
   seckill_count        decimal comment '秒杀总量',
   seckill_limit        decimal comment '每人限购数量',
   seckill_sort         int comment '排序',
   primary key (id)
);

alter table sms_seckill_sku_relation comment '秒杀活动商品关联';

/*==============================================================*/
/* Table: sms_sku_full_reduction                                */
/*==============================================================*/
create table sms_sku_full_reduction
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'spu_id',
   full_price           decimal(18,4) comment '满多少',
   reduce_price         decimal(18,4) comment '减多少',
   add_other            tinyint(1) comment '是否参与其他优惠',
   primary key (id)
);

alter table sms_sku_full_reduction comment '商品满减信息';

/*==============================================================*/
/* Table: sms_sku_ladder                                        */
/*==============================================================*/
create table sms_sku_ladder
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'spu_id',
   full_count           int comment '满几件',
   discount             decimal(4,2) comment '打几折',
   price                decimal(18,4) comment '折后价',
   add_other            tinyint(1) comment '是否叠加其他优惠[0-不可叠加,1-可叠加]',
   primary key (id)
);

alter table sms_sku_ladder comment '商品阶梯价格';

/*==============================================================*/
/* Table: sms_spu_bounds                                        */
/*==============================================================*/
create table sms_spu_bounds
(
   id                   bigint not null auto_increment comment 'id',
   spu_id               bigint,
   grow_bounds          decimal(18,4) comment '成长积分',
   buy_bounds           decimal(18,4) comment '购物积分',
   work                 tinyint(1) comment '优惠生效情况[1111(四个状态位,从右到左);0 - 无优惠,成长积分是否赠送;1 - 无优惠,购物积分是否赠送;2 - 有优惠,成长积分是否赠送;3 - 有优惠,购物积分是否赠送【状态位0:不赠送,1:赠送】]',
   primary key (id)
);

alter table sms_spu_bounds comment '商品spu积分设置';

在这里插入图片描述

drop table if exists ums_growth_change_history;

drop table if exists ums_integration_change_history;

drop table if exists ums_member;

drop table if exists ums_member_collect_spu;

drop table if exists ums_member_collect_subject;

drop table if exists ums_member_level;

drop table if exists ums_member_login_log;

drop table if exists ums_member_receive_address;

drop table if exists ums_member_statistics_info;

/*==============================================================*/
/* Table: ums_growth_change_history                             */
/*==============================================================*/
create table ums_growth_change_history
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   create_time          datetime comment 'create_time',
   change_count         int comment '改变的值(正负计数)',
   note                 varchar(0) comment '备注',
   source_type          tinyint comment '积分来源[0-购物,1-管理员修改]',
   primary key (id)
);

alter table ums_growth_change_history comment '成长值变化历史记录';

/*==============================================================*/
/* Table: ums_integration_change_history                        */
/*==============================================================*/
create table ums_integration_change_history
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   create_time          datetime comment 'create_time',
   change_count         int comment '变化的值',
   note                 varchar(255) comment '备注',
   source_tyoe          tinyint comment '来源[0->购物;1->管理员修改;2->活动]',
   primary key (id)
);

alter table ums_integration_change_history comment '积分变化历史记录';

/*==============================================================*/
/* Table: ums_member                                            */
/*==============================================================*/
create table ums_member
(
   id                   bigint not null auto_increment comment 'id',
   level_id             bigint comment '会员等级id',
   username             char(64) comment '用户名',
   password             varchar(64) comment '密码',
   nickname             varchar(64) comment '昵称',
   mobile               varchar(20) comment '手机号码',
   email                varchar(64) comment '邮箱',
   header               varchar(500) comment '头像',
   gender               tinyint comment '性别',
   birth                date comment '生日',
   city                 varchar(500) comment '所在城市',
   job                  varchar(255) comment '职业',
   sign                 varchar(255) comment '个性签名',
   source_type          tinyint comment '用户来源',
   integration          int comment '积分',
   growth               int comment '成长值',
   status               tinyint comment '启用状态',
   create_time          datetime comment '注册时间',
   primary key (id)
);

alter table ums_member comment '会员';

/*==============================================================*/
/* Table: ums_member_collect_spu                                */
/*==============================================================*/
create table ums_member_collect_spu
(
   id                   bigint not null comment 'id',
   member_id            bigint comment '会员id',
   spu_id               bigint comment 'spu_id',
   spu_name             varchar(500) comment 'spu_name',
   spu_img              varchar(500) comment 'spu_img',
   create_time          datetime comment 'create_time',
   primary key (id)
);

alter table ums_member_collect_spu comment '会员收藏的商品';

/*==============================================================*/
/* Table: ums_member_collect_subject                            */
/*==============================================================*/
create table ums_member_collect_subject
(
   id                   bigint not null auto_increment comment 'id',
   subject_id           bigint comment 'subject_id',
   subject_name         varchar(255) comment 'subject_name',
   subject_img          varchar(500) comment 'subject_img',
   subject_urll         varchar(500) comment '活动url',
   primary key (id)
);

alter table ums_member_collect_subject comment '会员收藏的专题活动';

/*==============================================================*/
/* Table: ums_member_level                                      */
/*==============================================================*/
create table ums_member_level
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(100) comment '等级名称',
   growth_point         int comment '等级需要的成长值',
   default_status       tinyint comment '是否为默认等级[0->不是;1->是]',
   free_freight_point   decimal(18,4) comment '免运费标准',
   comment_growth_point int comment '每次评价获取的成长值',
   priviledge_free_freight tinyint comment '是否有免邮特权',
   priviledge_member_price tinyint comment '是否有会员价格特权',
   priviledge_birthday  tinyint comment '是否有生日特权',
   note                 varchar(255) comment '备注',
   primary key (id)
);

alter table ums_member_level comment '会员等级';

/*==============================================================*/
/* Table: ums_member_login_log                                  */
/*==============================================================*/
create table ums_member_login_log
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   create_time          datetime comment '创建时间',
   ip                   varchar(64) comment 'ip',
   city                 varchar(64) comment 'city',
   login_type           tinyint(1) comment '登录类型[1-web,2-app]',
   primary key (id)
);

alter table ums_member_login_log comment '会员登录记录';

/*==============================================================*/
/* Table: ums_member_receive_address                            */
/*==============================================================*/
create table ums_member_receive_address
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment 'member_id',
   name                 varchar(255) comment '收货人姓名',
   phone                varchar(64) comment '电话',
   post_code            varchar(64) comment '邮政编码',
   province             varchar(100) comment '省份/直辖市',
   city                 varchar(100) comment '城市',
   region               varchar(100) comment '区',
   detail_address       varchar(255) comment '详细地址(街道)',
   areacode             varchar(15) comment '省市区代码',
   default_status       tinyint(1) comment '是否默认',
   primary key (id)
);

alter table ums_member_receive_address comment '会员收货地址';

/*==============================================================*/
/* Table: ums_member_statistics_info                            */
/*==============================================================*/
create table ums_member_statistics_info
(
   id                   bigint not null auto_increment comment 'id',
   member_id            bigint comment '会员id',
   consume_amount       decimal(18,4) comment '累计消费金额',
   coupon_amount        decimal(18,4) comment '累计优惠金额',
   order_count          int comment '订单数量',
   coupon_count         int comment '优惠券数量',
   comment_count        int comment '评价数',
   return_order_count   int comment '退货数量',
   login_count          int comment '登录次数',
   attend_count         int comment '关注数量',
   fans_count           int comment '粉丝数量',
   collect_product_count int comment '收藏的商品数量',
   collect_subject_count int comment '收藏的专题活动数量',
   collect_comment_count int comment '收藏的评论数量',
   invite_friend_count  int comment '邀请的朋友数量',
   primary key (id)
);

alter table ums_member_statistics_info comment '会员统计信息';

在这里插入图片描述

drop table if exists wms_purchase;

drop table if exists wms_purchase_detail;

drop table if exists wms_ware_info;

drop table if exists wms_ware_order_task;

drop table if exists wms_ware_order_task_detail;

drop table if exists wms_ware_sku;

/*==============================================================*/
/* Table: wms_purchase                                          */
/*==============================================================*/
create table wms_purchase
(
   id                   bigint not null auto_increment comment '采购单id',
   assignee_id          bigint comment '采购人id',
   assignee_name        varchar(255) comment '采购人名',
   phone                char(13) comment '联系方式',
   priority             int(4) comment '优先级',
   status               int(4) comment '状态',
   ware_id              bigint comment '仓库id',
   amount               decimal(18,4) comment '总金额',
   create_time          datetime comment '创建日期',
   update_time          datetime comment '更新日期',
   primary key (id)
);

alter table wms_purchase comment '采购信息';

/*==============================================================*/
/* Table: wms_purchase_detail                                   */
/*==============================================================*/
create table wms_purchase_detail
(
   id                   bigint not null auto_increment,
   purchase_id          bigint comment '采购单id',
   sku_id               bigint comment '采购商品id',
   sku_num              int comment '采购数量',
   sku_price            decimal(18,4) comment '采购金额',
   ware_id              bigint comment '仓库id',
   status               int comment '状态[0新建,1已分配,2正在采购,3已完成,4采购失败]',
   primary key (id)
);

/*==============================================================*/
/* Table: wms_ware_info                                         */
/*==============================================================*/
create table wms_ware_info
(
   id                   bigint not null auto_increment comment 'id',
   name                 varchar(255) comment '仓库名',
   address              varchar(255) comment '仓库地址',
   areacode             varchar(20) comment '区域编码',
   primary key (id)
);

alter table wms_ware_info comment '仓库信息';

/*==============================================================*/
/* Table: wms_ware_order_task                                   */
/*==============================================================*/
create table wms_ware_order_task
(
   id                   bigint not null auto_increment comment 'id',
   order_id             bigint comment 'order_id',
   order_sn             varchar(255) comment 'order_sn',
   consignee            varchar(100) comment '收货人',
   consignee_tel        char(15) comment '收货人电话',
   delivery_address     varchar(500) comment '配送地址',
   order_comment        varchar(200) comment '订单备注',
   payment_way          tinyint(1) comment '付款方式【 1:在线付款 2:货到付款】',
   task_status          tinyint(2) comment '任务状态',
   order_body           varchar(255) comment '订单描述',
   tracking_no          char(30) comment '物流单号',
   create_time          datetime comment 'create_time',
   ware_id              bigint comment '仓库id',
   task_comment         varchar(500) comment '工作单备注',
   primary key (id)
);

alter table wms_ware_order_task comment '库存工作单';

/*==============================================================*/
/* Table: wms_ware_order_task_detail                            */
/*==============================================================*/
create table wms_ware_order_task_detail
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   sku_name             varchar(255) comment 'sku_name',
   sku_num              int comment '购买个数',
   task_id              bigint comment '工作单id',
   primary key (id)
);

alter table wms_ware_order_task_detail comment '库存工作单';

/*==============================================================*/
/* Table: wms_ware_sku                                          */
/*==============================================================*/
create table wms_ware_sku
(
   id                   bigint not null auto_increment comment 'id',
   sku_id               bigint comment 'sku_id',
   ware_id              bigint comment '仓库id',
   stock                int comment '库存数',
   sku_name             varchar(200) comment 'sku_name',
   stock_locked         int comment '锁定库存',
   primary key (id)
);

alter table wms_ware_sku comment '商品库存';

3.3.8 安装Node.js

前端开发,少不了node.js;Node.js 是一个基于Chrome V8 引擎的JavaScript 运行环境。
node 下载 :https://nodejs.org/en/
NPM 是随同NodeJS 一起安装的包管理工具,JavaScript-NPM,Java-Maven;
在这里插入图片描述

  • 检查版本
    在这里插入图片描述
  • 配置npm 使用淘宝镜像 npm config set registry http://registry.npm.taobao.org/
    在这里插入图片描述

3.3.9 使用人人开源后台管理系统

去gitee上面搜索人人开源后台管理系统,
在这里插入图片描述
在这里插入图片描述

  • 下载 Git clone到本地
    在这里插入图片描述
  • 将 renren-fast复制到gulimall项目下。并删除掉其中的.git文件夹。
    在这里插入图片描述

在这里插入图片描述
renren-fast-vue到时候用vscode打开,不过一样要删掉里面的git 文件。

  • idea中修改renren-fast配置
    从模块中不难看出,已经自带了sql文件,所以我们去mysql中创建一个数据库,sql即为该模块下自带的sql.

在这里插入图片描述

在这里插入图片描述

  • application-dev.yml文件中修改成我们自己ip地址,数据库连接。
    在这里插入图片描述
  • 启动renren-fast模块
    在这里插入图片描述

在这里插入图片描述
测试后端,正常。
接下来是前端页面。

  • 前端联调。将renren-fast-vue用vs code打开。
    在这里插入图片描述
    控制台输入 npm install ,如果出现错误,关闭vs code之后重新打开即可。
    在这里插入图片描述
    在这里插入图片描述
    然后输入 npm run dev 进行前后端联调。
    在这里插入图片描述
    在这里插入图片描述

3.6 使用代码生成器快速CRUD

3.6.1 以gulimall_pms为例,对应的是product微服务。

  1. 在renren-generator这个模块下修改成我们要连接的数据库名称,账户和密码。
    在这里插入图片描述
  2. 在配置文件中修改成我们要使用代码生成器生成的代码。
    在这里插入图片描述
  3. 浏览器中访问代码生成器,代码生成器会根据配置文件帮我们生成,然后我们全选下载。下载之后是压缩包,我们可以将压缩包解压。
    在这里插入图片描述
  4. 解压之后里面帮我们把代码和sql也一起生成了,main中是代码。
    在这里插入图片描述
    在这里插入图片描述
  • main->resources->src里面的前端代码我们暂时不需要。
    在这里插入图片描述
    在这里插入图片描述

这个里面帮我们也生成了vue代码,目前暂时用不到所以我们需要将其删除。
5. 创建gulimall-common 工程
每一个微服务都需要引入一些相同的依赖和类。我们索性创建一个gulimall-common工程,到时候其他微服务只要引入这个依赖就可以通过maven的传递性引入依赖。
在这里插入图片描述
用代码生成器帮我们生成的这些代码,还多依赖还没有,所以我们需要进行一个微服务公共依赖的导入。
在这里插入图片描述
在这里插入图片描述

3.3.10 gulimall-common依赖引入

  1. gulimall-common中,引入mybatis-plus的依赖。
    在这里插入图片描述

  2. 我们发现controller中有这个@RequiresPermissions这个注解,我们暂时不需要这个。如果我们只是简单的将其在一个微服务的controller中注解掉,那么其他的微服务怎么办呢?
    在这里插入图片描述

  3. 我们直接在代码生成器中controller中的这个注解直接给注释掉。这样以后用这个代码生成器的就不会每个微服务都有了。
    在这里插入图片描述
    修改之后重启代码生成器,然后将其生成的代码生成器的代码中的controller重新复制到gulimall-product模块下。

  4. 整合mybatis-plus

在这里插入图片描述

  • 查看mysql
    在这里插入图片描述
    根据版本找对应的mysql的依赖。其实mysql的依赖的很多都可以直接使用最新的。
<!--导入mysql驱动-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
  • 引入servlet-api依赖
    在这里插入图片描述
  • 有些暂时用不掉的我们可以删掉。
    在这里插入图片描述
    在这里插入图片描述
    我们到时候不用这个。
    在这里插入图片描述
  • 创建application.yml中配置数据源

    在这里插入图片描述
  • 注意这里会一个这个会报错。
    在这里插入图片描述
    最开始我的解决办法是:将接口放到utils下面。但是这个解决办法应该是错误的,因为工具类总一般是不会放置接口。
    在这里插入图片描述
    暂时解决办法是:直接导入renren这个依赖
    在这里插入图片描述
    在gulimall-product中简单进行测试。
    在这里插入图片描述
    测试通过。
    在这里插入图片描述
    在数据库中查看是否保存成功。
    在这里插入图片描述
    在这里插入图片描述
    同时也可以测试其他的
    在这里插入图片描述
  • 同理快速生成其他微服务。比如说gulimall-sms这个优惠服务。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    测试:
    在这里插入图片描述
  • ums 用户微服务
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 对每一个微服务设置端口
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    测试
  • 注意这里的product端口改为的是11000,因为深信服vpn占用了10000端口,ware改为12000端口。
  • 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • wms 仓储微服务
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    测试
    在这里插入图片描述

4 springcloud alibaba

4.1 SpringCloud Alibaba-Nacos[作为注册中心]

4.1.1 下载

地址:https://github.com/alibaba/nacos/releases?page=3 下载这个版本的保持和老师教课一样,避免其他版本问题。
在这里插入图片描述

4.1.2 启动nacos:

  • 双击bin 中的startup.cmd 文件
  • 访问http://localhost:8848/nacos/
  • 使用默认的nacos/nacos 进行登录

在这里插入图片描述

4.1.3 将微服务注册到nacos中

  • 首先,修改pom.xml 文件,引入Nacos Discovery Starter
<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 在应用的/src/main/resources/application.properties 配置文件中配置Nacos Server 地址
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

  • 使用@EnableDiscoveryClient 开启服务注册发现功能
    在这里插入图片描述

  • 启动应用,观察nacos 服务列表是否已经注册上服务.

Nacos 使用三步
1、导包nacos-discovery
2、写配置,指定nacos 地址,指定应用的名字
3、开启服务注册发现功能@EnableDiscoveryClient

在这里插入图片描述

4.2 注册更多的服务上去,测试使用feign 远程调用

在这里插入图片描述
本次以gulimall-coupon远程调用gulimall-member中的方法为例。

4.2.1 首先在gulimall-coupon中的一个controller方法中写一个测试方法,以供被调用。

在这里插入图片描述

4.2.2 在gulimall-coupon中写一个feign接口,我们将这个接口都放在feign中。

@FeifnClient注解:告诉springcloud这个接口是一个远程客户端,它要调用远程服务。
在这里插入图片描述

4.2.3 在gulimall-coupon的controller方法中调用feign接口。

在这里插入图片描述

4.2.4 在调用微服务gulimall-coupon中的启动类上加注解@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign" )

在这里插入图片描述

4.2.5 启动这两个微服务。

记住:

  • 遇到的坑:SpringCloud启动报错Did you forget to include spring-cloud-starter-loadbalancer

添加匹配当前OpenFeign的负载均衡依赖

由于SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错
解决方法 : 加入spring-cloud-loadbalancer依赖, 并且在nacos中排除ribbon依赖,不然loadbalancer无效

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.netflix.ribbon</groupId>
                    <artifactId>ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>3.1.1</version>
        </dependency>

gulimall-comon中添加以上注解解决问题。再次进行测试,如下图,测试成功。

在这里插入图片描述

Feign 使用三步
1、导包openfeign
2、开启@EnableFeignClients 功能
3、编写接口,进行远程调用

4.3 SpringCloud Alibaba-Nacos[作为配置中心]

在这里插入图片描述

4.3.1 pom.xml 引入Nacos Config Starter

<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

4.3.2 在应用的/src/main/resources/bootstrap.properties 配置文件中配置Nacos Config 元数据

spring.application.name=nacos-config-example
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
主要配置应用名和配置中心地址

4.3.3 在nacos 中添加配置

在application.properties中:写这个,先测试。
在这里插入图片描述

在nacos 中创建一个应用名.properties 配置文件并编写配置
在这里插入图片描述

Nacos Config 数据结构
Nacos Config 主要通过dataId 和group 来唯一确定一条配置。
Nacos Client 从Nacos Server 端获取数据时,调用的是此接口ConfigService.getConfig(String dataId, String group, long timeoutMs)。

在这里插入图片描述

4.3.4 在应用中使用@Value 和@RefreshScope

完成上述两步后,应用会从Nacos Config 中获取相应的配置,并添加在Spring Environment的PropertySources 中。这里我们使用@Value 注解来将对应的配置注入到SampleController 的userName 和age 字段,并添加@RefreshScope 打开动态刷新功能。
在这里插入图片描述

4.3.5 测试

  • 坑,启动报错:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 踩坑 :配置中心不起作用,真的是这个问题。
    在这里插入图片描述

4.4 进阶

4.4.1 核心概念

在这里插入图片描述
在这里插入图片描述

4.4.2 原理

在这里插入图片描述

4.4.3 配置文件整合

我们将微服务中的配置文件进行整合。就是在nacos中每一个微服务都有自己的命名空间。每一个微服务的命名空间下有很多个配置列表,这些配置将我们原本在java本地的代码放到注册中心中,然后我们使用boostrap.properties来对命名空间进行设置。具体还可以进行分组。dev prod test这三组。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意如果在注册中心中找不到就去找默认的,默认的在注册中心中也找不到的时候,就会去找本地的。
在这里插入图片描述
在这里插入图片描述

4.5 前面遇到的坑的究极问题-------版本问题

在这里插入图片描述
突然发现前面为什么和老师一样的代码会报红了,原来是因为springcloud版本不一样导致。怪不得会莫名其妙的报错,为了以后能少点报错,将springboot和springcloud的版本都换成和老师一样的版本吧。

在这里插入图片描述

4.5.1 依赖没写错,更改版本仍然报错的解决方法---- 清除缓存

在这里插入图片描述
在这里插入图片描述

4.5.2 版本切换后测试

  1. 对于以前踩的坑的解决办法引入的在老师之外的依赖,尝试进行注释掉,测试能否正常运行。

4.6 springcloud ----- gateway---网关

4.6.1 简介

网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而springcloud gateway作为SpringCloud 官方推出的第二代网关框架,取代了Zuul 网关。
在这里插入图片描述
官网文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.3.RELEASE/single/spring-cloud-gateway.html
在这里插入图片描述
在这里插入图片描述

4.6.2 核心概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.6.2 使用

  1. 创建gulimall-gateway网关微服务

在这里插入图片描述
2. 加入gateway依赖
在这里插入图片描述
3. 编写网关配置文件
首先也要将其注册到nacos注册中心,并且为其创建单独的命名空间及端口号。

  • 断言(Predicates)
    在这里插入图片描述

  • 过滤器
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

id:路由的id,没有固定规则但要求唯一
uri:匹配后提供服务的路由地址
path:断言,路径相匹配的进行路由
Query:查询相匹配的进行路由

如果我们在地址栏中访问的地址有qq,或者baidu就会进行转发。
4. 启动测试

测试报错:

  • 网关启动报错 坑:Error creating bean with name 'routeDefinitionRouteLocator' defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]: Unsatisfied dependency expressed through method 'routeDefinitionRouteLocator' parameter 4; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.convert.ConversionService' available: expe

  • 网上搜索到的解决办法,我采用的是排除web内置容器的解决办法。

  • 解决办法:
    在这里插入图片描述
    1)解决办法1

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- Maven整个生命周期内排除内置容器,排除内置容器导出成war包可以让外部容器运行spring-boot项目-->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2)解决办法2 : 使用 spring-webflux 模块,webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

最后主启动类还要排除数据源:
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class,DataSourceAutoConfiguration.class})

排除的原因是我们将gateway也引入了gulimall-common,而这里面又有数据源配置(mybatis-plus),启动找不到对应的配置就会报错。解决办法就是排除数据源。

最后成功启动项目

在这里插入图片描述


5 前端

5.1 VSCode 使用

在这里插入图片描述
在这里插入图片描述
如果不想手动保存可以选择一下方法:
前端中Live Server 不起作用是因为需要设置自动保存代码或者是自己修改后手动保存才会热更新。
在设置中找到autosave,选择
在这里插入图片描述
这个即可,代码会自动保存,热更新插件会成功运行。

5.2 ES6

5.2.1 简介

在这里插入图片描述

5.2.2 什么是ECMAScript

在这里插入图片描述
在这里插入图片描述

5.2.3 ES6 新特性

快捷键:
shift + ! --- 快速生成html模板
代码格式化:shift + alt +f

1、 let 声明变量

// var 声明的变量往往会越域
// let 声明的变量有严格局部作用域
{
var a = 1;
let b = 2;
}
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
// var 可以声明多次
// let 只能声明一次
var m = 1
var m = 2
let n = 3
// let n = 4
console.log(m) // 2
console.log(n) // Identifier 'n' has already been declared
// var 会变量提升
// let 不存在变量提升
console.log(x); // undefined
var x = 10;
console.log(y); //ReferenceError: y is not defined
let y = 20;

2、 const 声明常量(只读变量)

// 1. 声明之后不允许改变
// 2. 一但声明必须初始化,否则会报错
const a = 1;
a = 3; //Uncaught TypeError: Assignment to constant variable.

3、解构表达式

1)、数组解构
let arr = [1,2,3];
//以前我们想获取其中的值,只能通过角标。ES6 可以这样:
const [x,y,z] = arr;// x,y,z 将与arr 中的每个位置对应来取值
// 然后打印
console.log(x,y,z);
2)、对象解构
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
// 解构表达式获取值,将person 里面每一个属性和左边对应赋值
const { name, age, language } = person;
// 等价于下面
// const name = person.name;
// const age = person.age;
// const language = person.language;
// 可以分别打印
console.log(name);
console.log(age);
console.log(language);
//扩展:如果想要将name 的值赋值给其他变量,可以如下,nn 是新的变量名
const { name: nn, age, language } = person;
console.log(nn);
console.log(age);
console.log(language);

4、字符串扩展

1)、几个新的API

ES6 为字符串扩展了几个新的API:

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let str = "hello.vue";
console.log(str.startsWith("hello"));//true
console.log(str.endsWith(".vue"));//true
console.log(str.includes("e"));//true
console.log(str.includes("hello"));//true
2)、字符串模板

模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。

// 1、多行字符串
let ss = `
<div>
<span>hello world<span>
</div>
`
console.log(ss)

// 2、字符串插入变量和表达式。变量名写在${} 中,${} 中可以放
入JavaScript 表达式。
let name = "张三";
let age = 18;
let info = `我是${name},今年${age}了`;
console.log(info)

// 3、字符串中调用函数
function fun() {
return "这是一个函数"
}
let sss = `O(∩_∩)O 哈哈~,${fun()}`;
console.log(sss); // O(∩_∩)O 哈哈~,这是一个函数

5、函数优化

1)、函数参数默认值
//在ES6 以前,我们无法给一个函数参数设置默认值,只能采用变通写法:
function add(a, b) {
// 判断b 是否为空,为空就给默认值1
b = b || 1;
return a + b;
}
// 传一个参数
console.log(add(10));
//现在可以这么写:直接给参数写上默认值,没传就会自动使用默认值
function add2(a , b = 1) {
return a + b;
}
// 传一个参数
console.log(add2(10));
2)、不定参数

不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数列表的最后,并且有且只有一个不定参数。

function fun(...values) {
console.log(values.length)
}
fun(1, 2) //2
fun(1, 2, 3, 4) //4
3)、箭头函数

ES6 中定义函数的简写方式:

  • 一个参数时:
//以前声明一个方法
// var print = function (obj) {
// console.log(obj);
// }
// 可以简写为:
var print = obj => console.log(obj);
// 测试调用
print(100);
  • 多个参数:
// 两个参数的情况:
var sum = function (a, b) {
return a + b;
}
// 简写为:
//当只有一行语句,并且需要返回结果时,可以省略{} , 结果会自动返回。
var sum2 = (a, b) => a + b;
//测试调用
console.log(sum2(10, 10));//20
// 代码不止一行,可以用`{}`括起来
var sum3 = (a, b) => {
c = a + b;
return c;
};
//测试调用
console.log(sum3(10, 20));//30
4)、实战:箭头函数结合解构表达式
//需求,声明一个对象,hello 方法需要对象的个别属性
//以前的方式:
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
function hello(person) {
console.log("hello," + person.name)
}
//现在的方式
var hello2 = ({ name }) => { console.log("hello," + name) };
//测试
hello2(person);

6、对象优化

1)、新增的API

ES6 给Object 拓展了许多新的方法,如:

  • keys(obj):获取对象的所有key 形成的数组
  • values(obj):获取对象的所有value 形成的数组
  • entries(obj):获取对象的所有key 和value 形成的二维数组。格式:[[k1,v1],[k2,v2],...]
  • assign(dest, ...src) :将多个src 对象的值拷贝到dest 中。(第一层为深拷贝,第二层为浅
    拷贝)
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
console.log(Object.keys(person));//["name", "age", "language"]
console.log(Object.values(person));//["jack", 21, Array(3)]
console.log(Object.entries(person));//[Array(2), Array(2), Array(2)]
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
//Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。
Object.assign(target, source1, source2);
console.log(target)//{a: 1, b: 2, c: 3}
2)、声明对象简写
const age = 23
const name = "张三"
// 传统
const person1 = { age: age, name: name }
console.log(person1)
// ES6:属性名和属性值变量名一样,可以省略
const person2 = { age, name }
console.log(person2) //{age: 23, name: "张三"}
3)、对象的函数属性简写
let person = {
name: "jack",
// 以前:
eat: function (food) {
console.log(this.name + "在吃" + food);
},
// 箭头函数版:这里拿不到this
eat2: food => console.log(person.name + "在吃" + food),
// 简写版:
eat3(food) {
console.log(this.name + "在吃" + food);
}
}
person.eat("apple");
4)、对象拓展运算符

拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。

// 1、拷贝对象(深拷贝)
let person1 = { name: "Amy", age: 15 }
let someone = { ...person1 }
console.log(someone) //{name: "Amy", age: 15}
// 2、合并对象
let age = { age: 15 }
let name = { name: "Amy" }
let person2 = { ...age, ...name } //如果两个对象的字段名重复,后面对象字
段值会覆盖前面对象的字段值
console.log(person2) //{age: 15, name: "Amy"}

7、map 和reduce

数组中新增了map 和reduce 方法。

1)、map

map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。

let arr = ['1', '20', '-5', '3'];
console.log(arr)
arr = arr.map(s => parseInt(s));
console.log(arr)	
2)、reduce

语法:
arr.reduce(callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元
素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调
用reduce 的数组。
callback (执行数组中每个值的函数,包含四个参数)
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用reduce 的数组)
initialValue (作为第一次调用callback 的第一个参数。)
示例:

const arr = [1,20,-5,3];
//没有初始值:
console.log(arr.reduce((a,b)=>a+b));//19
console.log(arr.reduce((a,b)=>a*b));//-300
//指定初始值:
console.log(arr.reduce((a,b)=>a+b,1));//20
console.log(arr.reduce((a,b)=>a*b,0));//-0

8、Promise

在JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。一旦有一连串的ajax 请求a,b,c,d... 后面的请求依赖前面的请求结果,就需要层层嵌套。这种缩进和层
层嵌套的方式,非常容易造成上下文代码混乱,我们不得不非常小心翼翼处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度就会加剧......总之,这种层叠上下文的层层嵌套方式,着实增加了神经的紧张程度。
案例:用户登录,并展示该用户的各科成绩。在页面发送两次请求:

  1. 查询用户,查询成功说明可以登录
  2. 查询用户成功,查询科目
  3. 根据科目的查询结果,获取去成绩
    分析:此时后台应该提供三个接口,一个提供用户查询接口,一个提供科目的接口,一个提供各科成绩的接口,为了渲染方便,最好响应json 数据。在这里就不编写后台接口了,而是提供三个json 文件,直接提供json 数据,模拟后台接口:
user.json:
{
"id": 1,
"name": "zhangsan",
"password": "123456"
}
user_corse_1.json:
{
"id": 10,
"name": "chinese"
}
corse_score_10.json:
{
"id": 100,
"score": 90
}
//回调函数嵌套的噩梦:层层嵌套。
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询用户:", data);
$.ajax({
url: `mock/user_corse_${data.id}.json`,
success(data) {
console.log("查询到课程:", data);
$.ajax({
url: `mock/corse_score_${data.id}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});

我们可以通过Promise 解决以上问题。

1)、Promise 语法
const promise = new Promise(function (resolve, reject) {
// 执行异步操作
if (/* 异步操作成功*/) {
resolve(value);// 调用resolve,代表Promise 将返回成功的结果
} else {
reject(error);// 调用reject,代表Promise 会返回失败结果
}
});

使用箭头函数可以简写为:

const promise = new Promise((resolve, reject) =>{
// 执行异步操作
if (/* 异步操作成功*/) {
resolve(value);// 调用resolve,代表Promise 将返回成功的结果
} else {
reject(error);// 调用reject,代表Promise 会返回失败结果
}
});

这样,在promise 中就封装了一段异步执行的结果。

2)、处理异步结果

如果我们想要等待异步执行完成,做一些事情,我们可以通过promise 的then 方法来实现。
如果想要处理promise 异步执行失败的事件,还可以跟上catch:

promise.then(function (value) {
// 异步执行成功后的回调
}).catch(function (error) {
// 异步执行失败后的回调
})
3)、Promise 改造以前嵌套方式
new Promise((resolve, reject) => {
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询用户:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
}).then((userId) => {
return new Promise((resolve, reject) => {
$.ajax({
url: `mock/user_corse_${userId}.json`,
success(data) {
console.log("查询到课程:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});
}).then((corseId) => {
console.log(corseId);
$.ajax({
url: `mock/corse_score_${corseId}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});
4)、优化处理

优化:通常在企业开发中,会把promise 封装成通用方法,如下:封装了一个通用的get 请求方法

let get = function (url, data) { // 实际开发中会单独放到common.js 中
return new Promise((resolve, reject) => {
$.ajax({
url: url,
type: "GET",
data: data,
success(result) {
resolve(result);
},
error(error) {
reject(error);
}
});
})
}
// 使用封装的get 方法,实现查询分数
get("mock/user.json").then((result) => {
console.log("查询用户:", result);
return get(`mock/user_corse_${result.id}.json`);
}).then((result) => {
console.log("查询到课程:", result);
return get(`mock/corse_score_${result.id}.json`)
}).then((result) => {
console.log("查询到分数:", result);
}).catch(() => {
console.log("出现异常了:" + error);
});

通过比较,我们知道了Promise 的扁平化设计理念,也领略了这种上层设计带来的好处。
我们的项目中会使用到这种异步处理的方式;

9、模块化

1)、什么是模块化

模块化就是把代码进行拆分,方便重复利用。类似java 中的导包:要使用一个包,必须先导包。而JS 中没有包的概念,换来的是模块。
模块功能主要由两个命令构成:exportimport

  • export命令用于规定模块的对外接口。
  • import命令用于导入其他模块提供的功能。
2)、export

比如我定义一个js 文件:hello.js,里面有一个对象:

const util = {
sum(a,b){
return a + b;
}
}

我可以使用export 将这个对象导出:

const util = {
sum(a,b){
return a + b;
}
}
export {util};

当然,也可以简写为:

export const util = {
sum(a,b){
return a + b;
}
}

export不仅可以导出对象,一切JS 变量都可以导出。比如:基本类型变量、函数、数组、对象。
当要导出多个值时,还可以简写。比如我有一个文件:user.js:

var name = "jack"
var age = 21
export {name,age}

省略名称
上面的导出代码中,都明确指定了导出的变量名,这样其它人在导入使用时就必须准确写出
变量名,否则就会出错。
因此js 提供了default关键字,可以对导出的变量名进行省略
例如:

// 无需声明对象的名字
export default {
sum(a,b){
return a + b;
}
}

这样,当使用者导入时,可以任意起名字。

3)、import

使用export命令定义了模块的对外接口以后,其他JS 文件就可以通过import命令加载这个模块。
例如我要使用上面导出的util:

// 导入util
import util from 'hello.js'
// 调用util 中的属性
util.sum(1,2)

要批量导入前面导出的name 和age:

import {name, age} from 'user.js'
console.log(name + " , 今年"+ age +"岁了")

但是上面的代码暂时无法测试,因为浏览器目前还不支持ES6 的导入和导出功能。除非借助于工具,把ES6 的语法进行编译降级到ES5,比如Babel-cli工具我们暂时不做测试,大家了解即可。

5.3 Node.js

前端开发,少不了node.js;Node.js 是一个基于Chrome V8 引擎的JavaScript 运行环境。
http://nodejs.cn/api/
我们关注与node.js 的npm 功能就行;
NPM 是随同NodeJS 一起安装的包管理工具,JavaScript-NPM,Java-Maven;

1)、官网下载安装node.js,并使用node -v 检查版本
2)、配置npm 使用淘宝镜像
npm config set registry http://registry.npm.taobao.org/
3)、大家如果npm install 安装依赖出现chromedriver 之类问题,先在项目里运行下面命令
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
然后再运行npm install

5.4 Vue

1、MVVM 思想

  • M:即Model,模型,包括数据和一些基本操作
  • V:即View,视图,页面渲染结果
  • VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉)
    在MVVM 之前,开发人员从后端获取需要的数据模型,然后要通过DOM 操作Model 渲染到View 中。而后当用户操作视图,我们还需要通过DOM 获取View 中的数据,然后同步到Model 中。

而MVVM 中的VM 要做的事情就是把DOM 操作完全封装起来,开发人员不用再关心Model和View 之间是如何互相影响的:

  • 只要我们Model 发生了改变,View 上自然就会表现出来。
  • 当用户修改了View,Model 中的数据也会跟着改变。
    把开发人员从繁琐的DOM 操作中解放出来,把关注点放在如何操作Model 上。

在这里插入图片描述

2、Vue 简介

Vue (读音/vjuː/,类似于view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

官网:https://cn.vuejs.org/
参考:https://cn.vuejs.org/v2/guide/
Git 地址:https://github.com/vuejs

3、入门案例

1)、安装

官网文档提供了3 种安装方式:

  1. 直接script 引入本地vue 文件。需要通过官网下载vue 文件。
  2. 通过script 引入CDN 代理。需要联网,生产环境可以使用这种方式
  3. 通过npm 安装。这种方式也是官网推荐的方式,需要nodejs 环境。
    本课程就采用第三种方式

在这里插入图片描述
在这里插入图片描述
这里没有vue.js文件的,vue版本错了,npm install vue@2 重装一下,npm默认装的是vue3.0

2)、创建示例项目

1、新建文件夹hello-vue,并使用vscode 打开
2、使用vscode 控制台,npm install -y;
项目会生成package-lock.json 文件,类似于maven 项目的pom.xml 文件。
3、使用npm install vue,给项目安装vue;项目下会多node_modules 目录,并且在下面有
一个vue 目录。
在这里插入图片描述

3)、HelloWorld

在hello.html 中,我们编写一段简单的代码。
h2 中要输出一句话:xx 非常帅。前面的xx是要渲染的数据。
在这里插入图片描述

4)、vue 声明式渲染

页面代码

 <div id="app">
            <h1>{{name}},非常帅!!!</h1>
        </div>
        <script src="./node_modules/vue/dist/vue.min.js"></script>
        <script>
            let vm = new Vue({
                el: "#app",
                data: {
                    name: "张三"
                }
            });
        </script>
    </body>

在这里插入图片描述

5)、双向绑定

我们对刚才的案例进行简单修改:

<div id="app">
            <input type="text" v-model="num">
            <h2>
                {{name}},非常帅!!!有{{num}}个人为他点赞。
            </h2>
        </div>
        <script src="./node_modules/vue/dist/vue.js"></script>
        <script>
            // 创建vue 实例
            let app = new Vue({
                el: "#app", // el 即element,该vue 实例要渲染的页面元素
                data: { // 渲染页面需要的数据
                    name: "张三",
                    num: 5
                }
            });
        </script>

在这里插入图片描述

6)、事件处理

给页面添加一个按钮:

<body>
 <div id="app">
            <input type="text" v-model="num">
            <button v-on:click="num++">关注</button>
            <h2>
                {{name}},非常帅!!!有{{num}}个人为他点赞。
            </h2>
        </div>
        <script src="./node_modules/vue/dist/vue.js"></script>
        <script>
            // 创建vue 实例
            let app = new Vue({
                el: "#app", // el 即element,该vue 实例要渲染的页面元素
                data: { // 渲染页面需要的数据
                    name: "张三",
                    num: 5
                }
            });
        </script>
</body>

在这里插入图片描述
在这里插入图片描述

简单使用总结:
1)、使用Vue 实例管理DOM
2)、DOM 与数据/事件等进行相关绑定
3)、我们只需要关注数据,事件等处理,无需关心视图如何进行修改

4、概念

1、创建Vue 实例

每个Vue 应用都是通过用Vue 函数创建一个新的Vue 实例开始的:

let app = new Vue({
});

在构造函数中传入一个对象,并且在对象中声明各种Vue 需要的数据和方法,包括:

  • el
  • data
  • methods

2、模板或元素

每个Vue 实例都需要关联一段Html 模板,Vue 会基于此模板进行视图渲染。
我们可以通过el 属性来指定。
例如一段html 模板:

<div id="app">
</div>

然后创建Vue 实例,关联这个div

let vm = new Vue({
el: "#app"
})

这样,Vue 就可以基于id 为app的div 元素作为模板进行渲染了。在这个div 范围以外的部
分是无法使用vue 特性的。

3、数据

当Vue 实例被创建时,它会尝试获取在data 中定义的所有属性,用于视图的渲染,并且监视data 中的属性变化,当data 发生改变,所有相关的视图都将重新渲染,这就是“响应式“系统。
html:

<div id="app">
<input type="text" v-model="name" />
</div>

JS:

let vm = new Vue({
el: "#app",
data: {
name: "刘德华"
}
})

4、方法

Vue 实例中除了可以定义data 属性,也可以定义方法,并且在Vue 实例的作用范围内使用。
Html:

<div id="app">
{{num}}
<button v-on:click="add">加</button>
</div>

JS:

let vm = new Vue({
el: "#app",
data: {
num: 0
},
methods: {
add: function () {
// this 代表的当前vue 实例
this.num++;
}
}
})

5、安装vue-devtools 方便调试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、安装vscode 的vue 插件

在这里插入图片描述
安装这个插件就可以有语法提示

5、指令

什么是指令?

  • 指令(Directives) 是带有v- 前缀的特殊特性。
  • 指令特性的预期值是:单个JavaScript 表达式。
  • 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM
    例如我们在入门案例中的v-on,代表绑定事件。

1、插值表达式

1)、花括号

格式:{{表达式}}
说明:

  • 该表达式支持JS 语法,可以调用js 内置函数(必须有返回值)
  • 表达式必须有返回结果。例如1 + 1,没有结果的表达式不允许使用,如:let a = 1 + 1;
  • 可以直接获取Vue 实例中定义的数据或函数
2)、插值闪烁

使用{{}}方式在网速较慢时会出现问题。在数据未加载完成时,页面会显示出原始的{{}},加载完毕后才显示正确数据,我们称为插值闪烁。
我们将网速调慢一些,然后刷新页面,试试看刚才的案例:
在这里插入图片描述

3)、v-text 和v-html

可以使用v-text 和v-html 指令来替代{{}}
说明:

  • v-text:将数据输出到元素内部,如果输出的数据有HTML 代码,会作为普通文本输出
  • v-html:将数据输出到元素内部,如果输出的数据有HTML 代码,会被渲染
    示例:
<div id="app">
v-text:<span v-text="hello"></span> <br />
v-html:<span v-html="hello"></span>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
hello: "<h1>大家好</h1>"
}
})
</script>

效果:
在这里插入图片描述

并且不会出现插值闪烁,当没有数据时,会显示空白或者默认数据。

2、v-bind

html 属性不能使用双大括号形式绑定,我们使用v-bind 指令给HTML 标签属性绑定值;
而且在将v-bind 用于classstyle 时,Vue.js 做了专门的增强。

1)、绑定class
<div class="static" v-bind:class="{ active: isActive, 'text-danger'
        : hasError }">
        </div>
        <script>
            let vm = new Vue({
                el: "#app",
                data: {
                    isActive: true,
                    hasError: false
                }
            })
        </script>
2)、绑定style

v-bind:style 的对象语法十分直观,看着非常像CSS,但其实是一个JavaScript 对象。style属性名可以用驼峰式(camelCase) 或短横线分隔(kebab-case,这种方式记得用单引号括起来) 来命名。
例如:font-size-->fontSize

<div id="app" v-bind:style="{ color: activeColor, fontSize: fontSiz
            e + 'px' }"></div>
        <script>
            let vm = new Vue({
                el: "#app",
                data: {
                    activeColor: 'red',
                    fontSize: 30
                }
            })
        </script>

结果:

3)、绑定其他任意属性
<div id="app" v-bind:style="{ color: activeColor, fontSize: fontS
            ize + 'px' }" v-bind:user="userName">
        </div>
        <script>
            let vm = new Vue({
                el: "#app",
                data: {
                    activeColor: 'red',
                    fontSize: 30,
                    userName: 'zhangsan'
                }
            })
        </script>
4)、v-bind 缩写
<div id="app" :style="{ color: activeColor, fontSize: fontSize +
            'px' }" :user="userName">
        </div>

3、v-model

刚才的v-text、v-html、v-bind 可以看做是单向绑定,数据影响了视图渲染,但是反过来就不
行。接下来学习的v-model 是双向绑定,视图(View)和模型(Model)之间会互相影响。
既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。目前
v-model 的可使用元素有:

  • input
  • select
  • textarea
  • checkbox
  • radio
  • components(Vue 中的自定义组件)
    基本上除了最后一项,其它都是表单的输入项。
    示例:
<div id="app">
            <input type="checkbox" v-model="language" value="Java" />Java<br />
            <input type="checkbox" v-model="language" value="PHP" />PHP<br />
            <input type="checkbox" v-model="language" value="Swift" />Swift<br />
            <h1>
                你选择了:{{language.join(',')}}
            </h1>
        </div>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script type="text/javascript">
            let vm = new Vue({
                el: "#app",
                data: {
                    language: []
                }
            })
        </script>
  • 多个CheckBox对应一个model 时,model 的类型是一个数组,单个checkbox 值默认是boolean 类型
  • radio 对应的值是input 的value 值
  • texttextarea 默认对应的model 是字符串
  • select单选对应字符串,多选对应也是数组
    在这里插入图片描述

4、v-on

1、基本用法

v-on 指令用于给页面元素绑定事件。
语法: v-on:事件名="js 片段或函数名"
示例:

 <div id="app">
            <!--事件中直接写js 片段-->
            <button v-on:click="num++">点赞</button>
            <!--事件指定一个回调函数,必须是Vue 实例中定义的函数-->
            <button v-on:click="decrement">取消</button>
            <h1>有{{num}}个赞</h1>
        </div>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script type="text/javascript">
            let vm = new Vue({
                el: "#app",
                data: {
                    num: 100
                },
                methods: {
                    decrement() {
                        this.num--; //要使用data 中的属性,必须this.属性名
                    }
                }
            })
        </script>

另外,事件绑定可以简写,例如v-on:click='add'可以简写为@click='add'

2、事件修饰符

在事件处理程序中调用event.preventDefault()event.stopPropagation() 是非常常见的
需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,
而不是去处理DOM 事件细节。
为了解决这个问题,Vue.js 为v-on 提供了事件修饰符。修饰符是由点开头的指令后缀来
表示的。

  • .stop :阻止事件冒泡到父元素
  • .prevent:阻止默认事件发生
  • .capture:使用事件捕获模式
  • .self:只有元素自身触发事件才执行。(冒泡或捕获的都不执行)
  • .once:只执行一次
<div id="app">
            <!--右击事件,并阻止默认事件发生-->
            <button v-on:contextmenu.prevent="num++">点赞</button>
            <br />
            <!--右击事件,不阻止默认事件发生-->
            <button v-on:contextmenu="decrement($event)">取消</button>
            <br />
            <h1>有{{num}}个赞</h1>
        </div>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script type="text/javascript">
            let app = new Vue({
                el: "#app",
                data: {
                    num: 100
                },
                methods: {
                    decrement(ev) {
                        // ev.preventDefault();
                        this.num--;
                    }
                }
            })
        </script>

效果:右键“点赞”,不会触发默认的浏览器右击事件;右键“取消”,会触发默认的浏览器右击事件)

3. 按键修饰符

在这里插入图片描述

4、组合按钮

在这里插入图片描述
在这里插入图片描述

5、v-for

遍历数据渲染页面是非常常用的需求,Vue 中通过v-for 指令来实现。

1、遍历数组

语法:v-for="item in items"

  • items:要遍历的数组,需要在vue 的data 中定义好。
  • item:迭代得到的当前正在遍历的元素
    示例:
<div id="app">
            <ul>
                <li v-for="user in users">
                    {{user.name}} - {{user.gender}} - {{user.age}}
                </li>
            </ul>
        </div>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script type="text/javascript">
            let app = new Vue({
                el: "#app",
                data: {
                    users: [
                        { name: '柳岩', gender: '女', age: 21 },
                        { name: '张三', gender: '男', age: 18 },
                        { name: '范冰冰', gender: '女', age: 24 },
                        { name: '刘亦菲', gender: '女', age: 18 },
                        { name: '古力娜扎', gender: '女', age: 25 }
                    ]
                },
            })
        </script>

效果:
在这里插入图片描述

2、数组角标

在这里插入图片描述

3、遍历对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、Key

在这里插入图片描述
在这里插入图片描述

6、v-if 和v-show

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7、v-else 和v-else-if

v-else 元素必须紧跟在带v-if 或者v-else-if 的元素的后面,否则它将不会被识别。
示例:

<div id="app">
            <button v-on:click="random=Math.random()">点我呀
            </button><span>{{random}}</span>
            <h1 v-if="random >= 0.75">
                看到我啦?!v-if >= 0.75
            </h1>
            <h1 v-else-if="random > 0.5">
                看到我啦?!v-else-if > 0.5
            </h1>
            <h1 v-else-if="random > 0.25">
                看到我啦?!v-else-if > 0.25
            </h1>
            <h1 v-else>
                看到我啦?!v-else
            </h1>
        </div>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script type="text/javascript">
            let app = new Vue({
                el: "#app",
                data: {
                    random: 1
                }
            })
        </script>

6、计算属性和侦听器

1、计算属性(computed)

某些结果是基于之前数据实时计算出来的,我们可以利用计算属性。来完成
示例:

 <div id="app">
        <ul>
            <li>西游记:价格{{xyjPrice}},数量:
                <input type="number" v-model="xyjNum">
            </li>
            <li>水浒传:价格{{shzPrice}},数量:
                <input type="number" v-model="shzNum">
            </li>
            <li>总价:{{totalPrice}}</li>
        </ul>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        let app = new Vue({
            el: "#app",
            data: {
                xyjPrice: 56.73,
                shzPrice: 47.98,
                xyjNum: 1,
                shzNum: 1
            },
            computed: {
                totalPrice() {
                    return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
                }
            },
        })
    </script>

在这里插入图片描述

2、侦听(watch)

watch 可以让我们监控一个值的变化。从而做出相应的反应。
示例:

 <div id="app">
        <ul>
            <li>西游记:价格{{xyjPrice}},数量:
                <input type="number" v-model="xyjNum">
            </li>
            <li>水浒传:价格{{shzPrice}},数量:
                <input type="number" v-model="shzNum">
            </li>
            <li>总价:{{totalPrice}}</li>
            {{msg}}
        </ul>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        let app = new Vue({
            el: "#app",
            data: {
                xyjPrice: 56.73,
                shzPrice: 47.98,
                xyjNum: 1,
                shzNum: 1,
                msg: ""
            },
            computed: {
                totalPrice() {
                    return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
                }
            },
            watch: {
                xyjNum(newVal, oldVal) {
                    if (newVal >= 3) {
                        this.msg = "西游记没有更多库存了";
                        this.xyjNum = 3;
                    } else {
                        this.msg = "";
                    }
                }
            }
        })
    </script>

在这里插入图片描述

3、过滤器(filters)

在这里插入图片描述

<body>
            <div id="app">
                <table>
                    <tr v-for="user in userList">
                        <td>{{user.id}}</td>
                        <td>{{user.name}}</td>
                        <!-- 使用代码块实现,有代码侵入-->
                        <td>{{user.gender===1? "男":"女"}}</td>
                    </tr>
                </table>
            </div>
        </body>
        <script src="../node_modules/vue/dist/vue.js"></script>
        <script>
            let app = new Vue({
                el: "#app",
                data: {
                    userList: [
                        { id: 1, name: 'jacky', gender: 1 },
                        { id: 2, name: 'peter', gender: 0 }
                    ]
                }
            });
        </script>
1、局部过滤器

注册在当前vue 实例中,只有当前实例能用

let app = new Vue({
                el: "#app",
                data: {
                    userList: [
                        { id: 1, name: 'jacky', gender: 1 },
                        { id: 2, name: 'peter', gender: 0 }
                    ]
                },
                // filters 定义局部过滤器,只可以在当前vue 实例中使用
                filters: {
                    genderFilter(gender) {
                        return gender === 1 ? '男~' : '女~'
                    }
                }
            });

在这里插入图片描述

2、全局过滤器

在这里插入图片描述

7、组件化

在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。
但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。在vue 里,所有的vue 实例都是组件。
在这里插入图片描述

1、全局组件

我们通过Vue 的component 方法来定义一个全局组件。

 <div id="app">
        <!--使用定义好的全局组件-->
        <counter></counter>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        // 定义全局组件,两个参数:1,组件名称。2,组件参数
        Vue.component("counter", {
            template: '<button v-on:click="count++">你点了我{{ count }} 次,我记住了.</button>',
            data() {
                return {
                    count: 0
                }
            }
        })
        let app = new Vue({
            el: "#app"
        })
    </script>

在这里插入图片描述

2、组件的复用

定义好的组件,可以任意复用多次:

<div id="app">
<!--使用定义好的全局组件-->
<counter></counter>
<counter></counter>
<counter></counter>
</div>

在这里插入图片描述

3、局部组件

一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着Vue 的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册。
我们先在外部定义一个对象,结构与创建组件时传递的第二个参数一致:
在这里插入图片描述

在这里插入图片描述

8、生命周期钩子函数

1、生命周期

每个Vue 实例在被创建时都要经过一系列的初始化过程:创建实例,装载模板,渲染模板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当Vue 实例处于不同的生命周期时,对应的函数就会被触发调用。
生命周期:你不需要立马弄明白所有的东西。
在这里插入图片描述

2、钩子函数

在这里插入图片描述
示例

<body>
    <div id="app">
        <span id="num">{{num}}</span>
        <button v-on:click="num++">赞!</button>
        <h2>
            {{name}},非常帅!!!有{{num}}个人点赞。
        </h2>
    </div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
    let app = new Vue({
        el: "#app",
        data: {
            name: "张三",
            num: 100
        },
        methods: {
            show() {
                return this.name;
            },
            add() {
                this.num++;
            }
        },
        beforeCreate() {
            console.log("=========beforeCreate=============");
            console.log("数据模型未加载:" + this.name, this.num);
            console.log("方法未加载:" + this.show());
            console.log("html 模板未加载:" + document.getElementById("num"));
        },
        created: function () {
            console.log("=========created=============");
            console.log("数据模型已加载:" + this.name, this.num);
            console.log("方法已加载:" + this.show());
            console.log("html 模板已加载:" + document.getElementById("num"));
            console.log("html 模板未渲染:" + document.getElementById("num").innerText);
        },
        beforeMount() {
            console.log("=========beforeMount=============");
            console.log("html 模板未渲染:" + document.getElementById("num").innerText);
        },
        mounted() {
            console.log("=========mounted=============");
            console.log("html 模板已渲染:" + document.getElementById("num").innerText);
        },
        beforeUpdate() {
            console.log("=========beforeUpdate=============");
            console.log("数据模型已更新:" + this.num);
            console.log("html 模板未更新:" + document.getElementById("num").innerText);
        },
        updated() {
            console.log("=========updated=============");
            console.log("数据模型已更新:" + this.num);
            console.log("html 模板已更新:" + document.getElementById("num").innerText);
        }
    });
</script>

</body>

在这里插入图片描述

9、vue 模块化开发

1、npm install webpack -g

全局安装webpack

2、npm install -g @vue/cli-init

全局安装vue 脚手架

3、初始化vue 项目

vue init webpack appname:vue 脚手架使用webpack 模板初始化一个appname 项目
在这里插入图片描述
在这里插入图片描述

4、启动vue 项目;

在这里插入图片描述
在这里插入图片描述
项目的package.json 中有scripts,代表我们能运行的命令
npm start = npm run dev:启动项目
npm run build:将项目打包

5、模块化开发

1、项目结构

在这里插入图片描述
在这里插入图片描述

2、Vue 单文件组件

Vue 单文件组件模板有三个部分;
在这里插入图片描述

3、vscode 添加用户代码片段(快速生成vue 模板)

在这里插入图片描述

{
"生成vue 模板": {
"prefix": "vue",
"body": [
"<template>",
"<div></div>",
"</template>",
"",
"<script>",
"//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json
文件,图片文件等等)",
"//例如:import 《组件名称》from '《组件路径》';",
"",
"export default {",
"//import 引入的组件需要注入到对象中才能使用",
"components: {},",
"props: {},",
"data() {",
"//这里存放数据",
"return {",
"",
"};",
"},",
"//计算属性类似于data 概念",
"computed: {},",
"//监控data 中的数据变化",
"watch: {},",
"//方法集合",
"methods: {",
"",
"},",
"//生命周期- 创建完成(可以访问当前this 实例)",
"created() {",
"",
"},",
"//生命周期- 挂载完成(可以访问DOM 元素)",
"mounted() {",
"",
"},",
"beforeCreate() {}, //生命周期- 创建之前",
"beforeMount() {}, //生命周期- 挂载之前",
"beforeUpdate() {}, //生命周期- 更新之前",
"updated() {}, //生命周期- 更新之后",
"beforeDestroy() {}, //生命周期- 销毁之前",
"destroyed() {}, //生命周期- 销毁完成",
"activated() {}, //如果页面有keep-alive 缓存功能,这个函数会触发
",
"}",
"</script>",
"<style lang='scss' scoped>",
"//@import url($3); 引入公共css 类",
"$4",
"</style>"
],
"description": "生成vue 模板"
}
}
4、导入element-ui 快速开发
  1. 安装element-ui: npm i element-ui
  2. 在main.js 中引入element-ui 就可以全局使用了。
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    Vue.use(ElementUI)
  3. 将App.vue 改为element-ui 中的后台布局
  4. 添加测试路由、组件,测试跳转逻辑
    (1) 、参照文档el-menu 添加router 属性
    (2) 、参照文档el-menu-item 指定index 需要跳转的地址

6 商品服务

6.1 三级分类

image-20221029090944745

商城的商品页面展示是一个三级分类的。有一级分类、二级分类、三级分类。这就是我们接下来要进行的操作。

表名解释

image-20221116142746821

数据库中表名 对应表
pms_attr 属性表
pms_attr_attrgroup_relation 属性&属性分组关联表
pms_attr_group 属性分组表
pms_brand 商品品牌表
pms_category 商品三级分类表

6.1.1 数据库

  • 首先我们在gulimall_pms这个数据库中的pms_category这个表下插入数据
DROP TABLE IF EXISTS `pms_category`;

CREATE TABLE `pms_category` (
  `cat_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id',
  `name` char(50) DEFAULT NULL COMMENT '分类名称',
  `parent_cid` bigint(20) DEFAULT NULL COMMENT '父分类id',
  `cat_level` int(11) DEFAULT NULL COMMENT '层级',
  `show_status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  `icon` char(255) DEFAULT NULL COMMENT '图标地址',
  `product_unit` char(50) DEFAULT NULL COMMENT '计量单位',
  `product_count` int(11) DEFAULT NULL COMMENT '商品数量',
  PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';

/*Data for the table `pms_category` */

insert  into `pms_category`(`cat_id`,`name`,`parent_cid`,`cat_level`,`show_status`,`sort`,`icon`,`product_unit`,`product_count`) values (1,'图书、音像、电子书刊',0,1,1,0,NULL,NULL,0),(2,'手机',0,1,1,0,NULL,NULL,0),(3,'家用电器',0,1,1,0,NULL,NULL,0),(4,'数码',0,1,1,0,NULL,NULL,0),(5,'家居家装',0,1,1,0,NULL,NULL,0),(6,'电脑办公',0,1,1,0,NULL,NULL,0),(7,'厨具',0,1,1,0,NULL,NULL,0),(8,'个护化妆',0,1,1,0,NULL,NULL,0),(9,'服饰内衣',0,1,1,0,NULL,NULL,0),(10,'钟表',0,1,1,0,NULL,NULL,0),(11,'鞋靴',0,1,1,0,NULL,NULL,0),(12,'母婴',0,1,1,0,NULL,NULL,0),(13,'礼品箱包',0,1,1,0,NULL,NULL,0),(14,'食品饮料、保健食品',0,1,1,0,NULL,NULL,0),(15,'珠宝',0,1,1,0,NULL,NULL,0),(16,'汽车用品',0,1,1,0,NULL,NULL,0),(17,'运动健康',0,1,1,0,NULL,NULL,0),(18,'玩具乐器',0,1,1,0,NULL,NULL,0),(19,'彩票、旅行、充值、票务',0,1,1,0,NULL,NULL,0),(20,'生鲜',0,1,1,0,NULL,NULL,0),(21,'整车',0,1,1,0,NULL,NULL,0),(22,'电子书刊',1,2,1,0,NULL,NULL,0),(23,'音像',1,2,1,0,NULL,NULL,0),(24,'英文原版',1,2,1,0,NULL,NULL,0),(25,'文艺',1,2,1,0,NULL,NULL,0),(26,'少儿',1,2,1,0,NULL,NULL,0),(27,'人文社科',1,2,1,0,NULL,NULL,0),(28,'经管励志',1,2,1,0,NULL,NULL,0),(29,'生活',1,2,1,0,NULL,NULL,0),(30,'科技',1,2,1,0,NULL,NULL,0),(31,'教育',1,2,1,0,NULL,NULL,0),(32,'港台图书',1,2,1,0,NULL,NULL,0),(33,'其他',1,2,1,0,NULL,NULL,0),(34,'手机通讯',2,2,1,0,NULL,NULL,0),(35,'运营商',2,2,1,0,NULL,NULL,0),(36,'手机配件',2,2,1,0,NULL,NULL,0),(37,'大 家 电',3,2,1,0,NULL,NULL,0),(38,'厨卫大电',3,2,1,0,NULL,NULL,0),(39,'厨房小电',3,2,1,0,NULL,NULL,0),(40,'生活电器',3,2,1,0,NULL,NULL,0),(41,'个护健康',3,2,1,0,NULL,NULL,0),(42,'五金家装',3,2,1,0,NULL,NULL,0),(43,'摄影摄像',4,2,1,0,NULL,NULL,0),(44,'数码配件',4,2,1,0,NULL,NULL,0),(45,'智能设备',4,2,1,0,NULL,NULL,0),(46,'影音娱乐',4,2,1,0,NULL,NULL,0),(47,'电子教育',4,2,1,0,NULL,NULL,0),(48,'虚拟商品',4,2,1,0,NULL,NULL,0),(49,'家纺',5,2,1,0,NULL,NULL,0),(50,'灯具',5,2,1,0,NULL,NULL,0),(51,'生活日用',5,2,1,0,NULL,NULL,0),(52,'家装软饰',5,2,1,0,NULL,NULL,0),(53,'宠物生活',5,2,1,0,NULL,NULL,0),(54,'电脑整机',6,2,1,0,NULL,NULL,0),(55,'电脑配件',6,2,1,0,NULL,NULL,0),(56,'外设产品',6,2,1,0,NULL,NULL,0),(57,'游戏设备',6,2,1,0,NULL,NULL,0),(58,'网络产品',6,2,1,0,NULL,NULL,0),(59,'办公设备',6,2,1,0,NULL,NULL,0),(60,'文具/耗材',6,2,1,0,NULL,NULL,0),(61,'服务产品',6,2,1,0,NULL,NULL,0),(62,'烹饪锅具',7,2,1,0,NULL,NULL,0),(63,'刀剪菜板',7,2,1,0,NULL,NULL,0),(64,'厨房配件',7,2,1,0,NULL,NULL,0),(65,'水具酒具',7,2,1,0,NULL,NULL,0),(66,'餐具',7,2,1,0,NULL,NULL,0),(67,'酒店用品',7,2,1,0,NULL,NULL,0),(68,'茶具/咖啡具',7,2,1,0,NULL,NULL,0),(69,'清洁用品',8,2,1,0,NULL,NULL,0),(70,'面部护肤',8,2,1,0,NULL,NULL,0),(71,'身体护理',8,2,1,0,NULL,NULL,0),(72,'口腔护理',8,2,1,0,NULL,NULL,0),(73,'女性护理',8,2,1,0,NULL,NULL,0),(74,'洗发护发',8,2,1,0,NULL,NULL,0),(75,'香水彩妆',8,2,1,0,NULL,NULL,0),(76,'女装',9,2,1,0,NULL,NULL,0),(77,'男装',9,2,1,0,NULL,NULL,0),(78,'内衣',9,2,1,0,NULL,NULL,0),(79,'洗衣服务',9,2,1,0,NULL,NULL,0),(80,'服饰配件',9,2,1,0,NULL,NULL,0),(81,'钟表',10,2,1,0,NULL,NULL,0),(82,'流行男鞋',11,2,1,0,NULL,NULL,0),(83,'时尚女鞋',11,2,1,0,NULL,NULL,0),(84,'奶粉',12,2,1,0,NULL,NULL,0),(85,'营养辅食',12,2,1,0,NULL,NULL,0),(86,'尿裤湿巾',12,2,1,0,NULL,NULL,0),(87,'喂养用品',12,2,1,0,NULL,NULL,0),(88,'洗护用品',12,2,1,0,NULL,NULL,0),(89,'童车童床',12,2,1,0,NULL,NULL,0),(90,'寝居服饰',12,2,1,0,NULL,NULL,0),(91,'妈妈专区',12,2,1,0,NULL,NULL,0),(92,'童装童鞋',12,2,1,0,NULL,NULL,0),(93,'安全座椅',12,2,1,0,NULL,NULL,0),(94,'潮流女包',13,2,1,0,NULL,NULL,0),(95,'精品男包',13,2,1,0,NULL,NULL,0),(96,'功能箱包',13,2,1,0,NULL,NULL,0),(97,'礼品',13,2,1,0,NULL,NULL,0),(98,'奢侈品',13,2,1,0,NULL,NULL,0),(99,'婚庆',13,2,1,0,NULL,NULL,0),(100,'进口食品',14,2,1,0,NULL,NULL,0),(101,'地方特产',14,2,1,0,NULL,NULL,0),(102,'休闲食品',14,2,1,0,NULL,NULL,0),(103,'粮油调味',14,2,1,0,NULL,NULL,0),(104,'饮料冲调',14,2,1,0,NULL,NULL,0),(105,'食品礼券',14,2,1,0,NULL,NULL,0),(106,'茗茶',14,2,1,0,NULL,NULL,0),(107,'时尚饰品',15,2,1,0,NULL,NULL,0),(108,'黄金',15,2,1,0,NULL,NULL,0),(109,'K金饰品',15,2,1,0,NULL,NULL,0),(110,'金银投资',15,2,1,0,NULL,NULL,0),(111,'银饰',15,2,1,0,NULL,NULL,0),(112,'钻石',15,2,1,0,NULL,NULL,0),(113,'翡翠玉石',15,2,1,0,NULL,NULL,0),(114,'水晶玛瑙',15,2,1,0,NULL,NULL,0),(115,'彩宝',15,2,1,0,NULL,NULL,0),(116,'铂金',15,2,1,0,NULL,NULL,0),(117,'木手串/把件',15,2,1,0,NULL,NULL,0),(118,'珍珠',15,2,1,0,NULL,NULL,0),(119,'维修保养',16,2,1,0,NULL,NULL,0),(120,'车载电器',16,2,1,0,NULL,NULL,0),(121,'美容清洗',16,2,1,0,NULL,NULL,0),(122,'汽车装饰',16,2,1,0,NULL,NULL,0),(123,'安全自驾',16,2,1,0,NULL,NULL,0),(124,'汽车服务',16,2,1,0,NULL,NULL,0),(125,'赛事改装',16,2,1,0,NULL,NULL,0),(126,'运动鞋包',17,2,1,0,NULL,NULL,0),(127,'运动服饰',17,2,1,0,NULL,NULL,0),(128,'骑行运动',17,2,1,0,NULL,NULL,0),(129,'垂钓用品',17,2,1,0,NULL,NULL,0),(130,'游泳用品',17,2,1,0,NULL,NULL,0),(131,'户外鞋服',17,2,1,0,NULL,NULL,0),(132,'户外装备',17,2,1,0,NULL,NULL,0),(133,'健身训练',17,2,1,0,NULL,NULL,0),(134,'体育用品',17,2,1,0,NULL,NULL,0),(135,'适用年龄',18,2,1,0,NULL,NULL,0),(136,'遥控/电动',18,2,1,0,NULL,NULL,0),(137,'毛绒布艺',18,2,1,0,NULL,NULL,0),(138,'娃娃玩具',18,2,1,0,NULL,NULL,0),(139,'模型玩具',18,2,1,0,NULL,NULL,0),(140,'健身玩具',18,2,1,0,NULL,NULL,0),(141,'动漫玩具',18,2,1,0,NULL,NULL,0),(142,'益智玩具',18,2,1,0,NULL,NULL,0),(143,'积木拼插',18,2,1,0,NULL,NULL,0),(144,'DIY玩具',18,2,1,0,NULL,NULL,0),(145,'创意减压',18,2,1,0,NULL,NULL,0),(146,'乐器',18,2,1,0,NULL,NULL,0),(147,'彩票',19,2,1,0,NULL,NULL,0),(148,'机票',19,2,1,0,NULL,NULL,0),(149,'酒店',19,2,1,0,NULL,NULL,0),(150,'旅行',19,2,1,0,NULL,NULL,0),(151,'充值',19,2,1,0,NULL,NULL,0),(152,'游戏',19,2,1,0,NULL,NULL,0),(153,'票务',19,2,1,0,NULL,NULL,0),(154,'产地直供',20,2,1,0,NULL,NULL,0),(155,'水果',20,2,1,0,NULL,NULL,0),(156,'猪牛羊肉',20,2,1,0,NULL,NULL,0),(157,'海鲜水产',20,2,1,0,NULL,NULL,0),(158,'禽肉蛋品',20,2,1,0,NULL,NULL,0),(159,'冷冻食品',20,2,1,0,NULL,NULL,0),(160,'熟食腊味',20,2,1,0,NULL,NULL,0),(161,'饮品甜品',20,2,1,0,NULL,NULL,0),(162,'蔬菜',20,2,1,0,NULL,NULL,0),(163,'全新整车',21,2,1,0,NULL,NULL,0),(164,'二手车',21,2,1,0,NULL,NULL,0),(165,'电子书',22,3,1,0,NULL,NULL,0),(166,'网络原创',22,3,1,0,NULL,NULL,0),(167,'数字杂志',22,3,1,0,NULL,NULL,0),(168,'多媒体图书',22,3,1,0,NULL,NULL,0),(169,'音乐',23,3,1,0,NULL,NULL,0),(170,'影视',23,3,1,0,NULL,NULL,0),(171,'教育音像',23,3,1,0,NULL,NULL,0),(172,'少儿',24,3,1,0,NULL,NULL,0),(173,'商务投资',24,3,1,0,NULL,NULL,0),(174,'英语学习与考试',24,3,1,0,NULL,NULL,0),(175,'文学',24,3,1,0,NULL,NULL,0),(176,'传记',24,3,1,0,NULL,NULL,0),(177,'励志',24,3,1,0,NULL,NULL,0),(178,'小说',25,3,1,0,NULL,NULL,0),(179,'文学',25,3,1,0,NULL,NULL,0),(180,'青春文学',25,3,1,0,NULL,NULL,0),(181,'传记',25,3,1,0,NULL,NULL,0),(182,'艺术',25,3,1,0,NULL,NULL,0),(183,'少儿',26,3,1,0,NULL,NULL,0),(184,'0-2岁',26,3,1,0,NULL,NULL,0),(185,'3-6岁',26,3,1,0,NULL,NULL,0),(186,'7-10岁',26,3,1,0,NULL,NULL,0),(187,'11-14岁',26,3,1,0,NULL,NULL,0),(188,'历史',27,3,1,0,NULL,NULL,0),(189,'哲学',27,3,1,0,NULL,NULL,0),(190,'国学',27,3,1,0,NULL,NULL,0),(191,'政治/军事',27,3,1,0,NULL,NULL,0),(192,'法律',27,3,1,0,NULL,NULL,0),(193,'人文社科',27,3,1,0,NULL,NULL,0),(194,'心理学',27,3,1,0,NULL,NULL,0),(195,'文化',27,3,1,0,NULL,NULL,0),(196,'社会科学',27,3,1,0,NULL,NULL,0),(197,'经济',28,3,1,0,NULL,NULL,0),(198,'金融与投资',28,3,1,0,NULL,NULL,0),(199,'管理',28,3,1,0,NULL,NULL,0),(200,'励志与成功',28,3,1,0,NULL,NULL,0),(201,'生活',29,3,1,0,NULL,NULL,0),(202,'健身与保健',29,3,1,0,NULL,NULL,0),(203,'家庭与育儿',29,3,1,0,NULL,NULL,0),(204,'旅游',29,3,1,0,NULL,NULL,0),(205,'烹饪美食',29,3,1,0,NULL,NULL,0),(206,'工业技术',30,3,1,0,NULL,NULL,0),(207,'科普读物',30,3,1,0,NULL,NULL,0),(208,'建筑',30,3,1,0,NULL,NULL,0),(209,'医学',30,3,1,0,NULL,NULL,0),(210,'科学与自然',30,3,1,0,NULL,NULL,0),(211,'计算机与互联网',30,3,1,0,NULL,NULL,0),(212,'电子通信',30,3,1,0,NULL,NULL,0),(213,'中小学教辅',31,3,1,0,NULL,NULL,0),(214,'教育与考试',31,3,1,0,NULL,NULL,0),(215,'外语学习',31,3,1,0,NULL,NULL,0),(216,'大中专教材',31,3,1,0,NULL,NULL,0),(217,'字典词典',31,3,1,0,NULL,NULL,0),(218,'艺术/设计/收藏',32,3,1,0,NULL,NULL,0),(219,'经济管理',32,3,1,0,NULL,NULL,0),(220,'文化/学术',32,3,1,0,NULL,NULL,0),(221,'少儿',32,3,1,0,NULL,NULL,0),(222,'工具书',33,3,1,0,NULL,NULL,0),(223,'杂志/期刊',33,3,1,0,NULL,NULL,0),(224,'套装书',33,3,1,0,NULL,NULL,0),(225,'手机',34,3,1,0,NULL,NULL,0),(226,'对讲机',34,3,1,0,NULL,NULL,0),(227,'合约机',35,3,1,0,NULL,NULL,0),(228,'选号中心',35,3,1,0,NULL,NULL,0),(229,'装宽带',35,3,1,0,NULL,NULL,0),(230,'办套餐',35,3,1,0,NULL,NULL,0),(231,'移动电源',36,3,1,0,NULL,NULL,0),(232,'电池/移动电源',36,3,1,0,NULL,NULL,0),(233,'蓝牙耳机',36,3,1,0,NULL,NULL,0),(234,'充电器/数据线',36,3,1,0,NULL,NULL,0),(235,'苹果周边',36,3,1,0,NULL,NULL,0),(236,'手机耳机',36,3,1,0,NULL,NULL,0),(237,'手机贴膜',36,3,1,0,NULL,NULL,0),(238,'手机存储卡',36,3,1,0,NULL,NULL,0),(239,'充电器',36,3,1,0,NULL,NULL,0),(240,'数据线',36,3,1,0,NULL,NULL,0),(241,'手机保护套',36,3,1,0,NULL,NULL,0),(242,'车载配件',36,3,1,0,NULL,NULL,0),(243,'iPhone 配件',36,3,1,0,NULL,NULL,0),(244,'手机电池',36,3,1,0,NULL,NULL,0),(245,'创意配件',36,3,1,0,NULL,NULL,0),(246,'便携/无线音响',36,3,1,0,NULL,NULL,0),(247,'手机饰品',36,3,1,0,NULL,NULL,0),(248,'拍照配件',36,3,1,0,NULL,NULL,0),(249,'手机支架',36,3,1,0,NULL,NULL,0),(250,'平板电视',37,3,1,0,NULL,NULL,0),(251,'空调',37,3,1,0,NULL,NULL,0),(252,'冰箱',37,3,1,0,NULL,NULL,0),(253,'洗衣机',37,3,1,0,NULL,NULL,0),(254,'家庭影院',37,3,1,0,NULL,NULL,0),(255,'DVD/电视盒子',37,3,1,0,NULL,NULL,0),(256,'迷你音响',37,3,1,0,NULL,NULL,0),(257,'冷柜/冰吧',37,3,1,0,NULL,NULL,0),(258,'家电配件',37,3,1,0,NULL,NULL,0),(259,'功放',37,3,1,0,NULL,NULL,0),(260,'回音壁/Soundbar',37,3,1,0,NULL,NULL,0),(261,'Hi-Fi专区',37,3,1,0,NULL,NULL,0),(262,'电视盒子',37,3,1,0,NULL,NULL,0),(263,'酒柜',37,3,1,0,NULL,NULL,0),(264,'燃气灶',38,3,1,0,NULL,NULL,0),(265,'油烟机',38,3,1,0,NULL,NULL,0),(266,'热水器',38,3,1,0,NULL,NULL,0),(267,'消毒柜',38,3,1,0,NULL,NULL,0),(268,'洗碗机',38,3,1,0,NULL,NULL,0),(269,'料理机',39,3,1,0,NULL,NULL,0),(270,'榨汁机',39,3,1,0,NULL,NULL,0),(271,'电饭煲',39,3,1,0,NULL,NULL,0),(272,'电压力锅',39,3,1,0,NULL,NULL,0),(273,'豆浆机',39,3,1,0,NULL,NULL,0),(274,'咖啡机',39,3,1,0,NULL,NULL,0),(275,'微波炉',39,3,1,0,NULL,NULL,0),(276,'电烤箱',39,3,1,0,NULL,NULL,0),(277,'电磁炉',39,3,1,0,NULL,NULL,0),(278,'面包机',39,3,1,0,NULL,NULL,0),(279,'煮蛋器',39,3,1,0,NULL,NULL,0),(280,'酸奶机',39,3,1,0,NULL,NULL,0),(281,'电炖锅',39,3,1,0,NULL,NULL,0),(282,'电水壶/热水瓶',39,3,1,0,NULL,NULL,0),(283,'电饼铛',39,3,1,0,NULL,NULL,0),(284,'多用途锅',39,3,1,0,NULL,NULL,0),(285,'电烧烤炉',39,3,1,0,NULL,NULL,0),(286,'果蔬解毒机',39,3,1,0,NULL,NULL,0),(287,'其它厨房电器',39,3,1,0,NULL,NULL,0),(288,'养生壶/煎药壶',39,3,1,0,NULL,NULL,0),(289,'电热饭盒',39,3,1,0,NULL,NULL,0),(290,'取暖电器',40,3,1,0,NULL,NULL,0),(291,'净化器',40,3,1,0,NULL,NULL,0),(292,'加湿器',40,3,1,0,NULL,NULL,0),(293,'扫地机器人',40,3,1,0,NULL,NULL,0),(294,'吸尘器',40,3,1,0,NULL,NULL,0),(295,'挂烫机/熨斗',40,3,1,0,NULL,NULL,0),(296,'插座',40,3,1,0,NULL,NULL,0),(297,'电话机',40,3,1,0,NULL,NULL,0),(298,'清洁机',40,3,1,0,NULL,NULL,0),(299,'除湿机',40,3,1,0,NULL,NULL,0),(300,'干衣机',40,3,1,0,NULL,NULL,0),(301,'收录/音机',40,3,1,0,NULL,NULL,0),(302,'电风扇',40,3,1,0,NULL,NULL,0),(303,'冷风扇',40,3,1,0,NULL,NULL,0),(304,'其它生活电器',40,3,1,0,NULL,NULL,0),(305,'生活电器配件',40,3,1,0,NULL,NULL,0),(306,'净水器',40,3,1,0,NULL,NULL,0),(307,'饮水机',40,3,1,0,NULL,NULL,0),(308,'剃须刀',41,3,1,0,NULL,NULL,0),(309,'剃/脱毛器',41,3,1,0,NULL,NULL,0),(310,'口腔护理',41,3,1,0,NULL,NULL,0),(311,'电吹风',41,3,1,0,NULL,NULL,0),(312,'美容器',41,3,1,0,NULL,NULL,0),(313,'理发器',41,3,1,0,NULL,NULL,0),(314,'卷/直发器',41,3,1,0,NULL,NULL,0),(315,'按摩椅',41,3,1,0,NULL,NULL,0),(316,'按摩器',41,3,1,0,NULL,NULL,0),(317,'足浴盆',41,3,1,0,NULL,NULL,0),(318,'血压计',41,3,1,0,NULL,NULL,0),(319,'电子秤/厨房秤',41,3,1,0,NULL,NULL,0),(320,'血糖仪',41,3,1,0,NULL,NULL,0),(321,'体温计',41,3,1,0,NULL,NULL,0),(322,'其它健康电器',41,3,1,0,NULL,NULL,0),(323,'计步器/脂肪检测仪',41,3,1,0,NULL,NULL,0),(324,'电动工具',42,3,1,0,NULL,NULL,0),(325,'手动工具',42,3,1,0,NULL,NULL,0),(326,'仪器仪表',42,3,1,0,NULL,NULL,0),(327,'浴霸/排气扇',42,3,1,0,NULL,NULL,0),(328,'灯具',42,3,1,0,NULL,NULL,0),(329,'LED灯',42,3,1,0,NULL,NULL,0),(330,'洁身器',42,3,1,0,NULL,NULL,0),(331,'水槽',42,3,1,0,NULL,NULL,0),(332,'龙头',42,3,1,0,NULL,NULL,0),(333,'淋浴花洒',42,3,1,0,NULL,NULL,0),(334,'厨卫五金',42,3,1,0,NULL,NULL,0),(335,'家具五金',42,3,1,0,NULL,NULL,0),(336,'门铃',42,3,1,0,NULL,NULL,0),(337,'电气开关',42,3,1,0,NULL,NULL,0),(338,'插座',42,3,1,0,NULL,NULL,0),(339,'电工电料',42,3,1,0,NULL,NULL,0),(340,'监控安防',42,3,1,0,NULL,NULL,0),(341,'电线/线缆',42,3,1,0,NULL,NULL,0),(342,'数码相机',43,3,1,0,NULL,NULL,0),(343,'单电/微单相机',43,3,1,0,NULL,NULL,0),(344,'单反相机',43,3,1,0,NULL,NULL,0),(345,'摄像机',43,3,1,0,NULL,NULL,0),(346,'拍立得',43,3,1,0,NULL,NULL,0),(347,'运动相机',43,3,1,0,NULL,NULL,0),(348,'镜头',43,3,1,0,NULL,NULL,0),(349,'户外器材',43,3,1,0,NULL,NULL,0),(350,'影棚器材',43,3,1,0,NULL,NULL,0),(351,'冲印服务',43,3,1,0,NULL,NULL,0),(352,'数码相框',43,3,1,0,NULL,NULL,0),(353,'存储卡',44,3,1,0,NULL,NULL,0),(354,'读卡器',44,3,1,0,NULL,NULL,0),(355,'滤镜',44,3,1,0,NULL,NULL,0),(356,'闪光灯/手柄',44,3,1,0,NULL,NULL,0),(357,'相机包',44,3,1,0,NULL,NULL,0),(358,'三脚架/云台',44,3,1,0,NULL,NULL,0),(359,'相机清洁/贴膜',44,3,1,0,NULL,NULL,0),(360,'机身附件',44,3,1,0,NULL,NULL,0),(361,'镜头附件',44,3,1,0,NULL,NULL,0),(362,'电池/充电器',44,3,1,0,NULL,NULL,0),(363,'移动电源',44,3,1,0,NULL,NULL,0),(364,'数码支架',44,3,1,0,NULL,NULL,0),(365,'智能手环',45,3,1,0,NULL,NULL,0),(366,'智能手表',45,3,1,0,NULL,NULL,0),(367,'智能眼镜',45,3,1,0,NULL,NULL,0),(368,'运动跟踪器',45,3,1,0,NULL,NULL,0),(369,'健康监测',45,3,1,0,NULL,NULL,0),(370,'智能配饰',45,3,1,0,NULL,NULL,0),(371,'智能家居',45,3,1,0,NULL,NULL,0),(372,'体感车',45,3,1,0,NULL,NULL,0),(373,'其他配件',45,3,1,0,NULL,NULL,0),(374,'智能机器人',45,3,1,0,NULL,NULL,0),(375,'无人机',45,3,1,0,NULL,NULL,0),(376,'MP3/MP4',46,3,1,0,NULL,NULL,0),(377,'智能设备',46,3,1,0,NULL,NULL,0),(378,'耳机/耳麦',46,3,1,0,NULL,NULL,0),(379,'便携/无线音箱',46,3,1,0,NULL,NULL,0),(380,'音箱/音响',46,3,1,0,NULL,NULL,0),(381,'高清播放器',46,3,1,0,NULL,NULL,0),(382,'收音机',46,3,1,0,NULL,NULL,0),(383,'MP3/MP4配件',46,3,1,0,NULL,NULL,0),(384,'麦克风',46,3,1,0,NULL,NULL,0),(385,'专业音频',46,3,1,0,NULL,NULL,0),(386,'苹果配件',46,3,1,0,NULL,NULL,0),(387,'学生平板',47,3,1,0,NULL,NULL,0),(388,'点读机/笔',47,3,1,0,NULL,NULL,0),(389,'早教益智',47,3,1,0,NULL,NULL,0),(390,'录音笔',47,3,1,0,NULL,NULL,0),(391,'电纸书',47,3,1,0,NULL,NULL,0),(392,'电子词典',47,3,1,0,NULL,NULL,0),(393,'复读机',47,3,1,0,NULL,NULL,0),(394,'延保服务',48,3,1,0,NULL,NULL,0),(395,'杀毒软件',48,3,1,0,NULL,NULL,0),(396,'积分商品',48,3,1,0,NULL,NULL,0),(397,'桌布/罩件',49,3,1,0,NULL,NULL,0),(398,'地毯地垫',49,3,1,0,NULL,NULL,0),(399,'沙发垫套/椅垫',49,3,1,0,NULL,NULL,0),(400,'床品套件',49,3,1,0,NULL,NULL,0),(401,'被子',49,3,1,0,NULL,NULL,0),(402,'枕芯',49,3,1,0,NULL,NULL,0),(403,'床单被罩',49,3,1,0,NULL,NULL,0),(404,'毯子',49,3,1,0,NULL,NULL,0),(405,'床垫/床褥',49,3,1,0,NULL,NULL,0),(406,'蚊帐',49,3,1,0,NULL,NULL,0),(407,'抱枕靠垫',49,3,1,0,NULL,NULL,0),(408,'毛巾浴巾',49,3,1,0,NULL,NULL,0),(409,'电热毯',49,3,1,0,NULL,NULL,0),(410,'窗帘/窗纱',49,3,1,0,NULL,NULL,0),(411,'布艺软饰',49,3,1,0,NULL,NULL,0),(412,'凉席',49,3,1,0,NULL,NULL,0),(413,'台灯',50,3,1,0,NULL,NULL,0),(414,'节能灯',50,3,1,0,NULL,NULL,0),(415,'装饰灯',50,3,1,0,NULL,NULL,0),(416,'落地灯',50,3,1,0,NULL,NULL,0),(417,'应急灯/手电',50,3,1,0,NULL,NULL,0),(418,'LED灯',50,3,1,0,NULL,NULL,0),(419,'吸顶灯',50,3,1,0,NULL,NULL,0),(420,'五金电器',50,3,1,0,NULL,NULL,0),(421,'筒灯射灯',50,3,1,0,NULL,NULL,0),(422,'吊灯',50,3,1,0,NULL,NULL,0),(423,'氛围照明',50,3,1,0,NULL,NULL,0),(424,'保暖防护',51,3,1,0,NULL,NULL,0),(425,'收纳用品',51,3,1,0,NULL,NULL,0),(426,'雨伞雨具',51,3,1,0,NULL,NULL,0),(427,'浴室用品',51,3,1,0,NULL,NULL,0),(428,'缝纫/针织用品',51,3,1,0,NULL,NULL,0),(429,'洗晒/熨烫',51,3,1,0,NULL,NULL,0),(430,'净化除味',51,3,1,0,NULL,NULL,0),(431,'相框/照片墙',52,3,1,0,NULL,NULL,0),(432,'装饰字画',52,3,1,0,NULL,NULL,0),(433,'节庆饰品',52,3,1,0,NULL,NULL,0),(434,'手工/十字绣',52,3,1,0,NULL,NULL,0),(435,'装饰摆件',52,3,1,0,NULL,NULL,0),(436,'帘艺隔断',52,3,1,0,NULL,NULL,0),(437,'墙贴/装饰贴',52,3,1,0,NULL,NULL,0),(438,'钟饰',52,3,1,0,NULL,NULL,0),(439,'花瓶花艺',52,3,1,0,NULL,NULL,0),(440,'香薰蜡烛',52,3,1,0,NULL,NULL,0),(441,'创意家居',52,3,1,0,NULL,NULL,0),(442,'宠物主粮',53,3,1,0,NULL,NULL,0),(443,'宠物零食',53,3,1,0,NULL,NULL,0),(444,'医疗保健',53,3,1,0,NULL,NULL,0),(445,'家居日用',53,3,1,0,NULL,NULL,0),(446,'宠物玩具',53,3,1,0,NULL,NULL,0),(447,'出行装备',53,3,1,0,NULL,NULL,0),(448,'洗护美容',53,3,1,0,NULL,NULL,0),(449,'笔记本',54,3,1,0,NULL,NULL,0),(450,'超极本',54,3,1,0,NULL,NULL,0),(451,'游戏本',54,3,1,0,NULL,NULL,0),(452,'平板电脑',54,3,1,0,NULL,NULL,0),(453,'平板电脑配件',54,3,1,0,NULL,NULL,0),(454,'台式机',54,3,1,0,NULL,NULL,0),(455,'服务器/工作站',54,3,1,0,NULL,NULL,0),(456,'笔记本配件',54,3,1,0,NULL,NULL,0),(457,'一体机',54,3,1,0,NULL,NULL,0),(458,'CPU',55,3,1,0,NULL,NULL,0),(459,'主板',55,3,1,0,NULL,NULL,0),(460,'显卡',55,3,1,0,NULL,NULL,0),(461,'硬盘',55,3,1,0,NULL,NULL,0),(462,'SSD固态硬盘',55,3,1,0,NULL,NULL,0),(463,'内存',55,3,1,0,NULL,NULL,0),(464,'机箱',55,3,1,0,NULL,NULL,0),(465,'电源',55,3,1,0,NULL,NULL,0),(466,'显示器',55,3,1,0,NULL,NULL,0),(467,'刻录机/光驱',55,3,1,0,NULL,NULL,0),(468,'散热器',55,3,1,0,NULL,NULL,0),(469,'声卡/扩展卡',55,3,1,0,NULL,NULL,0),(470,'装机配件',55,3,1,0,NULL,NULL,0),(471,'组装电脑',55,3,1,0,NULL,NULL,0),(472,'移动硬盘',56,3,1,0,NULL,NULL,0),(473,'U盘',56,3,1,0,NULL,NULL,0),(474,'鼠标',56,3,1,0,NULL,NULL,0),(475,'键盘',56,3,1,0,NULL,NULL,0),(476,'鼠标垫',56,3,1,0,NULL,NULL,0),(477,'摄像头',56,3,1,0,NULL,NULL,0),(478,'手写板',56,3,1,0,NULL,NULL,0),(479,'硬盘盒',56,3,1,0,NULL,NULL,0),(480,'插座',56,3,1,0,NULL,NULL,0),(481,'线缆',56,3,1,0,NULL,NULL,0),(482,'UPS电源',56,3,1,0,NULL,NULL,0),(483,'电脑工具',56,3,1,0,NULL,NULL,0),(484,'游戏设备',56,3,1,0,NULL,NULL,0),(485,'电玩',56,3,1,0,NULL,NULL,0),(486,'电脑清洁',56,3,1,0,NULL,NULL,0),(487,'网络仪表仪器',56,3,1,0,NULL,NULL,0),(488,'游戏机',57,3,1,0,NULL,NULL,0),(489,'游戏耳机',57,3,1,0,NULL,NULL,0),(490,'手柄/方向盘',57,3,1,0,NULL,NULL,0),(491,'游戏软件',57,3,1,0,NULL,NULL,0),(492,'游戏周边',57,3,1,0,NULL,NULL,0),(493,'路由器',58,3,1,0,NULL,NULL,0),(494,'网卡',58,3,1,0,NULL,NULL,0),(495,'交换机',58,3,1,0,NULL,NULL,0),(496,'网络存储',58,3,1,0,NULL,NULL,0),(497,'4G/3G上网',58,3,1,0,NULL,NULL,0),(498,'网络盒子',58,3,1,0,NULL,NULL,0),(499,'网络配件',58,3,1,0,NULL,NULL,0),(500,'投影机',59,3,1,0,NULL,NULL,0),(501,'投影配件',59,3,1,0,NULL,NULL,0),(502,'多功能一体机',59,3,1,0,NULL,NULL,0),(503,'打印机',59,3,1,0,NULL,NULL,0),(504,'传真设备',59,3,1,0,NULL,NULL,0),(505,'验钞/点钞机',59,3,1,0,NULL,NULL,0),(506,'扫描设备',59,3,1,0,NULL,NULL,0),(507,'复合机',59,3,1,0,NULL,NULL,0),(508,'碎纸机',59,3,1,0,NULL,NULL,0),(509,'考勤机',59,3,1,0,NULL,NULL,0),(510,'收款/POS机',59,3,1,0,NULL,NULL,0),(511,'会议音频视频',59,3,1,0,NULL,NULL,0),(512,'保险柜',59,3,1,0,NULL,NULL,0),(513,'装订/封装机',59,3,1,0,NULL,NULL,0),(514,'安防监控',59,3,1,0,NULL,NULL,0),(515,'办公家具',59,3,1,0,NULL,NULL,0),(516,'白板',59,3,1,0,NULL,NULL,0),(517,'硒鼓/墨粉',60,3,1,0,NULL,NULL,0),(518,'墨盒',60,3,1,0,NULL,NULL,0),(519,'色带',60,3,1,0,NULL,NULL,0),(520,'纸类',60,3,1,0,NULL,NULL,0),(521,'办公文具',60,3,1,0,NULL,NULL,0),(522,'学生文具',60,3,1,0,NULL,NULL,0),(523,'财会用品',60,3,1,0,NULL,NULL,0),(524,'文件管理',60,3,1,0,NULL,NULL,0),(525,'本册/便签',60,3,1,0,NULL,NULL,0),(526,'计算器',60,3,1,0,NULL,NULL,0),(527,'笔类',60,3,1,0,NULL,NULL,0),(528,'画具画材',60,3,1,0,NULL,NULL,0),(529,'刻录碟片/附件',60,3,1,0,NULL,NULL,0),(530,'上门安装',61,3,1,0,NULL,NULL,0),(531,'延保服务',61,3,1,0,NULL,NULL,0),(532,'维修保养',61,3,1,0,NULL,NULL,0),(533,'电脑软件',61,3,1,0,NULL,NULL,0),(534,'京东服务',61,3,1,0,NULL,NULL,0),(535,'炒锅',62,3,1,0,NULL,NULL,0),(536,'煎锅',62,3,1,0,NULL,NULL,0),(537,'压力锅',62,3,1,0,NULL,NULL,0),(538,'蒸锅',62,3,1,0,NULL,NULL,0),(539,'汤锅',62,3,1,0,NULL,NULL,0),(540,'奶锅',62,3,1,0,NULL,NULL,0),(541,'锅具套装',62,3,1,0,NULL,NULL,0),(542,'煲类',62,3,1,0,NULL,NULL,0),(543,'水壶',62,3,1,0,NULL,NULL,0),(544,'火锅',62,3,1,0,NULL,NULL,0),(545,'菜刀',63,3,1,0,NULL,NULL,0),(546,'剪刀',63,3,1,0,NULL,NULL,0),(547,'刀具套装',63,3,1,0,NULL,NULL,0),(548,'砧板',63,3,1,0,NULL,NULL,0),(549,'瓜果刀/刨',63,3,1,0,NULL,NULL,0),(550,'多功能刀',63,3,1,0,NULL,NULL,0),(551,'保鲜盒',64,3,1,0,NULL,NULL,0),(552,'烘焙/烧烤',64,3,1,0,NULL,NULL,0),(553,'饭盒/提锅',64,3,1,0,NULL,NULL,0),(554,'储物/置物架',64,3,1,0,NULL,NULL,0),(555,'厨房DIY/小工具',64,3,1,0,NULL,NULL,0),(556,'塑料杯',65,3,1,0,NULL,NULL,0),(557,'运动水壶',65,3,1,0,NULL,NULL,0),(558,'玻璃杯',65,3,1,0,NULL,NULL,0),(559,'陶瓷/马克杯',65,3,1,0,NULL,NULL,0),(560,'保温杯',65,3,1,0,NULL,NULL,0),(561,'保温壶',65,3,1,0,NULL,NULL,0),(562,'酒杯/酒具',65,3,1,0,NULL,NULL,0),(563,'杯具套装',65,3,1,0,NULL,NULL,0),(564,'餐具套装',66,3,1,0,NULL,NULL,0),(565,'碗/碟/盘',66,3,1,0,NULL,NULL,0),(566,'筷勺/刀叉',66,3,1,0,NULL,NULL,0),(567,'一次性用品',66,3,1,0,NULL,NULL,0),(568,'果盘/果篮',66,3,1,0,NULL,NULL,0),(569,'自助餐炉',67,3,1,0,NULL,NULL,0),(570,'酒店餐具',67,3,1,0,NULL,NULL,0),(571,'酒店水具',67,3,1,0,NULL,NULL,0),(572,'整套茶具',68,3,1,0,NULL,NULL,0),(573,'茶杯',68,3,1,0,NULL,NULL,0),(574,'茶壶',68,3,1,0,NULL,NULL,0),(575,'茶盘茶托',68,3,1,0,NULL,NULL,0),(576,'茶叶罐',68,3,1,0,NULL,NULL,0),(577,'茶具配件',68,3,1,0,NULL,NULL,0),(578,'茶宠摆件',68,3,1,0,NULL,NULL,0),(579,'咖啡具',68,3,1,0,NULL,NULL,0),(580,'其他',68,3,1,0,NULL,NULL,0),(581,'纸品湿巾',69,3,1,0,NULL,NULL,0),(582,'衣物清洁',69,3,1,0,NULL,NULL,0),(583,'清洁工具',69,3,1,0,NULL,NULL,0),(584,'驱虫用品',69,3,1,0,NULL,NULL,0),(585,'家庭清洁',69,3,1,0,NULL,NULL,0),(586,'皮具护理',69,3,1,0,NULL,NULL,0),(587,'一次性用品',69,3,1,0,NULL,NULL,0),(588,'洁面',70,3,1,0,NULL,NULL,0),(589,'乳液面霜',70,3,1,0,NULL,NULL,0),(590,'面膜',70,3,1,0,NULL,NULL,0),(591,'剃须',70,3,1,0,NULL,NULL,0),(592,'套装',70,3,1,0,NULL,NULL,0),(593,'精华',70,3,1,0,NULL,NULL,0),(594,'眼霜',70,3,1,0,NULL,NULL,0),(595,'卸妆',70,3,1,0,NULL,NULL,0),(596,'防晒',70,3,1,0,NULL,NULL,0),(597,'防晒隔离',70,3,1,0,NULL,NULL,0),(598,'T区护理',70,3,1,0,NULL,NULL,0),(599,'眼部护理',70,3,1,0,NULL,NULL,0),(600,'精华露',70,3,1,0,NULL,NULL,0),(601,'爽肤水',70,3,1,0,NULL,NULL,0),(602,'沐浴',71,3,1,0,NULL,NULL,0),(603,'润肤',71,3,1,0,NULL,NULL,0),(604,'颈部',71,3,1,0,NULL,NULL,0),(605,'手足',71,3,1,0,NULL,NULL,0),(606,'纤体塑形',71,3,1,0,NULL,NULL,0),(607,'美胸',71,3,1,0,NULL,NULL,0),(608,'套装',71,3,1,0,NULL,NULL,0),(609,'精油',71,3,1,0,NULL,NULL,0),(610,'洗发护发',71,3,1,0,NULL,NULL,0),(611,'染发/造型',71,3,1,0,NULL,NULL,0),(612,'香薰精油',71,3,1,0,NULL,NULL,0),(613,'磨砂/浴盐',71,3,1,0,NULL,NULL,0),(614,'手工/香皂',71,3,1,0,NULL,NULL,0),(615,'洗发',71,3,1,0,NULL,NULL,0),(616,'护发',71,3,1,0,NULL,NULL,0),(617,'染发',71,3,1,0,NULL,NULL,0),(618,'磨砂膏',71,3,1,0,NULL,NULL,0),(619,'香皂',71,3,1,0,NULL,NULL,0),(620,'牙膏/牙粉',72,3,1,0,NULL,NULL,0),(621,'牙刷/牙线',72,3,1,0,NULL,NULL,0),(622,'漱口水',72,3,1,0,NULL,NULL,0),(623,'套装',72,3,1,0,NULL,NULL,0),(624,'卫生巾',73,3,1,0,NULL,NULL,0),(625,'卫生护垫',73,3,1,0,NULL,NULL,0),(626,'私密护理',73,3,1,0,NULL,NULL,0),(627,'脱毛膏',73,3,1,0,NULL,NULL,0),(628,'其他',73,3,1,0,NULL,NULL,0),(629,'洗发',74,3,1,0,NULL,NULL,0),(630,'护发',74,3,1,0,NULL,NULL,0),(631,'染发',74,3,1,0,NULL,NULL,0),(632,'造型',74,3,1,0,NULL,NULL,0),(633,'假发',74,3,1,0,NULL,NULL,0),(634,'套装',74,3,1,0,NULL,NULL,0),(635,'美发工具',74,3,1,0,NULL,NULL,0),(636,'脸部护理',74,3,1,0,NULL,NULL,0),(637,'香水',75,3,1,0,NULL,NULL,0),(638,'底妆',75,3,1,0,NULL,NULL,0),(639,'腮红',75,3,1,0,NULL,NULL,0),(640,'眼影',75,3,1,0,NULL,NULL,0),(641,'唇部',75,3,1,0,NULL,NULL,0),(642,'美甲',75,3,1,0,NULL,NULL,0),(643,'眼线',75,3,1,0,NULL,NULL,0),(644,'美妆工具',75,3,1,0,NULL,NULL,0),(645,'套装',75,3,1,0,NULL,NULL,0),(646,'防晒隔离',75,3,1,0,NULL,NULL,0),(647,'卸妆',75,3,1,0,NULL,NULL,0),(648,'眉笔',75,3,1,0,NULL,NULL,0),(649,'睫毛膏',75,3,1,0,NULL,NULL,0),(650,'T恤',76,3,1,0,NULL,NULL,0),(651,'衬衫',76,3,1,0,NULL,NULL,0),(652,'针织衫',76,3,1,0,NULL,NULL,0),(653,'雪纺衫',76,3,1,0,NULL,NULL,0),(654,'卫衣',76,3,1,0,NULL,NULL,0),(655,'马甲',76,3,1,0,NULL,NULL,0),(656,'连衣裙',76,3,1,0,NULL,NULL,0),(657,'半身裙',76,3,1,0,NULL,NULL,0),(658,'牛仔裤',76,3,1,0,NULL,NULL,0),(659,'休闲裤',76,3,1,0,NULL,NULL,0),(660,'打底裤',76,3,1,0,NULL,NULL,0),(661,'正装裤',76,3,1,0,NULL,NULL,0),(662,'小西装',76,3,1,0,NULL,NULL,0),(663,'短外套',76,3,1,0,NULL,NULL,0),(664,'风衣',76,3,1,0,NULL,NULL,0),(665,'毛呢大衣',76,3,1,0,NULL,NULL,0),(666,'真皮皮衣',76,3,1,0,NULL,NULL,0),(667,'棉服',76,3,1,0,NULL,NULL,0),(668,'羽绒服',76,3,1,0,NULL,NULL,0),(669,'大码女装',76,3,1,0,NULL,NULL,0),(670,'中老年女装',76,3,1,0,NULL,NULL,0),(671,'婚纱',76,3,1,0,NULL,NULL,0),(672,'打底衫',76,3,1,0,NULL,NULL,0),(673,'旗袍/唐装',76,3,1,0,NULL,NULL,0),(674,'加绒裤',76,3,1,0,NULL,NULL,0),(675,'吊带/背心',76,3,1,0,NULL,NULL,0),(676,'羊绒衫',76,3,1,0,NULL,NULL,0),(677,'短裤',76,3,1,0,NULL,NULL,0),(678,'皮草',76,3,1,0,NULL,NULL,0),(679,'礼服',76,3,1,0,NULL,NULL,0),(680,'仿皮皮衣',76,3,1,0,NULL,NULL,0),(681,'羊毛衫',76,3,1,0,NULL,NULL,0),(682,'设计师/潮牌',76,3,1,0,NULL,NULL,0),(683,'衬衫',77,3,1,0,NULL,NULL,0),(684,'T恤',77,3,1,0,NULL,NULL,0),(685,'POLO衫',77,3,1,0,NULL,NULL,0),(686,'针织衫',77,3,1,0,NULL,NULL,0),(687,'羊绒衫',77,3,1,0,NULL,NULL,0),(688,'卫衣',77,3,1,0,NULL,NULL,0),(689,'马甲/背心',77,3,1,0,NULL,NULL,0),(690,'夹克',77,3,1,0,NULL,NULL,0),(691,'风衣',77,3,1,0,NULL,NULL,0),(692,'毛呢大衣',77,3,1,0,NULL,NULL,0),(693,'仿皮皮衣',77,3,1,0,NULL,NULL,0),(694,'西服',77,3,1,0,NULL,NULL,0),(695,'棉服',77,3,1,0,NULL,NULL,0),(696,'羽绒服',77,3,1,0,NULL,NULL,0),(697,'牛仔裤',77,3,1,0,NULL,NULL,0),(698,'休闲裤',77,3,1,0,NULL,NULL,0),(699,'西裤',77,3,1,0,NULL,NULL,0),(700,'西服套装',77,3,1,0,NULL,NULL,0),(701,'大码男装',77,3,1,0,NULL,NULL,0),(702,'中老年男装',77,3,1,0,NULL,NULL,0),(703,'唐装/中山装',77,3,1,0,NULL,NULL,0),(704,'工装',77,3,1,0,NULL,NULL,0),(705,'真皮皮衣',77,3,1,0,NULL,NULL,0),(706,'加绒裤',77,3,1,0,NULL,NULL,0),(707,'卫裤/运动裤',77,3,1,0,NULL,NULL,0),(708,'短裤',77,3,1,0,NULL,NULL,0),(709,'设计师/潮牌',77,3,1,0,NULL,NULL,0),(710,'羊毛衫',77,3,1,0,NULL,NULL,0),(711,'文胸',78,3,1,0,NULL,NULL,0),(712,'女式内裤',78,3,1,0,NULL,NULL,0),(713,'男式内裤',78,3,1,0,NULL,NULL,0),(714,'睡衣/家居服',78,3,1,0,NULL,NULL,0),(715,'塑身美体',78,3,1,0,NULL,NULL,0),(716,'泳衣',78,3,1,0,NULL,NULL,0),(717,'吊带/背心',78,3,1,0,NULL,NULL,0),(718,'抹胸',78,3,1,0,NULL,NULL,0),(719,'连裤袜/丝袜',78,3,1,0,NULL,NULL,0),(720,'美腿袜',78,3,1,0,NULL,NULL,0),(721,'商务男袜',78,3,1,0,NULL,NULL,0),(722,'保暖内衣',78,3,1,0,NULL,NULL,0),(723,'情侣睡衣',78,3,1,0,NULL,NULL,0),(724,'文胸套装',78,3,1,0,NULL,NULL,0),(725,'少女文胸',78,3,1,0,NULL,NULL,0),(726,'休闲棉袜',78,3,1,0,NULL,NULL,0),(727,'大码内衣',78,3,1,0,NULL,NULL,0),(728,'内衣配件',78,3,1,0,NULL,NULL,0),(729,'打底裤袜',78,3,1,0,NULL,NULL,0),(730,'打底衫',78,3,1,0,NULL,NULL,0),(731,'秋衣秋裤',78,3,1,0,NULL,NULL,0),(732,'情趣内衣',78,3,1,0,NULL,NULL,0),(733,'服装洗护',79,3,1,0,NULL,NULL,0),(734,'太阳镜',80,3,1,0,NULL,NULL,0),(735,'光学镜架/镜片',80,3,1,0,NULL,NULL,0),(736,'围巾/手套/帽子套装',80,3,1,0,NULL,NULL,0),(737,'袖扣',80,3,1,0,NULL,NULL,0),(738,'棒球帽',80,3,1,0,NULL,NULL,0),(739,'毛线帽',80,3,1,0,NULL,NULL,0),(740,'遮阳帽',80,3,1,0,NULL,NULL,0),(741,'老花镜',80,3,1,0,NULL,NULL,0),(742,'装饰眼镜',80,3,1,0,NULL,NULL,0),(743,'防辐射眼镜',80,3,1,0,NULL,NULL,0),(744,'游泳镜',80,3,1,0,NULL,NULL,0),(745,'女士丝巾/围巾/披肩',80,3,1,0,NULL,NULL,0),(746,'男士丝巾/围巾',80,3,1,0,NULL,NULL,0),(747,'鸭舌帽',80,3,1,0,NULL,NULL,0),(748,'贝雷帽',80,3,1,0,NULL,NULL,0),(749,'礼帽',80,3,1,0,NULL,NULL,0),(750,'真皮手套',80,3,1,0,NULL,NULL,0),(751,'毛线手套',80,3,1,0,NULL,NULL,0),(752,'防晒手套',80,3,1,0,NULL,NULL,0),(753,'男士腰带/礼盒',80,3,1,0,NULL,NULL,0),(754,'女士腰带/礼盒',80,3,1,0,NULL,NULL,0),(755,'钥匙扣',80,3,1,0,NULL,NULL,0),(756,'遮阳伞/雨伞',80,3,1,0,NULL,NULL,0),(757,'口罩',80,3,1,0,NULL,NULL,0),(758,'耳罩/耳包',80,3,1,0,NULL,NULL,0),(759,'假领',80,3,1,0,NULL,NULL,0),(760,'毛线/布面料',80,3,1,0,NULL,NULL,0),(761,'领带/领结/领带夹',80,3,1,0,NULL,NULL,0),(762,'男表',81,3,1,0,NULL,NULL,0),(763,'瑞表',81,3,1,0,NULL,NULL,0),(764,'女表',81,3,1,0,NULL,NULL,0),(765,'国表',81,3,1,0,NULL,NULL,0),(766,'日韩表',81,3,1,0,NULL,NULL,0),(767,'欧美表',81,3,1,0,NULL,NULL,0),(768,'德表',81,3,1,0,NULL,NULL,0),(769,'儿童手表',81,3,1,0,NULL,NULL,0),(770,'智能手表',81,3,1,0,NULL,NULL,0),(771,'闹钟',81,3,1,0,NULL,NULL,0),(772,'座钟挂钟',81,3,1,0,NULL,NULL,0),(773,'钟表配件',81,3,1,0,NULL,NULL,0),(774,'商务休闲鞋',82,3,1,0,NULL,NULL,0),(775,'正装鞋',82,3,1,0,NULL,NULL,0),(776,'休闲鞋',82,3,1,0,NULL,NULL,0),(777,'凉鞋/沙滩鞋',82,3,1,0,NULL,NULL,0),(778,'男靴',82,3,1,0,NULL,NULL,0),(779,'功能鞋',82,3,1,0,NULL,NULL,0),(780,'拖鞋/人字拖',82,3,1,0,NULL,NULL,0),(781,'雨鞋/雨靴',82,3,1,0,NULL,NULL,0),(782,'传统布鞋',82,3,1,0,NULL,NULL,0),(783,'鞋配件',82,3,1,0,NULL,NULL,0),(784,'帆布鞋',82,3,1,0,NULL,NULL,0),(785,'增高鞋',82,3,1,0,NULL,NULL,0),(786,'工装鞋',82,3,1,0,NULL,NULL,0),(787,'定制鞋',82,3,1,0,NULL,NULL,0),(788,'高跟鞋',83,3,1,0,NULL,NULL,0),(789,'单鞋',83,3,1,0,NULL,NULL,0),(790,'休闲鞋',83,3,1,0,NULL,NULL,0),(791,'凉鞋',83,3,1,0,NULL,NULL,0),(792,'女靴',83,3,1,0,NULL,NULL,0),(793,'雪地靴',83,3,1,0,NULL,NULL,0),(794,'拖鞋/人字拖',83,3,1,0,NULL,NULL,0),(795,'踝靴',83,3,1,0,NULL,NULL,0),(796,'筒靴',83,3,1,0,NULL,NULL,0),(797,'帆布鞋',83,3,1,0,NULL,NULL,0),(798,'雨鞋/雨靴',83,3,1,0,NULL,NULL,0),(799,'妈妈鞋',83,3,1,0,NULL,NULL,0),(800,'鞋配件',83,3,1,0,NULL,NULL,0),(801,'特色鞋',83,3,1,0,NULL,NULL,0),(802,'鱼嘴鞋',83,3,1,0,NULL,NULL,0),(803,'布鞋/绣花鞋',83,3,1,0,NULL,NULL,0),(804,'马丁靴',83,3,1,0,NULL,NULL,0),(805,'坡跟鞋',83,3,1,0,NULL,NULL,0),(806,'松糕鞋',83,3,1,0,NULL,NULL,0),(807,'内增高',83,3,1,0,NULL,NULL,0),(808,'防水台',83,3,1,0,NULL,NULL,0),(809,'婴幼奶粉',84,3,1,0,NULL,NULL,0),(810,'孕妈奶粉',84,3,1,0,NULL,NULL,0),(811,'益生菌/初乳',85,3,1,0,NULL,NULL,0),(812,'米粉/菜粉',85,3,1,0,NULL,NULL,0),(813,'果泥/果汁',85,3,1,0,NULL,NULL,0),(814,'DHA',85,3,1,0,NULL,NULL,0),(815,'宝宝零食',85,3,1,0,NULL,NULL,0),(816,'钙铁锌/维生素',85,3,1,0,NULL,NULL,0),(817,'清火/开胃',85,3,1,0,NULL,NULL,0),(818,'面条/粥',85,3,1,0,NULL,NULL,0),(819,'婴儿尿裤',86,3,1,0,NULL,NULL,0),(820,'拉拉裤',86,3,1,0,NULL,NULL,0),(821,'婴儿湿巾',86,3,1,0,NULL,NULL,0),(822,'成人尿裤',86,3,1,0,NULL,NULL,0),(823,'奶瓶奶嘴',87,3,1,0,NULL,NULL,0),(824,'吸奶器',87,3,1,0,NULL,NULL,0),(825,'暖奶消毒',87,3,1,0,NULL,NULL,0),(826,'儿童餐具',87,3,1,0,NULL,NULL,0),(827,'水壶/水杯',87,3,1,0,NULL,NULL,0),(828,'牙胶安抚',87,3,1,0,NULL,NULL,0),(829,'围兜/防溅衣',87,3,1,0,NULL,NULL,0),(830,'辅食料理机',87,3,1,0,NULL,NULL,0),(831,'食物存储',87,3,1,0,NULL,NULL,0),(832,'宝宝护肤',88,3,1,0,NULL,NULL,0),(833,'洗发沐浴',88,3,1,0,NULL,NULL,0),(834,'奶瓶清洗',88,3,1,0,NULL,NULL,0),(835,'驱蚊防晒',88,3,1,0,NULL,NULL,0),(836,'理发器',88,3,1,0,NULL,NULL,0),(837,'洗澡用具',88,3,1,0,NULL,NULL,0),(838,'婴儿口腔清洁',88,3,1,0,NULL,NULL,0),(839,'洗衣液/皂',88,3,1,0,NULL,NULL,0),(840,'日常护理',88,3,1,0,NULL,NULL,0),(841,'座便器',88,3,1,0,NULL,NULL,0),(842,'婴儿推车',89,3,1,0,NULL,NULL,0),(843,'餐椅摇椅',89,3,1,0,NULL,NULL,0),(844,'婴儿床',89,3,1,0,NULL,NULL,0),(845,'学步车',89,3,1,0,NULL,NULL,0),(846,'三轮车',89,3,1,0,NULL,NULL,0),(847,'自行车',89,3,1,0,NULL,NULL,0),(848,'电动车',89,3,1,0,NULL,NULL,0),(849,'扭扭车',89,3,1,0,NULL,NULL,0),(850,'滑板车',89,3,1,0,NULL,NULL,0),(851,'婴儿床垫',89,3,1,0,NULL,NULL,0),(852,'婴儿外出服',90,3,1,0,NULL,NULL,0),(853,'婴儿内衣',90,3,1,0,NULL,NULL,0),(854,'婴儿礼盒',90,3,1,0,NULL,NULL,0),(855,'婴儿鞋帽袜',90,3,1,0,NULL,NULL,0),(856,'安全防护',90,3,1,0,NULL,NULL,0),(857,'家居床品',90,3,1,0,NULL,NULL,0),(858,'睡袋/抱被',90,3,1,0,NULL,NULL,0),(859,'爬行垫',90,3,1,0,NULL,NULL,0),(860,'妈咪包/背婴带',91,3,1,0,NULL,NULL,0),(861,'产后塑身',91,3,1,0,NULL,NULL,0),(862,'文胸/内裤',91,3,1,0,NULL,NULL,0),(863,'防辐射服',91,3,1,0,NULL,NULL,0),(864,'孕妈装',91,3,1,0,NULL,NULL,0),(865,'孕期营养',91,3,1,0,NULL,NULL,0),(866,'孕妇护肤',91,3,1,0,NULL,NULL,0),(867,'待产护理',91,3,1,0,NULL,NULL,0),(868,'月子装',91,3,1,0,NULL,NULL,0),(869,'防溢乳垫',91,3,1,0,NULL,NULL,0),(870,'套装',92,3,1,0,NULL,NULL,0),(871,'上衣',92,3,1,0,NULL,NULL,0),(872,'裤子',92,3,1,0,NULL,NULL,0),(873,'裙子',92,3,1,0,NULL,NULL,0),(874,'内衣/家居服',92,3,1,0,NULL,NULL,0),(875,'羽绒服/棉服',92,3,1,0,NULL,NULL,0),(876,'亲子装',92,3,1,0,NULL,NULL,0),(877,'儿童配饰',92,3,1,0,NULL,NULL,0),(878,'礼服/演出服',92,3,1,0,NULL,NULL,0),(879,'运动鞋',92,3,1,0,NULL,NULL,0),(880,'皮鞋/帆布鞋',92,3,1,0,NULL,NULL,0),(881,'靴子',92,3,1,0,NULL,NULL,0),(882,'凉鞋',92,3,1,0,NULL,NULL,0),(883,'功能鞋',92,3,1,0,NULL,NULL,0),(884,'户外/运动服',92,3,1,0,NULL,NULL,0),(885,'提篮式',93,3,1,0,NULL,NULL,0),(886,'安全座椅',93,3,1,0,NULL,NULL,0),(887,'增高垫',93,3,1,0,NULL,NULL,0),(888,'钱包',94,3,1,0,NULL,NULL,0),(889,'手拿包',94,3,1,0,NULL,NULL,0),(890,'单肩包',94,3,1,0,NULL,NULL,0),(891,'双肩包',94,3,1,0,NULL,NULL,0),(892,'手提包',94,3,1,0,NULL,NULL,0),(893,'斜挎包',94,3,1,0,NULL,NULL,0),(894,'钥匙包',94,3,1,0,NULL,NULL,0),(895,'卡包/零钱包',94,3,1,0,NULL,NULL,0),(896,'男士钱包',95,3,1,0,NULL,NULL,0),(897,'男士手包',95,3,1,0,NULL,NULL,0),(898,'卡包名片夹',95,3,1,0,NULL,NULL,0),(899,'商务公文包',95,3,1,0,NULL,NULL,0),(900,'双肩包',95,3,1,0,NULL,NULL,0),(901,'单肩/斜挎包',95,3,1,0,NULL,NULL,0),(902,'钥匙包',95,3,1,0,NULL,NULL,0),(903,'电脑包',96,3,1,0,NULL,NULL,0),(904,'拉杆箱',96,3,1,0,NULL,NULL,0),(905,'旅行包',96,3,1,0,NULL,NULL,0),(906,'旅行配件',96,3,1,0,NULL,NULL,0),(907,'休闲运动包',96,3,1,0,NULL,NULL,0),(908,'拉杆包',96,3,1,0,NULL,NULL,0),(909,'登山包',96,3,1,0,NULL,NULL,0),(910,'妈咪包',96,3,1,0,NULL,NULL,0),(911,'书包',96,3,1,0,NULL,NULL,0),(912,'相机包',96,3,1,0,NULL,NULL,0),(913,'腰包/胸包',96,3,1,0,NULL,NULL,0),(914,'火机烟具',97,3,1,0,NULL,NULL,0),(915,'礼品文具',97,3,1,0,NULL,NULL,0),(916,'军刀军具',97,3,1,0,NULL,NULL,0),(917,'收藏品',97,3,1,0,NULL,NULL,0),(918,'工艺礼品',97,3,1,0,NULL,NULL,0),(919,'创意礼品',97,3,1,0,NULL,NULL,0),(920,'礼盒礼券',97,3,1,0,NULL,NULL,0),(921,'鲜花绿植',97,3,1,0,NULL,NULL,0),(922,'婚庆节庆',97,3,1,0,NULL,NULL,0),(923,'京东卡',97,3,1,0,NULL,NULL,0),(924,'美妆礼品',97,3,1,0,NULL,NULL,0),(925,'礼品定制',97,3,1,0,NULL,NULL,0),(926,'京东福卡',97,3,1,0,NULL,NULL,0),(927,'古董文玩',97,3,1,0,NULL,NULL,0),(928,'箱包',98,3,1,0,NULL,NULL,0),(929,'钱包',98,3,1,0,NULL,NULL,0),(930,'服饰',98,3,1,0,NULL,NULL,0),(931,'腰带',98,3,1,0,NULL,NULL,0),(932,'太阳镜/眼镜框',98,3,1,0,NULL,NULL,0),(933,'配件',98,3,1,0,NULL,NULL,0),(934,'鞋靴',98,3,1,0,NULL,NULL,0),(935,'饰品',98,3,1,0,NULL,NULL,0),(936,'名品腕表',98,3,1,0,NULL,NULL,0),(937,'高档化妆品',98,3,1,0,NULL,NULL,0),(938,'婚嫁首饰',99,3,1,0,NULL,NULL,0),(939,'婚纱摄影',99,3,1,0,NULL,NULL,0),(940,'婚纱礼服',99,3,1,0,NULL,NULL,0),(941,'婚庆服务',99,3,1,0,NULL,NULL,0),(942,'婚庆礼品/用品',99,3,1,0,NULL,NULL,0),(943,'婚宴',99,3,1,0,NULL,NULL,0),(944,'饼干蛋糕',100,3,1,0,NULL,NULL,0),(945,'糖果/巧克力',100,3,1,0,NULL,NULL,0),(946,'休闲零食',100,3,1,0,NULL,NULL,0),(947,'冲调饮品',100,3,1,0,NULL,NULL,0),(948,'粮油调味',100,3,1,0,NULL,NULL,0),(949,'牛奶',100,3,1,0,NULL,NULL,0),(950,'其他特产',101,3,1,0,NULL,NULL,0),(951,'新疆',101,3,1,0,NULL,NULL,0),(952,'北京',101,3,1,0,NULL,NULL,0),(953,'山西',101,3,1,0,NULL,NULL,0),(954,'内蒙古',101,3,1,0,NULL,NULL,0),(955,'福建',101,3,1,0,NULL,NULL,0),(956,'湖南',101,3,1,0,NULL,NULL,0),(957,'四川',101,3,1,0,NULL,NULL,0),(958,'云南',101,3,1,0,NULL,NULL,0),(959,'东北',101,3,1,0,NULL,NULL,0),(960,'休闲零食',102,3,1,0,NULL,NULL,0),(961,'坚果炒货',102,3,1,0,NULL,NULL,0),(962,'肉干肉脯',102,3,1,0,NULL,NULL,0),(963,'蜜饯果干',102,3,1,0,NULL,NULL,0),(964,'糖果/巧克力',102,3,1,0,NULL,NULL,0),(965,'饼干蛋糕',102,3,1,0,NULL,NULL,0),(966,'无糖食品',102,3,1,0,NULL,NULL,0),(967,'米面杂粮',103,3,1,0,NULL,NULL,0),(968,'食用油',103,3,1,0,NULL,NULL,0),(969,'调味品',103,3,1,0,NULL,NULL,0),(970,'南北干货',103,3,1,0,NULL,NULL,0),(971,'方便食品',103,3,1,0,NULL,NULL,0),(972,'有机食品',103,3,1,0,NULL,NULL,0),(973,'饮用水',104,3,1,0,NULL,NULL,0),(974,'饮料',104,3,1,0,NULL,NULL,0),(975,'牛奶乳品',104,3,1,0,NULL,NULL,0),(976,'咖啡/奶茶',104,3,1,0,NULL,NULL,0),(977,'冲饮谷物',104,3,1,0,NULL,NULL,0),(978,'蜂蜜/柚子茶',104,3,1,0,NULL,NULL,0),(979,'成人奶粉',104,3,1,0,NULL,NULL,0),(980,'月饼',105,3,1,0,NULL,NULL,0),(981,'大闸蟹',105,3,1,0,NULL,NULL,0),(982,'粽子',105,3,1,0,NULL,NULL,0),(983,'卡券',105,3,1,0,NULL,NULL,0),(984,'铁观音',106,3,1,0,NULL,NULL,0),(985,'普洱',106,3,1,0,NULL,NULL,0),(986,'龙井',106,3,1,0,NULL,NULL,0),(987,'绿茶',106,3,1,0,NULL,NULL,0),(988,'红茶',106,3,1,0,NULL,NULL,0),(989,'乌龙茶',106,3,1,0,NULL,NULL,0),(990,'花草茶',106,3,1,0,NULL,NULL,0),(991,'花果茶',106,3,1,0,NULL,NULL,0),(992,'养生茶',106,3,1,0,NULL,NULL,0),(993,'黑茶',106,3,1,0,NULL,NULL,0),(994,'白茶',106,3,1,0,NULL,NULL,0),(995,'其它茶',106,3,1,0,NULL,NULL,0),(996,'项链',107,3,1,0,NULL,NULL,0),(997,'手链/脚链',107,3,1,0,NULL,NULL,0),(998,'戒指',107,3,1,0,NULL,NULL,0),(999,'耳饰',107,3,1,0,NULL,NULL,0),(1000,'毛衣链',107,3,1,0,NULL,NULL,0),(1001,'发饰/发卡',107,3,1,0,NULL,NULL,0),(1002,'胸针',107,3,1,0,NULL,NULL,0),(1003,'饰品配件',107,3,1,0,NULL,NULL,0),(1004,'婚庆饰品',107,3,1,0,NULL,NULL,0),(1005,'黄金吊坠',108,3,1,0,NULL,NULL,0),(1006,'黄金项链',108,3,1,0,NULL,NULL,0),(1007,'黄金转运珠',108,3,1,0,NULL,NULL,0),(1008,'黄金手镯/手链/脚链',108,3,1,0,NULL,NULL,0),(1009,'黄金耳饰',108,3,1,0,NULL,NULL,0),(1010,'黄金戒指',108,3,1,0,NULL,NULL,0),(1011,'K金吊坠',109,3,1,0,NULL,NULL,0),(1012,'K金项链',109,3,1,0,NULL,NULL,0),(1013,'K金手镯/手链/脚链',109,3,1,0,NULL,NULL,0),(1014,'K金戒指',109,3,1,0,NULL,NULL,0),(1015,'K金耳饰',109,3,1,0,NULL,NULL,0),(1016,'投资金',110,3,1,0,NULL,NULL,0),(1017,'投资银',110,3,1,0,NULL,NULL,0),(1018,'投资收藏',110,3,1,0,NULL,NULL,0),(1019,'银吊坠/项链',111,3,1,0,NULL,NULL,0),(1020,'银手镯/手链/脚链',111,3,1,0,NULL,NULL,0),(1021,'银戒指',111,3,1,0,NULL,NULL,0),(1022,'银耳饰',111,3,1,0,NULL,NULL,0),(1023,'足银手镯',111,3,1,0,NULL,NULL,0),(1024,'宝宝银饰',111,3,1,0,NULL,NULL,0),(1025,'裸钻',112,3,1,0,NULL,NULL,0),(1026,'钻戒',112,3,1,0,NULL,NULL,0),(1027,'钻石项链/吊坠',112,3,1,0,NULL,NULL,0),(1028,'钻石耳饰',112,3,1,0,NULL,NULL,0),(1029,'钻石手镯/手链',112,3,1,0,NULL,NULL,0),(1030,'项链/吊坠',113,3,1,0,NULL,NULL,0),(1031,'手镯/手串',113,3,1,0,NULL,NULL,0),(1032,'戒指',113,3,1,0,NULL,NULL,0),(1033,'耳饰',113,3,1,0,NULL,NULL,0),(1034,'挂件/摆件/把件',113,3,1,0,NULL,NULL,0),(1035,'玉石孤品',113,3,1,0,NULL,NULL,0),(1036,'项链/吊坠',114,3,1,0,NULL,NULL,0),(1037,'耳饰',114,3,1,0,NULL,NULL,0),(1038,'手镯/手链/脚链',114,3,1,0,NULL,NULL,0),(1039,'戒指',114,3,1,0,NULL,NULL,0),(1040,'头饰/胸针',114,3,1,0,NULL,NULL,0),(1041,'摆件/挂件',114,3,1,0,NULL,NULL,0),(1042,'琥珀/蜜蜡',115,3,1,0,NULL,NULL,0),(1043,'碧玺',115,3,1,0,NULL,NULL,0),(1044,'红宝石/蓝宝石',115,3,1,0,NULL,NULL,0),(1045,'坦桑石',115,3,1,0,NULL,NULL,0),(1046,'珊瑚',115,3,1,0,NULL,NULL,0),(1047,'祖母绿',115,3,1,0,NULL,NULL,0),(1048,'葡萄石',115,3,1,0,NULL,NULL,0),(1049,'其他天然宝石',115,3,1,0,NULL,NULL,0),(1050,'项链/吊坠',115,3,1,0,NULL,NULL,0),(1051,'耳饰',115,3,1,0,NULL,NULL,0),(1052,'手镯/手链',115,3,1,0,NULL,NULL,0),(1053,'戒指',115,3,1,0,NULL,NULL,0),(1054,'铂金项链/吊坠',116,3,1,0,NULL,NULL,0),(1055,'铂金手镯/手链/脚链',116,3,1,0,NULL,NULL,0),(1056,'铂金戒指',116,3,1,0,NULL,NULL,0),(1057,'铂金耳饰',116,3,1,0,NULL,NULL,0),(1058,'小叶紫檀',117,3,1,0,NULL,NULL,0),(1059,'黄花梨',117,3,1,0,NULL,NULL,0),(1060,'沉香木',117,3,1,0,NULL,NULL,0),(1061,'金丝楠',117,3,1,0,NULL,NULL,0),(1062,'菩提',117,3,1,0,NULL,NULL,0),(1063,'其他',117,3,1,0,NULL,NULL,0),(1064,'橄榄核/核桃',117,3,1,0,NULL,NULL,0),(1065,'檀香',117,3,1,0,NULL,NULL,0),(1066,'珍珠项链',118,3,1,0,NULL,NULL,0),(1067,'珍珠吊坠',118,3,1,0,NULL,NULL,0),(1068,'珍珠耳饰',118,3,1,0,NULL,NULL,0),(1069,'珍珠手链',118,3,1,0,NULL,NULL,0),(1070,'珍珠戒指',118,3,1,0,NULL,NULL,0),(1071,'珍珠胸针',118,3,1,0,NULL,NULL,0),(1072,'机油',119,3,1,0,NULL,NULL,0),(1073,'正时皮带',119,3,1,0,NULL,NULL,0),(1074,'添加剂',119,3,1,0,NULL,NULL,0),(1075,'汽车喇叭',119,3,1,0,NULL,NULL,0),(1076,'防冻液',119,3,1,0,NULL,NULL,0),(1077,'汽车玻璃',119,3,1,0,NULL,NULL,0),(1078,'滤清器',119,3,1,0,NULL,NULL,0),(1079,'火花塞',119,3,1,0,NULL,NULL,0),(1080,'减震器',119,3,1,0,NULL,NULL,0),(1081,'柴机油/辅助油',119,3,1,0,NULL,NULL,0),(1082,'雨刷',119,3,1,0,NULL,NULL,0),(1083,'车灯',119,3,1,0,NULL,NULL,0),(1084,'后视镜',119,3,1,0,NULL,NULL,0),(1085,'轮胎',119,3,1,0,NULL,NULL,0),(1086,'轮毂',119,3,1,0,NULL,NULL,0),(1087,'刹车片/盘',119,3,1,0,NULL,NULL,0),(1088,'维修配件',119,3,1,0,NULL,NULL,0),(1089,'蓄电池',119,3,1,0,NULL,NULL,0),(1090,'底盘装甲/护板',119,3,1,0,NULL,NULL,0),(1091,'贴膜',119,3,1,0,NULL,NULL,0),(1092,'汽修工具',119,3,1,0,NULL,NULL,0),(1093,'改装配件',119,3,1,0,NULL,NULL,0),(1094,'导航仪',120,3,1,0,NULL,NULL,0),(1095,'安全预警仪',120,3,1,0,NULL,NULL,0),(1096,'行车记录仪',120,3,1,0,NULL,NULL,0),(1097,'倒车雷达',120,3,1,0,NULL,NULL,0),(1098,'蓝牙设备',120,3,1,0,NULL,NULL,0),(1099,'车载影音',120,3,1,0,NULL,NULL,0),(1100,'净化器',120,3,1,0,NULL,NULL,0),(1101,'电源',120,3,1,0,NULL,NULL,0),(1102,'智能驾驶',120,3,1,0,NULL,NULL,0),(1103,'车载电台',120,3,1,0,NULL,NULL,0),(1104,'车载电器配件',120,3,1,0,NULL,NULL,0),(1105,'吸尘器',120,3,1,0,NULL,NULL,0),(1106,'智能车机',120,3,1,0,NULL,NULL,0),(1107,'冰箱',120,3,1,0,NULL,NULL,0),(1108,'汽车音响',120,3,1,0,NULL,NULL,0),(1109,'车载生活电器',120,3,1,0,NULL,NULL,0),(1110,'车蜡',121,3,1,0,NULL,NULL,0),(1111,'补漆笔',121,3,1,0,NULL,NULL,0),(1112,'玻璃水',121,3,1,0,NULL,NULL,0),(1113,'清洁剂',121,3,1,0,NULL,NULL,0),(1114,'洗车工具',121,3,1,0,NULL,NULL,0),(1115,'镀晶镀膜',121,3,1,0,NULL,NULL,0),(1116,'打蜡机',121,3,1,0,NULL,NULL,0),(1117,'洗车配件',121,3,1,0,NULL,NULL,0),(1118,'洗车机',121,3,1,0,NULL,NULL,0),(1119,'洗车水枪',121,3,1,0,NULL,NULL,0),(1120,'毛巾掸子',121,3,1,0,NULL,NULL,0),(1121,'脚垫',122,3,1,0,NULL,NULL,0),(1122,'座垫',122,3,1,0,NULL,NULL,0),(1123,'座套',122,3,1,0,NULL,NULL,0),(1124,'后备箱垫',122,3,1,0,NULL,NULL,0),(1125,'头枕腰靠',122,3,1,0,NULL,NULL,0),(1126,'方向盘套',122,3,1,0,NULL,NULL,0),(1127,'香水',122,3,1,0,NULL,NULL,0),(1128,'空气净化',122,3,1,0,NULL,NULL,0),(1129,'挂件摆件',122,3,1,0,NULL,NULL,0),(1130,'功能小件',122,3,1,0,NULL,NULL,0),(1131,'车身装饰件',122,3,1,0,NULL,NULL,0),(1132,'车衣',122,3,1,0,NULL,NULL,0),(1133,'安全座椅',123,3,1,0,NULL,NULL,0),(1134,'胎压监测',123,3,1,0,NULL,NULL,0),(1135,'防盗设备',123,3,1,0,NULL,NULL,0),(1136,'应急救援',123,3,1,0,NULL,NULL,0),(1137,'保温箱',123,3,1,0,NULL,NULL,0),(1138,'地锁',123,3,1,0,NULL,NULL,0),(1139,'摩托车',123,3,1,0,NULL,NULL,0),(1140,'充气泵',123,3,1,0,NULL,NULL,0),(1141,'储物箱',123,3,1,0,NULL,NULL,0),(1142,'自驾野营',123,3,1,0,NULL,NULL,0),(1143,'摩托车装备',123,3,1,0,NULL,NULL,0),(1144,'清洗美容',124,3,1,0,NULL,NULL,0),(1145,'功能升级',124,3,1,0,NULL,NULL,0),(1146,'保养维修',124,3,1,0,NULL,NULL,0),(1147,'油卡充值',124,3,1,0,NULL,NULL,0),(1148,'车险',124,3,1,0,NULL,NULL,0),(1149,'加油卡',124,3,1,0,NULL,NULL,0),(1150,'ETC',124,3,1,0,NULL,NULL,0),(1151,'驾驶培训',124,3,1,0,NULL,NULL,0),(1152,'赛事服装',125,3,1,0,NULL,NULL,0),(1153,'赛事用品',125,3,1,0,NULL,NULL,0),(1154,'制动系统',125,3,1,0,NULL,NULL,0),(1155,'悬挂系统',125,3,1,0,NULL,NULL,0),(1156,'进气系统',125,3,1,0,NULL,NULL,0),(1157,'排气系统',125,3,1,0,NULL,NULL,0),(1158,'电子管理',125,3,1,0,NULL,NULL,0),(1159,'车身强化',125,3,1,0,NULL,NULL,0),(1160,'赛事座椅',125,3,1,0,NULL,NULL,0),(1161,'跑步鞋',126,3,1,0,NULL,NULL,0),(1162,'休闲鞋',126,3,1,0,NULL,NULL,0),(1163,'篮球鞋',126,3,1,0,NULL,NULL,0),(1164,'板鞋',126,3,1,0,NULL,NULL,0),(1165,'帆布鞋',126,3,1,0,NULL,NULL,0),(1166,'足球鞋',126,3,1,0,NULL,NULL,0),(1167,'乒羽网鞋',126,3,1,0,NULL,NULL,0),(1168,'专项运动鞋',126,3,1,0,NULL,NULL,0),(1169,'训练鞋',126,3,1,0,NULL,NULL,0),(1170,'拖鞋',126,3,1,0,NULL,NULL,0),(1171,'运动包',126,3,1,0,NULL,NULL,0),(1172,'羽绒服',127,3,1,0,NULL,NULL,0),(1173,'棉服',127,3,1,0,NULL,NULL,0),(1174,'运动裤',127,3,1,0,NULL,NULL,0),(1175,'夹克/风衣',127,3,1,0,NULL,NULL,0),(1176,'卫衣/套头衫',127,3,1,0,NULL,NULL,0),(1177,'T恤',127,3,1,0,NULL,NULL,0),(1178,'套装',127,3,1,0,NULL,NULL,0),(1179,'乒羽网服',127,3,1,0,NULL,NULL,0),(1180,'健身服',127,3,1,0,NULL,NULL,0),(1181,'运动背心',127,3,1,0,NULL,NULL,0),(1182,'毛衫/线衫',127,3,1,0,NULL,NULL,0),(1183,'运动配饰',127,3,1,0,NULL,NULL,0),(1184,'折叠车',128,3,1,0,NULL,NULL,0),(1185,'山地车/公路车',128,3,1,0,NULL,NULL,0),(1186,'电动车',128,3,1,0,NULL,NULL,0),(1187,'其他整车',128,3,1,0,NULL,NULL,0),(1188,'骑行服',128,3,1,0,NULL,NULL,0),(1189,'骑行装备',128,3,1,0,NULL,NULL,0),(1190,'平衡车',128,3,1,0,NULL,NULL,0),(1191,'鱼竿鱼线',129,3,1,0,NULL,NULL,0),(1192,'浮漂鱼饵',129,3,1,0,NULL,NULL,0),(1193,'钓鱼桌椅',129,3,1,0,NULL,NULL,0),(1194,'钓鱼配件',129,3,1,0,NULL,NULL,0),(1195,'钓箱鱼包',129,3,1,0,NULL,NULL,0),(1196,'其它',129,3,1,0,NULL,NULL,0),(1197,'泳镜',130,3,1,0,NULL,NULL,0),(1198,'泳帽',130,3,1,0,NULL,NULL,0),(1199,'游泳包防水包',130,3,1,0,NULL,NULL,0),(1200,'女士泳衣',130,3,1,0,NULL,NULL,0),(1201,'男士泳衣',130,3,1,0,NULL,NULL,0),(1202,'比基尼',130,3,1,0,NULL,NULL,0),(1203,'其它',130,3,1,0,NULL,NULL,0),(1204,'冲锋衣裤',131,3,1,0,NULL,NULL,0),(1205,'速干衣裤',131,3,1,0,NULL,NULL,0),(1206,'滑雪服',131,3,1,0,NULL,NULL,0),(1207,'羽绒服/棉服',131,3,1,0,NULL,NULL,0),(1208,'休闲衣裤',131,3,1,0,NULL,NULL,0),(1209,'抓绒衣裤',131,3,1,0,NULL,NULL,0),(1210,'软壳衣裤',131,3,1,0,NULL,NULL,0),(1211,'T恤',131,3,1,0,NULL,NULL,0),(1212,'户外风衣',131,3,1,0,NULL,NULL,0),(1213,'功能内衣',131,3,1,0,NULL,NULL,0),(1214,'军迷服饰',131,3,1,0,NULL,NULL,0),(1215,'登山鞋',131,3,1,0,NULL,NULL,0),(1216,'雪地靴',131,3,1,0,NULL,NULL,0),(1217,'徒步鞋',131,3,1,0,NULL,NULL,0),(1218,'越野跑鞋',131,3,1,0,NULL,NULL,0),(1219,'休闲鞋',131,3,1,0,NULL,NULL,0),(1220,'工装鞋',131,3,1,0,NULL,NULL,0),(1221,'溯溪鞋',131,3,1,0,NULL,NULL,0),(1222,'沙滩/凉拖',131,3,1,0,NULL,NULL,0),(1223,'户外袜',131,3,1,0,NULL,NULL,0),(1224,'帐篷/垫子',132,3,1,0,NULL,NULL,0),(1225,'睡袋/吊床',132,3,1,0,NULL,NULL,0),(1226,'登山攀岩',132,3,1,0,NULL,NULL,0),(1227,'户外配饰',132,3,1,0,NULL,NULL,0),(1228,'背包',132,3,1,0,NULL,NULL,0),(1229,'户外照明',132,3,1,0,NULL,NULL,0),(1230,'户外仪表',132,3,1,0,NULL,NULL,0),(1231,'户外工具',132,3,1,0,NULL,NULL,0),(1232,'望远镜',132,3,1,0,NULL,NULL,0),(1233,'旅游用品',132,3,1,0,NULL,NULL,0),(1234,'便携桌椅床',132,3,1,0,NULL,NULL,0),(1235,'野餐烧烤',132,3,1,0,NULL,NULL,0),(1236,'军迷用品',132,3,1,0,NULL,NULL,0),(1237,'救援装备',132,3,1,0,NULL,NULL,0),(1238,'滑雪装备',132,3,1,0,NULL,NULL,0),(1239,'极限户外',132,3,1,0,NULL,NULL,0),(1240,'冲浪潜水',132,3,1,0,NULL,NULL,0),(1241,'综合训练器',133,3,1,0,NULL,NULL,0),(1242,'其他大型器械',133,3,1,0,NULL,NULL,0),(1243,'哑铃',133,3,1,0,NULL,NULL,0),(1244,'仰卧板/收腹机',133,3,1,0,NULL,NULL,0),(1245,'其他中小型器材',133,3,1,0,NULL,NULL,0),(1246,'瑜伽舞蹈',133,3,1,0,NULL,NULL,0),(1247,'甩脂机',133,3,1,0,NULL,NULL,0),(1248,'踏步机',133,3,1,0,NULL,NULL,0),(1249,'武术搏击',133,3,1,0,NULL,NULL,0),(1250,'健身车/动感单车',133,3,1,0,NULL,NULL,0),(1251,'跑步机',133,3,1,0,NULL,NULL,0),(1252,'运动护具',133,3,1,0,NULL,NULL,0),(1253,'羽毛球',134,3,1,0,NULL,NULL,0),(1254,'乒乓球',134,3,1,0,NULL,NULL,0),(1255,'篮球',134,3,1,0,NULL,NULL,0),(1256,'足球',134,3,1,0,NULL,NULL,0),(1257,'网球',134,3,1,0,NULL,NULL,0),(1258,'排球',134,3,1,0,NULL,NULL,0),(1259,'高尔夫',134,3,1,0,NULL,NULL,0),(1260,'台球',134,3,1,0,NULL,NULL,0),(1261,'棋牌麻将',134,3,1,0,NULL,NULL,0),(1262,'轮滑滑板',134,3,1,0,NULL,NULL,0),(1263,'其他',134,3,1,0,NULL,NULL,0),(1264,'0-6个月',135,3,1,0,NULL,NULL,0),(1265,'6-12个月',135,3,1,0,NULL,NULL,0),(1266,'1-3岁',135,3,1,0,NULL,NULL,0),(1267,'3-6岁',135,3,1,0,NULL,NULL,0),(1268,'6-14岁',135,3,1,0,NULL,NULL,0),(1269,'14岁以上',135,3,1,0,NULL,NULL,0),(1270,'遥控车',136,3,1,0,NULL,NULL,0),(1271,'遥控飞机',136,3,1,0,NULL,NULL,0),(1272,'遥控船',136,3,1,0,NULL,NULL,0),(1273,'机器人',136,3,1,0,NULL,NULL,0),(1274,'轨道/助力',136,3,1,0,NULL,NULL,0),(1275,'毛绒/布艺',137,3,1,0,NULL,NULL,0),(1276,'靠垫/抱枕',137,3,1,0,NULL,NULL,0),(1277,'芭比娃娃',138,3,1,0,NULL,NULL,0),(1278,'卡通娃娃',138,3,1,0,NULL,NULL,0),(1279,'智能娃娃',138,3,1,0,NULL,NULL,0),(1280,'仿真模型',139,3,1,0,NULL,NULL,0),(1281,'拼插模型',139,3,1,0,NULL,NULL,0),(1282,'收藏爱好',139,3,1,0,NULL,NULL,0),(1283,'炫舞毯',140,3,1,0,NULL,NULL,0),(1284,'爬行垫/毯',140,3,1,0,NULL,NULL,0),(1285,'户外玩具',140,3,1,0,NULL,NULL,0),(1286,'戏水玩具',140,3,1,0,NULL,NULL,0),(1287,'电影周边',141,3,1,0,NULL,NULL,0),(1288,'卡通周边',141,3,1,0,NULL,NULL,0),(1289,'网游周边',141,3,1,0,NULL,NULL,0),(1290,'摇铃/床铃',142,3,1,0,NULL,NULL,0),(1291,'健身架',142,3,1,0,NULL,NULL,0),(1292,'早教启智',142,3,1,0,NULL,NULL,0),(1293,'拖拉玩具',142,3,1,0,NULL,NULL,0),(1294,'积木',143,3,1,0,NULL,NULL,0),(1295,'拼图',143,3,1,0,NULL,NULL,0),(1296,'磁力棒',143,3,1,0,NULL,NULL,0),(1297,'立体拼插',143,3,1,0,NULL,NULL,0),(1298,'手工彩泥',144,3,1,0,NULL,NULL,0),(1299,'绘画工具',144,3,1,0,NULL,NULL,0),(1300,'情景玩具',144,3,1,0,NULL,NULL,0),(1301,'减压玩具',145,3,1,0,NULL,NULL,0),(1302,'创意玩具',145,3,1,0,NULL,NULL,0),(1303,'钢琴',146,3,1,0,NULL,NULL,0),(1304,'电子琴/电钢琴',146,3,1,0,NULL,NULL,0),(1305,'吉他/尤克里里',146,3,1,0,NULL,NULL,0),(1306,'打击乐器',146,3,1,0,NULL,NULL,0),(1307,'西洋管弦',146,3,1,0,NULL,NULL,0),(1308,'民族管弦乐器',146,3,1,0,NULL,NULL,0),(1309,'乐器配件',146,3,1,0,NULL,NULL,0),(1310,'电脑音乐',146,3,1,0,NULL,NULL,0),(1311,'工艺礼品乐器',146,3,1,0,NULL,NULL,0),(1312,'口琴/口风琴/竖笛',146,3,1,0,NULL,NULL,0),(1313,'手风琴',146,3,1,0,NULL,NULL,0),(1314,'双色球',147,3,1,0,NULL,NULL,0),(1315,'大乐透',147,3,1,0,NULL,NULL,0),(1316,'福彩3D',147,3,1,0,NULL,NULL,0),(1317,'排列三',147,3,1,0,NULL,NULL,0),(1318,'排列五',147,3,1,0,NULL,NULL,0),(1319,'七星彩',147,3,1,0,NULL,NULL,0),(1320,'七乐彩',147,3,1,0,NULL,NULL,0),(1321,'竞彩足球',147,3,1,0,NULL,NULL,0),(1322,'竞彩篮球',147,3,1,0,NULL,NULL,0),(1323,'新时时彩',147,3,1,0,NULL,NULL,0),(1324,'国内机票',148,3,1,0,NULL,NULL,0),(1325,'国内酒店',149,3,1,0,NULL,NULL,0),(1326,'酒店团购',149,3,1,0,NULL,NULL,0),(1327,'度假',150,3,1,0,NULL,NULL,0),(1328,'景点',150,3,1,0,NULL,NULL,0),(1329,'租车',150,3,1,0,NULL,NULL,0),(1330,'火车票',150,3,1,0,NULL,NULL,0),(1331,'旅游团购',150,3,1,0,NULL,NULL,0),(1332,'手机充值',151,3,1,0,NULL,NULL,0),(1333,'游戏点卡',152,3,1,0,NULL,NULL,0),(1334,'QQ充值',152,3,1,0,NULL,NULL,0),(1335,'电影票',153,3,1,0,NULL,NULL,0),(1336,'演唱会',153,3,1,0,NULL,NULL,0),(1337,'话剧歌剧',153,3,1,0,NULL,NULL,0),(1338,'音乐会',153,3,1,0,NULL,NULL,0),(1339,'体育赛事',153,3,1,0,NULL,NULL,0),(1340,'舞蹈芭蕾',153,3,1,0,NULL,NULL,0),(1341,'戏曲综艺',153,3,1,0,NULL,NULL,0),(1342,'东北',154,3,1,0,NULL,NULL,0),(1343,'华北',154,3,1,0,NULL,NULL,0),(1344,'西北',154,3,1,0,NULL,NULL,0),(1345,'华中',154,3,1,0,NULL,NULL,0),(1346,'华东',154,3,1,0,NULL,NULL,0),(1347,'华南',154,3,1,0,NULL,NULL,0),(1348,'西南',154,3,1,0,NULL,NULL,0),(1349,'苹果',155,3,1,0,NULL,NULL,0),(1350,'橙子',155,3,1,0,NULL,NULL,0),(1351,'奇异果/猕猴桃',155,3,1,0,NULL,NULL,0),(1352,'车厘子/樱桃',155,3,1,0,NULL,NULL,0),(1353,'芒果',155,3,1,0,NULL,NULL,0),(1354,'蓝莓',155,3,1,0,NULL,NULL,0),(1355,'火龙果',155,3,1,0,NULL,NULL,0),(1356,'葡萄/提子',155,3,1,0,NULL,NULL,0),(1357,'柚子',155,3,1,0,NULL,NULL,0),(1358,'香蕉',155,3,1,0,NULL,NULL,0),(1359,'牛油果',155,3,1,0,NULL,NULL,0),(1360,'梨',155,3,1,0,NULL,NULL,0),(1361,'菠萝/凤梨',155,3,1,0,NULL,NULL,0),(1362,'桔/橘',155,3,1,0,NULL,NULL,0),(1363,'柠檬',155,3,1,0,NULL,NULL,0),(1364,'草莓',155,3,1,0,NULL,NULL,0),(1365,'桃/李/杏',155,3,1,0,NULL,NULL,0),(1366,'更多水果',155,3,1,0,NULL,NULL,0),(1367,'水果礼盒/券',155,3,1,0,NULL,NULL,0),(1368,'牛肉',156,3,1,0,NULL,NULL,0),(1369,'羊肉',156,3,1,0,NULL,NULL,0),(1370,'猪肉',156,3,1,0,NULL,NULL,0),(1371,'内脏类',156,3,1,0,NULL,NULL,0),(1372,'鱼类',157,3,1,0,NULL,NULL,0),(1373,'虾类',157,3,1,0,NULL,NULL,0),(1374,'蟹类',157,3,1,0,NULL,NULL,0),(1375,'贝类',157,3,1,0,NULL,NULL,0),(1376,'海参',157,3,1,0,NULL,NULL,0),(1377,'海产干货',157,3,1,0,NULL,NULL,0),(1378,'其他水产',157,3,1,0,NULL,NULL,0),(1379,'海产礼盒',157,3,1,0,NULL,NULL,0),(1380,'鸡肉',158,3,1,0,NULL,NULL,0),(1381,'鸭肉',158,3,1,0,NULL,NULL,0),(1382,'蛋类',158,3,1,0,NULL,NULL,0),(1383,'其他禽类',158,3,1,0,NULL,NULL,0),(1384,'水饺/馄饨',159,3,1,0,NULL,NULL,0),(1385,'汤圆/元宵',159,3,1,0,NULL,NULL,0),(1386,'面点',159,3,1,0,NULL,NULL,0),(1387,'火锅丸串',159,3,1,0,NULL,NULL,0),(1388,'速冻半成品',159,3,1,0,NULL,NULL,0),(1389,'奶酪黄油',159,3,1,0,NULL,NULL,0),(1390,'熟食',160,3,1,0,NULL,NULL,0),(1391,'腊肠/腊肉',160,3,1,0,NULL,NULL,0),(1392,'火腿',160,3,1,0,NULL,NULL,0),(1393,'糕点',160,3,1,0,NULL,NULL,0),(1394,'礼品卡券',160,3,1,0,NULL,NULL,0),(1395,'冷藏果蔬汁',161,3,1,0,NULL,NULL,0),(1396,'冰激凌',161,3,1,0,NULL,NULL,0),(1397,'其他',161,3,1,0,NULL,NULL,0),(1398,'叶菜类',162,3,1,0,NULL,NULL,0),(1399,'茄果瓜类',162,3,1,0,NULL,NULL,0),(1400,'根茎类',162,3,1,0,NULL,NULL,0),(1401,'鲜菌菇',162,3,1,0,NULL,NULL,0),(1402,'葱姜蒜椒',162,3,1,0,NULL,NULL,0),(1403,'半加工蔬菜',162,3,1,0,NULL,NULL,0),(1404,'微型车',163,3,1,0,NULL,NULL,0),(1405,'小型车',163,3,1,0,NULL,NULL,0),(1406,'紧凑型车',163,3,1,0,NULL,NULL,0),(1407,'中型车',163,3,1,0,NULL,NULL,0),(1408,'中大型车',163,3,1,0,NULL,NULL,0),(1409,'豪华车',163,3,1,0,NULL,NULL,0),(1410,'MPV',163,3,1,0,NULL,NULL,0),(1411,'SUV',163,3,1,0,NULL,NULL,0),(1412,'跑车',163,3,1,0,NULL,NULL,0),(1413,'微型车(二手)',164,3,1,0,NULL,NULL,0),(1414,'小型车(二手)',164,3,1,0,NULL,NULL,0),(1415,'紧凑型车(二手)',164,3,1,0,NULL,NULL,0),(1416,'中型车(二手)',164,3,1,0,NULL,NULL,0),(1417,'中大型车(二手)',164,3,1,0,NULL,NULL,0),(1418,'豪华车(二手)',164,3,1,0,NULL,NULL,0),(1419,'MPV(二手)',164,3,1,0,NULL,NULL,0),(1420,'SUV(二手)',164,3,1,0,NULL,NULL,0),(1421,'跑车(二手)',164,3,1,0,NULL,NULL,0),(1422,'皮卡(二手)',164,3,1,0,NULL,NULL,0),(1423,'面包车(二手)',164,3,1,0,NULL,NULL,0),(1431,'dsa323',1,2,1,NULL,NULL,NULL,NULL),(1432,'fdsffdsadddd大萨达',1431,3,1,NULL,NULL,NULL,NULL);


以上就将商品分类的表和数据都给创建好了,接下来我们就需要进行代码编写了。

6.1.2 查出所有分类及其子分类

1、CategoryController

gulimall-product中的controller包下的CategoryController

  • 在类中对原来逆向生成的代码进行修改,
@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){
        List<CategoryEntity> entities =  categoryService.listWithTree();

        return R.ok().put("data", entities);
    }
 }

2、CategoryService

接着我们使用idea自带的工具帮助我们生成相应的方法。

/**
 * 商品三级分类
 */
public interface CategoryService extends IService<CategoryEntity> {

    void removeMenuByIds(List<Long> asList);    //本次修改的方法
}

3、CategoryServiceImpl

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

//    @Autowired
//    CategoryDao categoryDao;   //其实这里ServiceImpl的泛型就是categoryDao,所以我们可以直接使用ServiceImpl里面的baseMapper

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        //没有查询条件,就是查询所有
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2.组装成父子的树形结构
        //2.1 找到所有的一级分类  categoryEntity -> {} lambda表达式
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
            categoryEntity.getParentCid() == 0   //一级分类的父分类id是0   过滤
        ).map((menu) -> {    //在菜单收集成list之前先通过递归找到菜单的所有子分类,放在map中,然后排序,即将当前菜单改了之后重新返回,然后再收集菜单。
            menu.setChildren(getChildrens(menu,entities));
            return menu;
        }).sorted((menu1,menu2) -> {
            return (menu1.getSort() == null ?0:menu1.getSort()) - (menu2.getSort() == null ?0:menu2.getSort());   //子菜单肯定有前一个和后一个之分,
        }).collect(Collectors.toList());


        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        baseMapper.deleteBatchIds(asList);
    }

    //递归查找所有的子菜单
    //root  当前菜单   all  所有菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){

        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId(); // 二级菜单的父分类id == 一级分类的catid

        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildrens(categoryEntity, all));   // 二级菜单下还有三级菜单,继续查找
            return categoryEntity;

        }).sorted((menu1, menu2) -> {    //sorted()定制排序
            return (menu1.getSort() == null ?0:menu1.getSort()) - (menu2.getSort() == null ?0:menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }

}

这里使用的是流式编程,对于这方面我们可去参考java8新特性的StreamAPI来进行相应的学习。

4、启动测试(端口占用问题解决)

我们启动gulimall-product微服务进行测试查询。在启动的时候发现有报错

Description:

The Tomcat connector configured to listen on port 10000 failed to start. The port may already be in use or the connector may be misconfigured.

Action:

Verify the connector's configuration, identify and stop any process that's listening on port 10000, or configure this application to listen on another port.

上面的意思是我们的10000端口被占用。接下来我们看看到底是什么服务占用了10000端口。

在这里插入图片描述
在这里插入图片描述

image-20221119171718001

netstat -aon |findstr "12000"     #找到12000端口
taskkill /F /PID  6164   #杀死12000端口对应的线程

记:深信服vpn进程在日常工作中需要,而且不知道为什么杀不死?

  • 解决办法:暂时修改gulimall_product端口为11000,gulimall_ware端口为12000.

    我们接着进行测试,浏览器发送http://localhost:11000/product/category/list/tree,测试结果如下图,显示正确。这里我们推荐浏览器装一个Json格式的处理的插件可以很好的帮助我们查看Json数据。

  • 查询所有


6.1.3 配置网关路由与路径重写

启动renren-fast微服务,这个是后台。将renren-fast-vue在vscode中启动,接着我们来到后台系统进行菜单模块的添加。

1、 后台添加目录和菜单

  1. 在菜单管理中添加一个商品系统的目录。如下图。image-20221027213836441

  2. 在商品系统中新增一个分类维护的菜单。菜单的路由其实就是我们商品微服务中的访问路径。

image-20221027214924676

我们在后台系统中修改的,在数据库的gulimall-admin中也会同步进行修改。

image-20221027215105624

  • 我们可以看到如果我们点击角色管理的话,地址栏是/sys-role,但是我们实际发送的请求应该是/sys/role,所以由此可以知道后台会将/自动转换为-,同理我们去访问/product/category也会自动被转换为/product-category

  • 我们在renren-fast-vue中可以看到有一个文件,对应的其实就是/sys-role,即sys文件夹下的role.vue对应的就是角色管理这个页面的展示。所以对于商品分类/product/category,我们接下来要做的就是在renren-fast-vue下创建一个product文件夹,文件夹中创建一个category.vue来进行页面展示。

image-20221029122852894

image-20221027215558125

2、编写树形结构

  1. 对于这一段前端开发的代码,我们可以借鉴element.eleme.cn中的快速开发指南进行编写。
<template>
    <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
    name: 'category',
    components: {},
    directives: {},
     data() {
      return {
        data: [],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    mounted() {
        
    },
    methods: {
        handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(data=>{
            console.log(data)
        })
      }
    },
    created(){
        this.getMenus();
    }
};
</script>

<style scoped>

</style>
  1. 进行测试

测试中发现检查网页源代码发现,本来应该是给商品微服务11000端口发送的查询的,但是发送到了renren-fast了。

renren-fast-vue中有一个Index.js是管理api接口请求地址的,如下图。如果我们本次只是简单的将8080改为11000端口,那么当下次如果是12000呢?难道每次都要改吗?所以我们的下一步做法是使用网关进行路由。通过网关映射到具体的请求地址。

image-20221027222102566

注意这里截图中vue的格式是有错误的,如果不进行修正的话,运行会报错。我们前端的代码中只要显示了红色的,就要检查是哪里错误

image-20221028095819963

对于微服务,后面我们统一改为加api前缀才能路由过去。

接下来进行测试访问,我们发现验证码一直加载不出来。检查网页源代码发现是因为我们直接给网关发送验证码请求了。但是真实的应该是给renren-fast发送请求。image-20221027222409536

3、将renren-fast注册进nacos,使用网关进行统一管理并进行路径重写

  • 引入gulimall-common

image-20221027222544585

  • 在renren-fast的application.yml文件中配置nacos注册中心地址image-20221027222916180

  • 在renren-fast的主启动类上加入@EnableDiscoveryClient注解,使得该微服务会被注册中心发现image-20221027222947987

  • 在网关配置文件中加入:

    - id: admin_route
      uri: lb://renren-fast # 路由给renren-fast,lb代表负载均衡
      predicates:  # 什么情况下路由给它
       - Path=/api/** # 默认前端项目都带上api前缀,路径中带了api前缀的
      filters:
           - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
    

    注意

    这个地方一定要格式对齐,否则启动后会出现下面这个问题:

    启动报错:Caused by: org.yaml.snakeyaml.scanner.ScannerException: mapping values are not allowed here

    这个地方报错的原因大概率是yml文件语法错误:注意这个坑找了好久,id uri predicates filters都要对齐,同一层级。

4、启动测试(有坑)

  1. 最开始进行启动,在renren-fast的CorsConfig跨域配置中,allowedOriginPatterns报错。出现原因是因为:我们使用的springboot版本是2.1.8.RELEASE。所以将这个.allowedOriginPatterns换成.allowedOrigins即可。

image-20221028084313631

image-20221028084331356

  1. 最开始报错,在b站看了评论和弹幕之后将gulimall-common这个依赖给取消了,因为启动报依赖循环报错。后面我将所有的依赖都换成老师的同样的版本之后就没有了。
  • 记一次错误踩坑 (这个在后来将gulimall下的所有依赖进行按照老师统一版本进行重构之后,还是使用的引入)

    对于renren-fast中不要引入gulimall-common这个依赖,否则会出现依赖循环报错这个情况以及一系列的突发情况,正常应该是我们自己引入nacos的pom。

    舍弃:

    	<dependency>
    		<groupId>com.alibaba.cloud</groupId>
    		<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    		<version>2.1.0.RELEASE</version>
    	</dependency>
    
    	<!--nacos作为注册中心,服务注册与发现-->
    	<dependency>
    		<groupId>com.alibaba.cloud</groupId>
    		<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    		<version>2.1.0.RELEASE</version>
    	</dependency>
    

如此这样之后,启动renren-fast项目后发现仍然报错,报错的原因居然是:no server available nacos

这样的错误可能是application.yml文件中nacos配置错误。回去一看,果然是写成了nacos-config-addr,其实应该是nacos-discovery-addr . (注意,细心啊。)

再次启动,启动成功。

image-20221028094934894

鉴于上面出现很多错误,但是老师视频中没有出现这些错误,大概率是因为依赖的原因,所以对于gulimall中所有的依赖进行统一,按照老师的依赖进行配置。以防止后面出现很多突发的错误。

  • 根据老师的依赖进行重新设置,然后重新运行网关。

修改后运行成功,验证码出现。

5、浏览器跨域问题

上面我们验证码出现了,但是我们登录却报错,原因在于浏览器的跨域问题。

image-20221028141815905

  1. 引入浏览器跨域知识

image-20221029163137756

image-20221029163152496

image-20221029163210926

image-20221029163222814

  1. 解决办法:在gulimall-gateway中设置GulimallCorsConfiguration解决跨域问题

我们在gulimall-gateway中创建一个config来存放GulimallCorsConfiguration。注意这个包一定是要在gateway这个包下,否则启动报错(坑)。


@Configuration
public class GulimallCorsConfiguration {

    @Bean   // 添加过滤器
    public CorsWebFilter corsWebFilter(){
        // 基于url跨域,选择reactive包下的
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 跨域配置信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        // 允许跨域的头
        corsConfiguration.addAllowedHeader("*");
        // 允许跨域的请求方式
        corsConfiguration.addAllowedMethod("*");
        // 允许跨域的请求来源
        corsConfiguration.addAllowedOrigin("*");
        // 是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);
        // 任意url都要进行跨域配置
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}
  1. 再次启动测试

浏览器检查报错,报错的原因是:renren-fast中也配置了跨域,但是我们只需要一个,所以要给注释掉。

image-20221028163003311

image-20221029165122758

  1. 启动发现还是报错,报错的原因是404:找不到。那么接下来我们就将商品服务也注册到nacos中。

image-20221028163912532

6.1.4 树形展示三级分类数据

image-20221029170058736

在显示商品系统/分类信息的时候,出现了404异常,请求的http://localhost:88/api/product/category/list/tree不存在
只有通过http://localhost:11000/product/category/list/tree路径才能够正常访问,所以会报404异常。这是路径映射错误。我们需要在网关中进行路径重写,让网关帮我们转到正确的地址。

1、商品微服务注册到nacos

  1. 在nacos中为商品微服务设置单独的命名空间,以后商品微服务的配置都在该命名空间下读取。image-20221028165423516

配置naocs标准步骤:

1)、bootstrap.properties中将命名空间添加上

image-20221029165532163

2)、application.yml中配置nacos注册中心地址

image-20221028165758595

3)、主启动类上加入服务注册发现注解

image-20221028165813825

4)、启动测试,成功注册上。image-20221028165827261

2、在网关中对商品微服务进行路径重写

- id: product_route
  uri: lb://gulimall-product
  predicates:
       - Path=/api/product/**
  filters:
      - RewritePath=/api/(?<segment>/?.*), /$\{segment}

注意:

访问 localhost:88/api/product/category/list/tree invalid token,非法令牌,后台管理系统中没有登录,所以没有带令牌

原因:先匹配的先路由,fast和product路由重叠,fast要求登录

修正:在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中try catch子句中异常的处理顺序。

http://localhost:88/api/product/category/list/tree 正常

访问http://localhost:8001/#/product-category,正常

原因是:先访问网关88,网关路径重写后访问nacos8848,nacos找到服务

image-20221028170358650

image-20221028170508287

image-20221028170530357

上面的测试,显访问正确。

3、前端代码修改

我们使用{}将data的数据进行解构:

image-20221028170907519

  //获取菜单集合
  methods: {
     getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功获取到菜单数据....", data.data);
        this.menus = data.data;
      });
    },//fail
        .catch(() => {});
    },

image-20221028170940375

image-20221028171253462

image-20221028171416595


此时有了3级结构,但是没有数据,在category.vue的模板中,数据是menus,而还有一个props。这是element-ui的规则
<template>
  <div>
    <el-tree
      :data="menus"
      :props="defaultProps"
    >

而在data中
defaultProps: {
        children: "children",
        label: "name"
}

6.1.5 删除数据----逻辑删除

1、前端代码

node与data
在element-ui的tree中,有2个非常重要的属性

node代表当前节点(是否展开等信息,element-ui自带属性)
data是节点数据,是自己的数据。
data从哪里来:前面ajax发送请求,拿到data,赋值给menus属性,而menus属性绑定到标签的data属性。而node是ui的默认规则

  • 在每一个菜单后面添加append, delete

  • 点击按钮时,不进行菜单的打开合并:expand-on-click-node="false"

  • 当没有子菜单时,才可以显示delete按钮。当为一级、二级菜单时,才显示append按钮

  • 添加多选框show-checkbox

  • 设置node-key=""标识每一个节点的不同

<!--  -->
<template>
  <el-tree
    :data="menus"
    show-checkbox
    :props="defaultProps"
    @node-click="handleNodeClick"
    :expand-on-click-node="false"
    node-key="catId"
  >
    <span class="custom-tree-node" slot-scope="{ node, data }">
      <span>{{ node.label }}</span>
      <span>
        <el-button
          type="text"
          v-if="node.level <= 2"
          size="mini"
          @click="() => append(data)"
        >
          Append
        </el-button>
        <el-button
          type="text"
          v-if="node.childNodes.length == 0"
          size="mini"
          @click="() => remove(node, data)"
        >
          Delete
        </el-button>
      </span>
    </span>
  </el-tree>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  data() {
    return {
      menus: [],
      defaultProps: {
        children: "children", //子节点
        label: "name", //name属性作为标签的值,展示出来
      },
    };
  },
  methods: {
    handleNodeClick(data) {},
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功了获取到菜单数据....", data.data);
        this.menus = data.data;
      });
    },
    append(data) {
      console.log("append", data);
    },
    remove(node, data) {
      console.log("remove", node, data);
    },
  },
  //监听属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //生命周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

2、逻辑删除

  1. 删除之前,我们要先检查该菜单是否被引用了。
  • 修改CategoryController类
 @RequestMapping("/delete")
    public R delete(@RequestBody Long[] catIds){
        //删除之前需要判断待删除的菜单那是否被别的地方所引用。
//		categoryService.removeByIds(Arrays.asList(catIds));

        categoryService.removeMenuByIds(Arrays.asList(catIds));
        return R.ok();
    }
  • CategoryServiceImpl类
@Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 1.检查当前删除的菜单,是否被别的地方引用
        //其实开发中使用的都是逻辑删除,并不是真正物理意义上的删除
        baseMapper.deleteBatchIds(asList);
    }

image-20221029112316511

在学习的过程中,看到老师使用TODO才知道IDEA有一个类似备忘录的功能。

  1. 对于开发中,我们常常采用的是逻辑删除,即设置一个show_status 为0,表示被删除。

我们使用mybatis-plus中的逻辑删除语法:

image-20221029181434228

1)、配置全局逻辑删除规则

application.yml中

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto             #主键自增
      logic-delete-value: 1     #1表示删除
      logic-not-delete-value: 0   #0表示未删除

注意:这里有一个坑,数据库中我们最开始设置的是1:未删除,0:删除。这个坑马上解决。

image-20221029181701991

配置之后,我们可以继续使用APIFox进行测试,实际测试成功。为了验证,我们也可以在application.yml设置一个全局打印日志,将sql语句打印出来。

image-20221029181847775

==>  Preparing: UPDATE pms_category SET show_status=0 WHERE cat_id IN ( ? ) AND show_status=1 
==> Parameters: 1000(Long)
<==    Updates: 1

打印的sql语句,可以看到删除其实是更新。

3、测试删除

测试删除数据,打开postman或者是APIFox都可以(推荐使用APIFox)

输入“ http://localhost:88/api/product/category/delete ”,请求方式设置为POST,为了比对效果,可以在删除之前查询数据库的pms_category表:

delete请求传入的是数组,所以我们使用json数据。

image-20221029172859327

image-20221028185033858

image-20221028192015897

image-20221028192032411

image-20221028203336926

删除1434,之后从1---0,即逻辑删除正确。

4、前端代码

发送的请求:delete

发送的数据:this.$http.adornData(ids, false)

util/httpRequest.js中,封装了一些拦截器

http.adornParams是封装get请求的数据

http.adornData封装post请求的数据

ajax的get请求会被缓存,就不会请求服务器了。

所以我们在url后面拼接个date,让他每次都请求服务器

  • 编写前端remove方法,实现向后端发送请求
  • 点击delete弹出提示框,是否删除这个节点: elementui中MessageBox 弹框中的确认消息添加到删除之前
  • 删除成功后有消息提示: elementui中Message 消息提示
  • 原来展开状态的菜单栏,在删除之后也应该展开: el-tree组件的default-expanded-keys属性,默认展开。 每次删除之后,把删除菜单的父菜单的id值赋给默认展开值即可。

注意:

前端向后端发送post请求和get请求。对于这个我们可以设置一个自定义的代码块。文件->首选项->用户片段,以后我们就可以通过快捷键直接进行输出了。

"http-get请求": {
        "prefix": "httpget",
        "body":[
            "this.\\$http({",
            "url: this.\\$http.adornUrl(''),",
            "method:'get',",
            "params:this.\\$http.adornParams({})",
            "}).then(({data})=>{",
            "})"
        ],
        "description":"httpGET请求"
    },

    "http-post请求":{
        "prefix":"httppost",
        "body":[
            "this.\\$http({",
            "url:this.\\$http.adornUrl(''),",
            "method:'post',",
            "data: this.\\$http.adornData(data, false)",
            "}).then(({data})=>{ })"
        ],
        "description":"httpPOST请求"
    }

要求:删除之后,显示弹窗,而且展开的菜单仍然展开。

image-20221028213346094

//在el-tree中设置默认展开属性,绑定给expandedKey
:default-expanded-keys="expandedKey"

//data中添加属性
expandedKey: [],

//完整的remove方法
    remove(node, data) {
      var ids = [data.catId];
      this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false),
          }).then(({ data }) => {
            this.$message({
              message: "菜单删除成功",
              type: "success",
            });
            //刷新出新的菜单
            this.getMenus();
            //设置需要默认展开的菜单
            this.expandedKey = [node.parent.data.catId]
          });
        })
        .catch(() => {});
    },

6.1.6 新增分类

    1. elementui中Dialog 对话框
  • 一个会话的属性为:visible.sync=“dialogVisible”

  • 导出的data中"dialogVisible = false"

  • 点击确认或者取消后的逻辑都是@click=“dialogVisible = false” 关闭会话

    1. 点击append,弹出对话框,输入分类名称
    1. 点击确定,添加到数据库: 新建方法addCategory发送post请求到后端; 因为要把数据添加到数据库,所以在前端数据中按照数据库的格式声明一个category。点击append时,计算category属性,点击确定时发送post请求。
<!--对话框组件-->
	<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
      <el-form :model="categroy">
        <el-form-item label="分类名称">
          <el-input v-model="categroy.name" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addCategory">确 定</el-button>
      </span>
    </el-dialog>

//1.data中新增数据
//按照数据库格式声明的数据
      categroy: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },
//判断是否显示对话框
      dialogVisible: false,

          
//修改append方法,新增addCategory方法
//点击append后,计算category属性,显示对话框
    append(data) {
      console.log("append", data);
      this.dialogVisible = true;
      this.categroy.parentCid = data.catId;
      this.categroy.catLevel = data.catLevel * 1 + 1;
    },

//点击确定后,发送post请求
//成功后显示添加成功,展开刚才的菜单
    addCategory() {
      console.log("提交的数据", this.categroy);
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.categroy, false),
      }).then(({ data }) => {
          this.$message({
              message: "添加成功",
              type: "success",
            });
            //刷新出新的菜单
            this.getMenus();
            //设置需要默认展开的菜单
            this.expandedKey = [this.categroy.parentCid];
            this.dialogVisible = false;
      });

6.1.7 修改分类

  1. 修改CategoryController
/**
     * 信息
     */
    @RequestMapping("/info/{catId}")
    public R info(@PathVariable("catId") Long catId){
		CategoryEntity category = categoryService.getById(catId);

        return R.ok().put("data", category);
    }
  1. 前端代码

实现修改名称,图标,计量单位。

1、新增Edit按钮:复制之前的append

2、查看controller,发现updata方法是由id进行更新的,所以data中的category中新增catId

3、增加、修改的时候也修改图标和计量单位,所以data的category新增inco,productUnit

4、新建edit方法,用来绑定Edit按钮。新建editCategory方法,用来绑定对话框的确定按钮。

5、复用对话框:

  • data数据中新增dialogType,用来标记此时对话框是由edit打开的,还是由append打开的。
  • 新建方法submitData,与对话框的确定按钮进行绑定,在方法中判断,如果dialogTypeadd调用addCategory(),如果dialogTypeedit调用editCategory()
  • data数据中新增title,绑定对话框的title,用来做提示信息。判断dialogType的值,来选择提示信息。

6、防止多个人同时操作,对话框中的回显的信息应该是由数据库中读出来的:点击Edit按钮,发送httpget请求。(看好返回的数据)

7、编辑editCategory方法:

controller之中的更新是动态更新,根据id,发回去什么值修改什么值,所以把要修改的数据发回后端就好。
成功之后发送提示消息,展开刚才的菜单。
8、编辑之后,再点击添加,发现会回显刚才编辑的信息。所以在append方法中重置回显的信息。

<!--编辑按钮-->
		  <el-button type="text" size="mini" @click="() => edit(data)">
            Edit
          </el-button>

<!--可复用的对话框-->
	<el-dialog :title="title" :visible.sync="dialogVisible" width="30%">
      <el-form :model="categroy">
        <el-form-item label="分类名称">
          <el-input v-model="categroy.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="categroy.inco" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="categroy.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>


//data, 新增了title、dialogType。 categroy中新增了inco、productUnit、catId
  data() {
    return {
      title: "",
      dialogType: "",
      categroy: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        inco: "",
        productUnit: "",
        catId: null,
      },
      dialogVisible: false,
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children", //子节点
        label: "name", //name属性作为标签的值,展示出来
      },
    };
      

//方法
     //绑定对话框的确定按钮,根据dialogType判断调用哪个函数
    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
        
        //绑定Edit按钮,设置dialogType、title,从后台读取数据,展示到对话框内
    edit(data) {
      this.dialogType = "edit";
      this.title = "修改菜单";
      this.dialogVisible = true;
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get",
      }).then(({ data }) => {
        console.log(data);
        this.categroy.catId = data.data.catId;
        this.categroy.name = data.data.name;
        this.categroy.inco = data.data.inco;
        this.categroy.productUnit = data.data.productUnit;
      });
    },
        
        //绑定对话框的确定按钮,向后台发送更新请求,传过去想要修改的字段
    editCategory() {
      var { catId, name, inco, productUnit } = this.categroy;
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, inco, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "修改成功",
          type: "success",
        });
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.categroy.parentCid];
        this.dialogVisible = false;
      });
    },
    
    //点击append按钮,清空编辑之后的回显数据
    append(data) {
      this.dialogType = "add";
      this.title = "添加菜单";
      console.log("append", data);
      this.dialogVisible = true;
      this.categroy.parentCid = data.catId;
      this.categroy.catLevel = data.catLevel * 1 + 1;
      this.categroy.name = "",
      this.categroy.inco = "",
      this.categroy.productUnit = ""
    },

6.1.8 拖拽效果

1、前端代码

  1. 拖拽功能的前端实现:lementui树型控件->可拖拽节点
  • 中加入属性draggable表示节点可拖拽。
  • 中加入属性:allow-drop="allowDrop",拖拽时判定目标节点能否被放置。
  • allowDrop有三个参数draggingNode表示拖拽的节点,dropNode表示拖拽到哪个节点,type表示拖拽的类型’prev’、‘inner’ 和 ‘next’,表示拖拽到目标节点之前、里面、之后。
  • allowDrop函数实现判断,拖拽后必须保持数型的三层结构。
  • 节点的深度 = 最深深度 - 当前深度 + 1
  • 当拖拽节点拖拽到目标节点的内部,要满足: 拖拽节点的深度 + 目标节点的深度 <= 3
  • 当拖拽节点拖拽的目标节点的两侧,要满足: 拖拽节点的深度 + 目标节点的父节点的深度 <= 3
<!--el-tree中添加属性-->
	   draggable
      :allow-drop="allowDrop"





// data中新增属性,用来记录当前节点的最大深度
maxLevel: 1,
    
    
//新增方法
    allowDrop(draggingNode, dropNode, type) {
      console.log("allowDrag:", draggingNode, dropNode, type);
      //节点的最大深度
      this.countNodeLevel(draggingNode.data);
      console.log("level:", this.maxLevel);
      //当前节点的深度
      let deep = (this.maxLevel - draggingNode.data.catLevel) + 1;
      console.log(deep)
      if (type == "inner"){
          return (deep + dropNode.level) <= 3;
      }else{
          return (deep + dropNode.parent.level) <= 3;
      }
    },

    //计算当前节点的最大深度
    countNodeLevel(node) {
        //找到所有的子节点,求出最大深度
        if (node.children != null && node.children.length > 0){
            for (let i = 0; i < node.children.length; i++){
                if (node.children[i].catLevel > this.maxLevel){
                    this.maxLevel = node.children[i].catLevel;
                }
                this.countNodeLevel(node.children[i]);
            }
        }
    },

  1. 拖拽功能的数据收集
  • 中加入属性@node-drop="handleDrop",表示拖拽事件结束后触发事件handleDrop,handleDrop共四个参数,draggingNode:被拖拽节点对应的 Node; dropNode:结束拖拽时最后进入的节点; dropType:被拖拽节点的放置位置(before、after、inner);ev:event
  • 拖拽可能影响的节点的数据:parentCid、catLevel、sort
    • data中新增updateNodes,把所有要修改的节点都传进来。
    • 要修改的数据:拖拽节点的parentCid、catLevel、sort
    • 要修改的数据:新的兄弟节点的sort (把新的节点收集起来,然后重新排序)
    • 要修改的数据:子节点的catLeve
//el-tree中新增属性,绑定handleDrop,表示拖拽完触发
@node-drop="handleDrop"

//data 中新增数据,用来记录需要更新的节点(拖拽的节点(parentCid、catLevel、sort),拖拽后的兄弟节点(sort),拖拽节点的子节点(catLevel))
updateNodes: [],
    

//新增方法
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新父节点的id
      let pCid = 0;
      //拖拽后的兄弟节点,分两种情况,一种是拖拽到两侧,一种是拖拽到内部
      let sibings = null;
      if (dropType == "before" || dropType == "after") {
        pCid = dropNode.parent.data.catId == undefined ? 0: dropNode.parent.data.catId;
        sibings = dropNode.parent.childNodes;
      } else {
        pCid = dropNode.data.catId;
        sibings = dropNode.childNodes;
      }

      //2、当前拖拽节点的最新顺序
      //遍历所有的兄弟节点,如果是拖拽节点,传入(catId,sort,parentCid,catLevel),如果是兄弟节点传入(catId,sort)
      for (let i = 0; i < sibings.length; i++) {
          if (sibings[i].data.catId == draggingNode.data.catId){
              //如果遍历的是当前正在拖拽的节点
              let catLevel = draggingNode.level;
              if (sibings[i].level != draggingNode.level){
                  //当前节点的层级发生变化
                  catLevel = sibings[i].level;
                  //修改他子节点的层级
                  this.updateChildNodeLevel(sibings[i]);
              }
              this.updateNodes.push({catId:sibings[i].data.catId, sort: i, parentCid: pCid, catLevel:catLevel});
          }else{
              this.updateNodes.push({catId:sibings[i].data.catId, sort: i});
          }
          
      }
    
    
    
    //每次拖拽后把数据清空,否则要修改的节点将会越拖越多
    this.updateNodes = [],
    this.maxLevel = 1,
    }

// 修改拖拽节点的子节点的层级
updateChildNodeLevel(node){
        if (node.childNodes.length > 0){
            for (let i = 0; i < node.childNodes.length; i++){
                //遍历子节点,传入(catId,catLevel)
                var cNode = node.childNodes[i].data;
                this.updateNodes.push({catId:cNode.catId,catLevel:node.childNodes[i].level});
                //处理子节点的子节点
                this.updateChildNodeLevel(node.childNodes[i]);
            }
        }
    },

  1. 拖拽功能实现
  • 在后端编写批量修改的方法update/sort
  • 前端发送post请求,把要修改的数据发送过来
  • 提示信息,展开拖拽节点的父节点

CategoryController修改方法

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;


    /**
     * 修改分类
     */
    @RequestMapping("/update/sort")
    // @RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity[] category){

        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }

}

前端发送请求:

this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单顺序修改成功",
          type: "success",
        });
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [pCid];
      });

  1. 批量拖拽功能
  • 添加开关,控制拖拽功能是否开启
  • 每次拖拽都要和数据库交互,不合理。批量拖拽过后,一次性保存。
<!--添加拖拽开关和批量保存按钮-->
	<el-switch
      v-model="draggable"
      active-text="开启拖拽"
      inactive-text="关闭拖拽"
    >
    </el-switch>
    <el-button v-if="draggable" size="small" round @click="batchSave"
      >批量保存</el-button
    >

//data中新增数据
 pCid:[], //批量保存过后要展开的菜单id
 draggable: false, //绑定拖拽开关是否打开
  

//修改了一些方法,修复bug,修改过的方法都贴在下面了
     
//点击批量保存按钮,发送请求
    batchSave() {
      this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单顺序修改成功",
          type: "success",
        });
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = this.pCid;
      });

      this.updateNodes = [];
    },
  
        
//
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新父节点的id
      let pCid = 0;
      let sibings = null;
      if (dropType == "before" || dropType == "after") {
        pCid =
          dropNode.parent.data.catId == undefined
            ? 0
            : dropNode.parent.data.catId;
        sibings = dropNode.parent.childNodes;
      } else {
        pCid = dropNode.data.catId;
        sibings = dropNode.childNodes;
      }

      //2、当前拖拽节点的最新顺序
      for (let i = 0; i < sibings.length; i++) {
        if (sibings[i].data.catId == draggingNode.data.catId) {
          //如果遍历的是当前正在拖拽的节点
          let catLevel = draggingNode.level;
          if (sibings[i].level != draggingNode.level) {
            //当前节点的层级发生变化
            catLevel = sibings[i].level;
            //修改他子节点的层级
            this.updateChildNodeLevel(sibings[i]);
          }
          this.updateNodes.push({
            catId: sibings[i].data.catId,
            sort: i,
            parentCid: pCid,
            catLevel: catLevel,
          });
        } else {
          this.updateNodes.push({ catId: sibings[i].data.catId, sort: i });
        }
      }

      this.pCid.push(pCid);
      console.log(this.pCid)
      //3、当前拖拽节点的最新层级
      //console.log("updateNodes", this.updateNodes)
      //拖拽之后重新置1
      this.maxLevel = 1;
    },
        
// 修改拖拽判断逻辑
   allowDrop(draggingNode, dropNode, type) {
      console.log("allowDrag:", draggingNode, dropNode, type);
      this.maxLevel = draggingNode.level;
      //节点的最大深度
      this.countNodeLevel(draggingNode);
      console.log("maxLevel:", this.maxLevel);
      //当前节点的深度
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("level",deep);
      if (type == "inner") {
        return deep + dropNode.level <= 3;
      } else {
        return deep + dropNode.parent.level <= 3;
      }
    },
        
//计算深度时,用当前数据,而不是数据库中的数据。因为可能还没来得及保存到数据库
	countNodeLevel(node) {
      //找到所有的子节点,求出最大深度
      if (node.childNodes != null && node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          if (node.childNodes[i].level > this.maxLevel) {
            this.maxLevel = node.childNodes[i].level;
          }
          this.countNodeLevel(node.childNodes[i]);
        }
      }
    },

6.1.9 批量删除

前端代码

  • 新增删除按钮
<el-button type="danger" size="small" @click="batchDelete" round>批量删除</el-button>

<!--eltree中新增属性,用作组件的唯一标示-->
ref="menuTree"

  • 批量删除方法
batchDelete(){
      let catIds = [];
      let catNames = [];
      let checkedNodes = this.$refs.menuTree.getCheckedNodes();
      for (let i = 0; i < checkedNodes.length; i++){
          catIds.push(checkedNodes[i].catId);
          catNames.push(checkedNodes[i].name);
      }
      this.$confirm(`是否批量删除【${catNames}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(()=>{
        this.$http({
        url:this.$http.adornUrl('/product/category/delete'),
        method:'post',
        data: this.$http.adornData(catIds, false)
        }).then(({data})=>{ 
          this.$message({
          message: "菜单批量删除成功",
          type: "success",
        });
        this.getMenus();
        })
      }).catch(()=>{

      });

    },

6.1.10 前端代码(总)

<template>
  <div>
    <el-switch
      v-model="draggable"
      active-text="开启拖拽"
      inactive-text="关闭拖拽"
    ></el-switch>
    <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <el-tree
      :data="menus"
      :props="defaultProps"
      :expand-on-click-node="false"
      show-checkbox
      node-key="catId"
      :default-expanded-keys="expandedKey"
      :draggable="draggable"
      :allow-drop="allowDrop"
      @node-drop="handleDrop"
      ref="menuTree"
    >
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <!-- 只有一级和二级菜单才有添加按钮,三级菜单是没有添加按钮的 -->
          <el-button
            v-if="node.level <= 2"
            type="text"
            size="mini"
            @click="() => append(data)"
          >
            Append
          </el-button>
          <!-- 编辑 -->
          <el-button type="text" size="mini" @click="edit(data)">
            edit
          </el-button>
          <!-- 没有子节点的时候才有移除按钮 -->
          <el-button
            v-if="node.childNodes.length == 0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
          >
            Delete
          </el-button>
        </span>
      </span>
    </el-tree>
    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》from '《组件路径》';

export default {
  //import 引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      pCid: [],
      draggable: false,
      updateNodes: [],
      maxLevel: 0, //默认最大层级
      menus: [],
      title: "",
      dialogType: "", //edit  add  确定按钮区分是点击的edit还是add。
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        icon: "",
        productUnit: "",
        catId: null,
      },
      dialogVisible: false,
      expandedKey: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
  //计算属性类似于data 概念
  computed: {},
  //监控data 中的数据变化
  watch: {},
  //方法集合
  methods: {
    //获取菜单
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功获取到菜单数据....", data.data);
        this.menus = data.data;
      });
    },
    batchDelete() {
      let catIds = [];
      let checkedNodes = this.$refs.menuTree.getCheckedNodes();
      console.log("被选中的元素", checkedNodes);
      for (let i = 0; i < checkedNodes.length; i++) {
        catIds.push(checkedNodes[i].catId);
      }
      this.$confirm(`是否删除【${catIds}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
      .then(() => {
        this.$http({
        url: this.$http.adornUrl("/product/category/delete"),
        method: 'post',
        data: this.$http.adornData(catIds, false)
        }).then(({ data }) => { 
          this.$message({
          message: "菜单批量删除成功",
          type: "success"
        });
        this.getMenus();
        });
      })
      .catch(() => {} );
    },
    batchSave() {
      this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单等顺序修改成功",
          type: "success",
        });
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = this.pCid;
        (this.updateNodes = []), (this.maxLevel = 0); //默认最大层级
        // this.pCid = 0;
      });
    },
    append(data) {
      console.log("append", data);
      this.dialogVisible = true;
      this.dialogType = "add";
      this.title = "添加分类";
      this.category.parentCid = data.catId;
      this.category.catLevel = data.catLevel * 1 + 1;
      this.category.name = "";
      this.category.catId = null;
      this.category.icon = "";
      this.category.productUnit = "";
      this.category.sort = 0;
      this.category.showStatus = 1;
    },
    edit(data) {
      console.log("要修改的数据", data);
      this.dialogVisible = true;
      this.dialogType = "edit";
      this.title = "修改分类";

      //发送请求获取当前节点最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get",
      }).then(({ data }) => {
        //请求成功
        console.log("要回显的数据", data);
        this.category.name = data.data.name;
        this.category.catId = data.data.catId;
        this.category.icon = data.data.icon;
        this.category.productUnit = data.data.productUnit;
        this.category.parentCid = data.data.parentCid;
      });
    },

    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
    //修改三级分类数据
    editCategory() {
      var { catId, name, icon, productUnit } = this.category; //解构表达式
      // var data = {catId:catId,name:name,icon:icon,productUnit:productUnit}
      // var data = {catId,name,icon,productUnit}
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, icon, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单修改成功",
          type: "success",
        });
        //关闭对话框
        this.dialogVisible = false;
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },

    //添加三级分类
    addCategory() {
      console.log("提交的三级分类数据", this.category);
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单保存成功",
          type: "success",
        });
        //关闭对话框
        this.dialogVisible = false;
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("tree drop: ", dropNode.label, dropType);

      //1.当前节点最新的父节点id
      let pCid = 0;
      let siblings = null;
      if (dropType == "before" || dropType == "after") {
        pCid =
          dropNode.parent.data.catId == undefined
            ? 0
            : dropNode.parent.data.catId;
        siblings = dropNode.parent.childNodes;
      } else {
        pCid = dropNode.data.catId;
        siblings = dropNode.childNodes;
      }
      this.pCid.push(pCid);
      //2.当前拖拽节点的最新顺序,
      for (let i = 0; i < siblings.length; i++) {
        if (siblings[i].data.catId == draggingNode.data.catId) {
          //如果遍历的是当前正在拖拽的节点
          let catLevel = draggingNode.level;
          if (siblings[i].level != draggingNode.level) {
            //当前节点的层级变化
            catLevel = siblings[i].level;
            //修改它子节点的层级
            this.updateChildNodeLevel(siblings[i]);
          }
          this.updateNodes.push({
            catId: siblings[i].data.catId,
            sort: i,
            parentCid: pCid,
            catLevel: catLevel,
          });
        } else {
          this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
        }
      }
      //3.当前拖拽节点的最新层级
      console.log("updateNodes", this.updateNodes);
    },
    updateChildNodeLevel(node) {
      if (node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          var cNode = node.childNodes[i].data;
          this.updateNodes.push({
            catId: cNode,
            catLevel: node.childNodes[i].level,
          });
          this.updateChildNodeLevel(node.childNodes[i]);
        }
      }
    },
    //可拖拽节点
    allowDrop(draggingNode, dropNode, type) {
      //1、被拖动的当前节点以及所在的父节点总层数不能大于3

      //1)、被拖动的当前节点总层数
      console.log("allowDrop:", draggingNode, dropNode, type);
      //
      this.countNodeLevel(draggingNode);
      //当前正在拖动的节点+父节点所在的深度不大于3即可
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("深度:", deep);
      //   this.maxLevel
      if (type == "inner") {
        return deep + dropNode.level <= 3;
      } else {
        return deep + dropNode.parent.level <= 3;
      }
    },
    countNodeLevel(node) {
      //找到所有子节点,求出最大深度
      if (node.childNodes != null && node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          if (node.childNodes[i].level > this.maxLevel) {
            this.maxLevel = node.childNodes[i].level;
          }
          this.countNodeLevel(node.childNodes[i]);
        }
      }
    },

    remove(node, data) {
      var ids = [data.catId];
      this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false),
          }).then(({ data }) => {
            this.$message({
              message: "菜单删除成功",
              type: "success",
            });
            //刷新出新的菜单
            this.getMenus();
            //设置需要默认展开的菜单
            this.expandedKey = [node.parent.data.catId];
          });
        })
        .catch(() => {});
    },
  },
  //生命周期- 创建完成(可以访问当前this 实例)
  created() {
    this.getMenus();
  },
  //生命周期- 挂载完成(可以访问DOM 元素)
  mounted() {},
  beforeCreate() {}, //生命周期- 创建之前
  beforeMount() {}, //生命周期- 挂载之前
  beforeUpdate() {}, //生命周期- 更新之前
  updated() {}, //生命周期- 更新之后
  beforeDestroy() {}, //生命周期- 销毁之前
  destroyed() {}, //生命周期- 销毁完成
  activated() {}, //如果页面有keep-alive 缓存功能,这个函数会触发
};
</script>
<style lang='scss' scoped>
//@import url(); 引入公共css 类
</style>

至此商品服务告一段落。


6.2 品牌管理

这次要用到的代码是通过renren-generator代码生成器中生成的前端代码。在前面中如果我们不小心进行删除了,可以通过idea自带的恢复功能进行恢复。

步骤:

  1. 右键点击resources->Local History->Show History

image-20221031155936100

  1. 找到删除前端的记录
  2. 右键->Revert。 找回成功!

image-20221031160027348

6.2.1 使用逆向工程前端代码

  1. 菜单管理---新增菜单

image-20221030162542202

  1. 将gulimall-product中的前端代码复制到前端工程product下。

image-20221030170804793

  1. 没有新增删除按钮: 修改权限,Ctrl+Shift+F查找isAuth,全部返回为true

image-20221031160612205

image-20221031160622591

  1. 查看效果

image-20221031160656833

这里提一嘴,我们可以将es6语法检查关闭。

image-20221030183344496

6.2.2 效果优化-快速显示开关

  1. 在列表中添加自定义列:中间加标签。可以通过 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据

  2. 修改开关状态,发送修改请求

  3. 数据库中showStatus是01,开关默认值是true/false。 所以在开关中设置:active-value="1" 、:inactive-value="0"属性,与数据库同步

image-20221030183722150

image-20221030183940992

<!--brand.vue中显示状态那一列-->
      <el-table-column
        prop="showStatus"
        header-align="center"
        align="center"
        label="显示状态"
      >
        <template slot-scope="scope">
          <el-switch
            v-model="scope.row.showStatus"
            active-color="#13ce66"
            inactive-color="#ff4949"
            :active-value="1" 
            :inactive-value="0"
            @change="updateBrandStatus(scope.row)" 
          >
          </el-switch>
        </template>
      </el-table-column>

<!--brand-add-or-update.vue中显示状态那一列-->
      <el-form-item label="显示状态" prop="showStatus">
        <el-switch
          v-model="dataForm.showStatus"
          active-color="#13ce66"
          inactive-color="#ff4949"
          :active-value="1"
          :inactive-value="0"
        >
        </el-switch>
      </el-form-item>

//brand.vue中新增方法,用来修改状态
updateBrandStatus(data) {
      let { brandId, showStatus } = data;
      this.$http({
        url: this.$http.adornUrl("/product/brand/update"),
        method: "post",
        data: this.$http.adornData({ brandId, showStatus }, false),
      }).then(({ data }) => {
        this.$message({
          message: "状态修改成功",
          type: "success",
        });
      });
    },

6.2.3 文件上传功能

  1. 知识补充

image-20221031162619746

image-20221031162633731

image-20221030192307480

这里我们选用服务端签名后直传进行文件上传功能。

  1. 开通阿里云OSS对象存储服务,并创建新的Bucket

image-20221030215541499

https://help.aliyun.com/document_detail/32007.html sdk--java版本

image-20221030220330997

  1. 如何使用

根据官网的文档,我们可以直接在项目中引入依赖进行安装

这个依赖是最原始的。配置什么要写一大堆。

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.0</version>
</dependency>

更推荐我们使用的是封装好的,这样配置可以直接在application.yml中。

<!--        阿里云oss存储服务-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
        </dependency>

image-20221030223858104

在gulimall-common中引入上述依赖。

  1. 获取EndpointAccessKey IDAccessKey Secret
  • Endpointimage-20221030221432086

  • 创建子账户,获取AccessKey IDAccessKey Secret

image-20221030221541237

image-20221030221604212

image-20221030221732035

image-20221030221837906

  • 对子账户分配权限,管理OSS对象存储服务image-20221030222028868

image-20221030223237657

注意:

视频中将阿里巴巴oss存储服务依赖加到gulimall-common中,但是这个时候如果启动product是会报错的,原因是其他微服务都依赖了gulimall-common服务,如果其他微服务没有进行相关配置,会报依赖循环的错误,导致启动失败。但是后面我们创建一个专属于第三方服务的微服务,所以如果你要在这里跟着老师的步骤,进行测试的话,最好的建议就是将阿里云服务的oss进行单独引入到product服务,并将common中的注释掉。

6.2.4 建议第三方服务微服务工程并完成文件上传功能

我们将文件上传或者以后的短信验证这些第三方服务抽取出来放到一个专门的第三方微服务的工程项目中。gulimall-third-party

image-20221031184330135

  • 照例引入springweb和openfeign

image-20221031184425929

  • 在nacos中创建专属于第三方服务的命名空间third-party

image-20221031185333422

  • third-party中引入gulimall-common,注意在其中排除mybatisplus依赖。如果不排除,启动会报错。

image-20221031190801304

  • 引入阿里云oss存储服务
 <!--        阿里云oss存储服务-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
  • 微服务端口设置为:30000
  • 启动测试

启动thrid-party报错:Oss endpoint can‘t be empty.

image-20221031192504012

加入以下这个依赖之后就可以启动成功。

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

image-20221031193540593

1、单元测试

在test文件夹下的主测试类进行测试。

@SpringBootTest
class GulimallThirdPartyApplicationTest {

    @Autowired
    OSSClient ossClient;

    @Test
    public void testUpload() throws FileNotFoundException {

        //上传文件流。
        InputStream inputStream = new FileInputStream("E:\\SystemDefault\\桌面\\1.jpg");
        ossClient.putObject("gulimall-xmh", "hahaha1.jpg", inputStream);

        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功.");
    }

}

image-20221031193556011

测试成功。

2、服务端签名直传并设置上传回调

image-20221101151944994

阿里云OSS存储服务中对于服务器签名直传这部分的文档。链接在下面:

https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.11186623.0.0.1607566a7iSEvF#concept-ahk-rfz-2fb

我们参考这个文档创建属于我们自己的配置。

  1. 创建OssController
package com.atguigu.gulimall.thirdparty.controller;


import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;


@RestController
public class OssController {
    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")   //从配置文件动态读取,不写死
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @RequestMapping("/oss/policy")
   
   public Map<String, String> policy () {
            String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint

            //自定义日期格式文件夹
            String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            String dir = format; // 用户上传文件时指定的前缀。

            Map<String, String> respMap = null;
            try {
                long expireTime = 30;
                long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
                Date expiration = new Date(expireEndTime);
                PolicyConditions policyConds = new PolicyConditions();
                policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
                policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

                String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
                byte[] binaryData = postPolicy.getBytes("utf-8");
                String encodedPolicy = BinaryUtil.toBase64String(binaryData);
                String postSignature = ossClient.calculatePostSignature(postPolicy);

                respMap = new LinkedHashMap<String, String>();
                respMap.put("accessid", accessId);
                respMap.put("policy", encodedPolicy);
                respMap.put("signature", postSignature);
                respMap.put("dir", dir);
                respMap.put("host", host);
                respMap.put("expire", String.valueOf(expireEndTime / 1000));

            } catch (Exception e) {
                // Assert.fail(e.getMessage());
                System.out.println(e.getMessage());
            } finally {
                ossClient.shutdown();
            }
          return respMap;
      
        }

    }

image-20221031215306825

  1. 我们直接进行访问,发现可以读取出数据,但是我们整个微服务都是要经过网关的,所以我们接下来进行网关配置,进行路径重写。

在网关中进行配置

        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

重启微服务,进行访问。如下图,能获取到签名信息。

image-20221031220631916

  1. 接下来我们进行前端联调,实现微服务的文件上传功能。
  • 文件上传组件在/renren-fast-vue/src/components中
  • 修改组件中el-upload中的action属性,替换成自己的Bucket域名

image-20221101153404818

singleUpload.vue是单文件上传,multiUploca.vue是多文件上传。

  • 把单个文件上传组件应用到brand-add-or-update.vue
//在<script>标签中导入组件
import singleUpload from "@/components/upload/singleUpload"

//在export default中声明要用到的组件
  components:{
    singleUpload
  },
 <!--用新的组件替换原来的输入框-->
	  <el-form-item label="品牌logo地址" prop="logo">
        <singleUpload v-model="dataForm.logo"></singleUpload>
      </el-form-item>
  1. 我们在进行启动的时候发现存在浏览器跨域问题

image-20221101153758239

  1. 解决跨域问题,在bucket中设置跨域设置

image-20221031222825001

  1. 配置后点击图片上传,进行测试。

点击上传,拿到police.阿里云服务器验证,上传。

image-20221031223445262

在课件资源中将老师编写好的单文件上传和多文件上传一起复制到renren-fast-vue下的src\upload文件夹下。

image-20221101163153540

image-20221101163306333

image-20221101153922438

测试完成。

6.2.5 效果优化-显示图片

新增品牌,发现在品牌logo下面显示的是地址。应该显示图片。

image-20221101162245831

  • 在在品牌logo下添加图片标签
 <el-form-item label="品牌logo地址" prop="logo">
        <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
        <single-upload v-model="dataForm.logo"></single-upload>
      </el-form-item>



<el-table-column
        prop="logo"
        header-align="center"
        align="center"
        label="品牌logo"
      >
        <template slot-scope="scope">
          <img :src="scope.row.logo" style="width: 100px; height: 80px" />
        </template>
      </el-table-column>

image-20221101163647492

image-20221101163715428

记得修改OSScontroller中的返回值,因为前端的数据要求是在data.

image-20221101163910832

image-20221101163926666

6.2.6 前端表单校验

  • 首字母只能为a-z或者A-Z的一个字母
  • 排序必须是大于等于0的一个整数

el-form中rules表示校验规则

//排序加上.number表示要接受一个数字        
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>

	    //首字母校验规则
		firstLetter: [
          {
            validator: (rule, value, callback) => {
              if (value == '') {
                callback(new Error("首字母必须填写"));
              } else if (!/^[a-zA-Z]$/.test(value)) {
                callback(new Error("首字母必须在a-z或者A-Z之间"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
        //排序字段校验规则
        sort: [
          {
            validator: (rule, value, callback) => {
              if (value == '') {
                callback(new Error("排序字段必须填写"));
              } else if (!Number.isInteger(value) || value < 0) {
                callback(new Error("排序字段必须是一个大于等于0的整数"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],

6.2.7 JSR303数字校验

JSR303
        1)、给Bean添加校验注解:javax.validation.Constraints,并定义自己的message提示
        2)、在需要校验的bean上添加注解   开启校验功能  @Valid
            效果:校验错误以后会有默认的响应;
        3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
        4)、分组校验(多场景的复杂校验)
            1)、@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
                给校验注解标注什么情况需要进行校验
            2)、@Validated({AddGroup.class})
            3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况下不生效,只会在@Validated生效

1、基本校验实现

  1. 引入validation-api依赖,在gulimall-common中。
 <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
  1. 在需要校验的bean上添加注解 开启校验功能
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotEmpty
	@URL(message = "logo必须是一个合法的url地址")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty
	@Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0, message = "排序必须大于等于0")
	private Integer sort;

}

  1. 在需要校验的方法上添加@Valid注解,并返回提示信息
  2. 给校验的bean后紧跟着一个BindingResult,就可以获取到校验的结果
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if (result.hasErrors()){
            Map<String, String> map = new HashMap<>();
            //1、获取校验的结果
            result.getFieldErrors().forEach((item)->{
                //获取到错误提示
                String message = item.getDefaultMessage();
                //获取到错误属性的名字
                String field = item.getField();
                map.put(field, message);
            });
            return R.error().put("data", map);
        }else{
            brandService.save(brand);
        }
        return R.ok();
    }

  1. 测试

image-20221101190758229

2、统一异常处理

  1. 针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义

image-20221102171347166

为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码。

  1. 在gulimall-common中新建BizCodeEnume用来存储状态码
/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
public enum BizCodeEnum {

    UNKNOW_EXEPTION(10000,"系统未知异常"),

    VALID_EXCEPTION( 10001,"参数格式校验失败");

    private int code;
    private String msg;
    BizCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

  1. 在product里面新建类GulimallExceptionControllerAdvice,用来集中处理所有异常

    /**
     * 集中处理所有异常
     */
    //@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
    //@ResponseBody
    @Slf4j
    @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
    public class GulimallExceptionControllerAdvice {
    
        @ExceptionHandler(value= MethodArgumentNotValidException.class)
            public R handleValidException(MethodArgumentNotValidException e){
                log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
            BindingResult bindingResult = e.getBindingResult();
    
            Map<String,String> errorMap = new HashMap<>();
                bindingResult.getFieldErrors().forEach((fieldError -> {
                    errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
                }));
            return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
        }
    
        @ExceptionHandler(value=Throwable.class)
        public R handleException(Throwable throwable){
            log.error("错误:",throwable);
            return R.error(BizCodeEnume.UNKNOWN_EXCEPTION.getCode(), BizCodeEnume.UNKNOWN_EXCEPTION.getMsg());
        }
    
    }
    
    
  2. 测试结果

image-20221101191405324

3、分组校验功能

  1. 在gulimall-common中新建valid包,里面新建两个空接口AddGroup,UpdateGroup用来分组

  2. 给校验注解,标注上groups,指定什么情况下才需要进行校验

    如:指定在更新和添加的时候,都需要进行校验

    	@NotEmpty
    	@NotBlank(message = "品牌名必须非空",groups = {UpdateGroup.class,AddGroup.class})
    	private String name;
    
    

在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。

  1. 业务方法参数上使用@Validated注解,并在value中给出group接口,标记当前校验是哪个组
	 @RequestMapping("/save")
    public R save(@Valided({AddGroup.class}) @RequestBody BrandEntity brand){
        ...
    }

  • 默认情况下,在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效

4、自定义校验

gulimall-common中

  1. 编写一个自定义校验注解ListValue
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    String message() default "{com.xmh.common.valid.ListValue.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int[] vals() default {};
}

  1. 新建配置文件ValidationMessages.properties保存注解信息

image-20221102172434053

  1. 编写一个自定义校验器ListValueConstraintValidator
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {


    private Set<Integer> set=new HashSet<>();

    @Override
    public void initialize(ListValue constraintAnnotation) {
       int[] value = constraintAnnotation.vals();
        for (int i :value) {
            set.add(i);
        }
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
  1. 关联自定义的校验器和自定义的校验注解(可以指定多个不同的校验器,适配不同类型的校验)
  • 关联校验器和校验注解:在校验注解的@Constraint注解上关联校验器

    image-20221102172701984

  • 校验注解添加到showStatus上,进行测试

@ListValue(vals = {0, 1},groups = {AddGroup.class, UpdateGroup.class})
	private Integer showStatus;
  1. 测试

image-20221101223417783

6.3 属性服务

6.3.1 spu和sku

  1. SPU:Standard Product Unit(标准化产品单元)

​ 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。

image-20221105100223239

image-20221105100228267

iphoneX 是SPU、MI 8 是SPU
iphoneX 64G 黑曜石是SKU
MI8 8+64G+黑色是SKU

  1. SKU:Stock Keeping Unit(库存量单位)
    即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU 这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU 号。

个人理解:

  • 商品介绍、规格包装这些都是spu,每一个spu下的所有的sku都具备同样的属性。

  • 颜色和内存都是销售属性,因为这个是决定每一个sku的不同的价格的。决定sku的属性我们叫做销售属性。每一个sku的商品介绍的图片和文字这些都是一样的。

  • 基本属性就是指的主体中的型号或者是长度等。这些都是基本属性。

6.3.2 基本属性【规格参数】与销售属性

image-20221105100456651

image-20221109190207850

image-20221109190227500

image-20221109190242373

6.3.3 属性分组准备工作

如下图,这个是属性分组的效果展示。

image-20221116144745023

1、前端组件

从老师给的课件资源中找到sys_menus.sql,将其复制后,在数据库gulimall_admin中进行找到sys_menus这张表进行完善。即可出现。

image-20221105114534191

注意这里有一个坑:就是看清楚sql文件中的语句是否和你数据库中对应的表的名称一样,否则不管运行多少次sql都不会生效。

image-20221104163242323

  1. 在moudules下新建common/categroy.vue,这是一个公共组件,后面我们要引用他,即树形结构。这里我们可以直接将以前写过的category.vue复制,然后进行简单的删除即可。
<!--  -->
<template>
  <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree">
  </el-tree>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  data() {
    //这里存放数据
    return {
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children", //子节点
        label: "name", //name属性作为标签的值,展示出来
      },
    };
  },
  //监听属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //方法集合
  methods: {
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功了获取到菜单数据....", data.data);
        this.menus = data.data;
      });
    },
  },
  //生命周期 - 创建完成(可以访问当前this实例)
  created() {
      this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

  1. 将逆向生成的前端代码复制到product下面。

image-20221105143250879

  1. 在modules/product/下创建attgroup.vue组件
    • 左侧6 用来显示菜单,右侧18用来显示表格
    • image-20221104212222336
    • 引入公共组件Category, AddOrUpdate
    • 剩下的复制生成的attrgroup.vue
<!--  -->
<template>
  <el-row :gutter="20">
    <el-col :span="6"><category></category></el-col>
    <el-col :span="18"
      ><div class="mod-config">
        <el-form
          :inline="true"
          :model="dataForm"
          @keyup.enter.native="getDataList()"
        >
          <el-form-item>
            <el-input
              v-model="dataForm.key"
              placeholder="参数名"
              clearable
            ></el-input>
          </el-form-item>
          <el-form-item>
            <el-button @click="getDataList()">查询</el-button>
            <el-button
              v-if="isAuth('product:attrgroup:save')"
              type="primary"
              @click="addOrUpdateHandle()"
              >新增</el-button
            >
            <el-button
              v-if="isAuth('product:attrgroup:delete')"
              type="danger"
              @click="deleteHandle()"
              :disabled="dataListSelections.length <= 0"
              >批量删除</el-button
            >
          </el-form-item>
        </el-form>
        <el-table
          :data="dataList"
          border
          v-loading="dataListLoading"
          @selection-change="selectionChangeHandle"
          style="width: 100%"
        >
          <el-table-column
            type="selection"
            header-align="center"
            align="center"
            width="50"
          >
          </el-table-column>
          <el-table-column
            prop="attrGroupId"
            header-align="center"
            align="center"
            label="分组id"
          >
          </el-table-column>
          <el-table-column
            prop="attrGroupName"
            header-align="center"
            align="center"
            label="组名"
          >
          </el-table-column>
          <el-table-column
            prop="sort"
            header-align="center"
            align="center"
            label="排序"
          >
          </el-table-column>
          <el-table-column
            prop="descript"
            header-align="center"
            align="center"
            label="描述"
          >
          </el-table-column>
          <el-table-column
            prop="icon"
            header-align="center"
            align="center"
            label="组图标"
          >
          </el-table-column>
          <el-table-column
            prop="catelogId"
            header-align="center"
            align="center"
            label="所属分类id"
          >
          </el-table-column>
          <el-table-column
            fixed="right"
            header-align="center"
            align="center"
            width="150"
            label="操作"
          >
            <template slot-scope="scope">
              <el-button
                type="text"
                size="small"
                @click="addOrUpdateHandle(scope.row.attrGroupId)"
                >修改</el-button
              >
              <el-button
                type="text"
                size="small"
                @click="deleteHandle(scope.row.attrGroupId)"
                >删除</el-button
              >
            </template>
          </el-table-column>
        </el-table>
        <el-pagination
          @size-change="sizeChangeHandle"
          @current-change="currentChangeHandle"
          :current-page="pageIndex"
          :page-sizes="[10, 20, 50, 100]"
          :page-size="pageSize"
          :total="totalPage"
          layout="total, sizes, prev, pager, next, jumper"
        >
        </el-pagination>
        <!-- 弹窗, 新增 / 修改 -->
        <add-or-update
          v-if="addOrUpdateVisible"
          ref="addOrUpdate"
          @refreshDataList="getDataList"
        ></add-or-update></div
    ></el-col>
  </el-row>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import Category from "../common/category.vue";
import AddOrUpdate from "./attrgroup-add-or-update.vue";

export default {
  //import引入的组件需要注入到对象中才能使用
  components: { Category, AddOrUpdate},
  data() {
    return {
      dataForm: {
        key: "",
      },
      dataList: [],
      pageIndex: 1,
      pageSize: 10,
      totalPage: 0,
      dataListLoading: false,
      dataListSelections: [],
      addOrUpdateVisible: false,
    };
  },
  activated() {
    this.getDataList();
  },
  methods: {
    // 获取数据列表
    getDataList() {
      this.dataListLoading = true;
      this.$http({
        url: this.$http.adornUrl("/product/attrgroup/list"),
        method: "get",
        params: this.$http.adornParams({
          page: this.pageIndex,
          limit: this.pageSize,
          key: this.dataForm.key,
        }),
      }).then(({ data }) => {
        if (data && data.code === 0) {
          this.dataList = data.page.list;
          this.totalPage = data.page.totalCount;
        } else {
          this.dataList = [];
          this.totalPage = 0;
        }
        this.dataListLoading = false;
      });
    },
    // 每页数
    sizeChangeHandle(val) {
      this.pageSize = val;
      this.pageIndex = 1;
      this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
      this.pageIndex = val;
      this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
      this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
      this.addOrUpdateVisible = true;
      this.$nextTick(() => {
        this.$refs.addOrUpdate.init(id);
      });
    },
    // 删除
    deleteHandle(id) {
      var ids = id
        ? [id]
        : this.dataListSelections.map((item) => {
            return item.attrGroupId;
          });
      this.$confirm(
        `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      ).then(() => {
        this.$http({
          url: this.$http.adornUrl("/product/attrgroup/delete"),
          method: "post",
          data: this.$http.adornData(ids, false),
        }).then(({ data }) => {
          if (data && data.code === 0) {
            this.$message({
              message: "操作成功",
              type: "success",
              duration: 1500,
              onClose: () => {
                this.getDataList();
              },
            });
          } else {
            this.$message.error(data.msg);
          }
        });
      });
    },
  },
};
</script>
<style scoped>
</style>

踩坑:

Can't resolve './attrgroup-add-or-update' in 'C:\Users\hxld\Desktop\renren-fast-vue\src\views\modules\product'

解决办法:

原来是绝对路径,后面改为相对路径即可。错误原因是因为版本问题可能。

2、父子组件传递数据

我们要实现的功能是点击左侧,右侧表格对应显示。

父子组件传递数据:category.vue点击时,引用它的attgroup.vue能感知到, 然后通知到add-or-update。

image-20221104220708918

  1. 子组件发送事件
  • 在category.vue中的树形控件绑定点击事件@node-click="nodeclick"

  • node-click方法中有三个参数(data, node, component),data表示当前数据,node为elementui封装的数据

  • 点击之后向父组件发送事件:this.$emit("tree-node-click",...) …为参数

//组件绑定事件
<el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick">

    
//methods中新增方法
	nodeclick(data, node, component){
      console.log("子组件categroy的节点被点击:", data,node,component);
      this.$emit("tree-node-click", data,node,component); //向父组件发送tree-node-click事件
    }

  1. 父组件接收事件
//引用的组件,可能会发散tree-node-click事件,当接收到时,触发父组件的treenodeclick方法
<category @tree-node-click="treenodeclick"></category>


//methods中新增treenodeclick方法,验证父组件是否接收到
      treenodeclick(data, node, component){
          console.log("attrgroup感知到category的节点被点击:",data, node, component);
          console.log("刚才被点击的菜单Id", data.catId);
      },

3、启动测试

6.3.4 获取分类属性分组

接口在线文档地址:https://easydoc.net/s/78237135/ZUqEdvA4/OXTgKobR

image-20221105132403292

接口地址:/product/attrgroup/list/{catelogId}

@RequestParam:获取请求参数的值,和方法形参绑定,并自动赋值

@PathVariable:获取路径参数中的值,和方法形参绑定,并自动赋值

1、修改product下的controller

    @RequestMapping("/list/{catelogId}")
    public R list(@RequestParam Map<String, Object> params, @PathVariable Long catelogId){
        //PageUtils page = attrGroupService.queryPage(params);
        PageUtils page = attrGroupService.queryPage(params, catelogId);
        return R.ok().put("page", page);
    }

2、service新增接口,实现类新增方法

@Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
        if (catelogId == 0){ //如果传过来的id是0,则查询所有属性
            //this.page两个参数,第一个参数是查询页码信息,其中Query.getPage方法传入一个map,会自动封装成IPage
            //第二个参数是查询条件,空的wapper就是查询全部
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    new QueryWrapper<AttrGroupEntity>());
            return new PageUtils(page);
        }else{
            String key = (String) params.get("key");
            QueryWrapper<AttrGroupEntity> wapper = new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId);
            if (!StringUtils.isEmpty(key)){
                wapper.and((obj)->{
                    obj.like("attr_group_name", key).or().eq("attr_group_id", key);
                });
            }
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    wapper);
            return new PageUtils(page);
        }

    }

3、细节完善

我们不让一级和二级分类下,点击的时候也出现表格,只让三级分类的时候才出现相应的表格,所以我们可以设置一个判断。

修改前端代码

  • 修改getDataList()中的请求路径

image-20221105133328275

  • data中新增catId,设置默认为0
  • methods中修改点击方法
    treenodeclick(data, node, component) {
      //必须是三级分类,才显示属性
      if (data.catLevel == 3){
          this.catId = data.catId;
          this.getDataList();
      }
    },

4、数据库中新增数据,进行测试。

6.3.5 属性分组新增功能

新增时,父id改换为选择框

1、新增选择框,添加菜单数据

image-20221105142813514

  • 我们发现可以选择分类,但是分类显示的是空白,这个是因为 显示的属性是label,通过props属性进行绑定

          <!--v-model 绑定要提交的数据,options绑定选择菜单, props绑定选择框的参数-->
          <el-form-item label="所属分类id" prop="catelogId">
            <el-cascader v-model="dataForm.catelogIds" :options="categroys" :props="props"></el-cascader>
          </el-form-item>
    
    
    //data中新增属性,props用来绑定选择框的参数,categorys用来保存菜单
    	  props:{
            value:"catId",
            label:"name",
            children:"children"
          },
          categroys:[],
              
    //方法中新增getCategorys(),用来获取选择菜单的值
    getCategorys() {
          this.$http({
            url: this.$http.adornUrl("/product/category/list/tree"),
            method: "get",
          }).then(({ data }) => {
            console.log("成功了获取到菜单数据....", data.data);
            this.categroys = data.data;
          });
        },
            
            
    //组件创建的时候就要获取菜单的值
      created(){
        this.getCategorys();
      }        
    
    

2、发现返回的数据,三级菜单下面也有children(为空)

  • 解决方法:在CategoryEntity中设置相应的属性不为空既可。当children为空时,不返回children字段

​ 在children字段上添加注解:当值为空时,不返回当前字段

image-20221105145603189

image-20221105145847866

3、修改之后仍然报错

原因是:el-cascader绑定的dataForm.catelogId是一个数组,其中包含选择框的父节点id和自己的id。而我们要提交的只是他自己的id。

//修改data中的dataFrom
      dataForm: {
        attrGroupId: 0,
        attrGroupName: "",
        sort: "",
        descript: "",
        icon: "",
        catelogIds: [], //保存父节点和子节点的id
        catelogId: 0 //保存要提交的子节点的id
      },
          
//修改表单提交方法,要传送的数据,只传最后一个子id
catelogId: this.dataForm.catelogIds[this.dataForm.catelogIds.length - 1],

6.3.6 修改回显分类功能

image-20221107093235212

我们要设置选择进行修改的时候将原来本属于这个原信息回显出来。

image-20221116154504512

1、前端 attrgroup-add-or-update.vue新增完整路径

init(id) {
      this.dataForm.attrGroupId = id || 0;
      this.visible = true;
      this.$nextTick(() => {
        this.$refs["dataForm"].resetFields();
        if (this.dataForm.attrGroupId) {
          this.$http({
            url: this.$http.adornUrl(
              `/product/attrgroup/info/${this.dataForm.attrGroupId}`
            ),
            method: "get",
            params: this.$http.adornParams(),
          }).then(({ data }) => {
            if (data && data.code === 0) {
              this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
              this.dataForm.sort = data.attrGroup.sort;
              this.dataForm.descript = data.attrGroup.descript;
              this.dataForm.icon = data.attrGroup.icon;
              this.dataForm.catelogId = data.attrGroup.catelogId;
              //查出catelogId的完整路径
              this.dataForm.catelogPath = data.attrGroup.catelogPath;
            }
          });
        }
      });
    },

2、后端AttrGroupEntity新增完整路径属性

@TableField(exist = false)
	private Long[] catelogPath;

3、修改AttrGroupController

@Autowired
    private CategoryService categoryService; 


/**
     * 信息
     */
    @RequestMapping("/info/{attrGroupId}")
    public R info(@PathVariable("attrGroupId") Long attrGroupId){
		AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);

        Long catelogId = attrGroup.getCatelogId();
        //根据id查询完整路径
        Long[] path =   categoryService.findCatelogPath(catelogId);
	//	查询到的传入回去
        attrGroup.setCatelogPath(path);
	//返回
        return R.ok().put("attrGroup", attrGroup);
    }

4、修改categoryService以及categoryServiceImpl的方法,新增接口,实现方法

//categoryService接口

    /*
     *找到catelogId的完整路径
     */
    Long[] findCatelogPath(Long catelogId);



//categoryServiceImpl实现方法
	//查找完整路径方法
	@Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        // list.toArray  list对象转换为数组对象 
        return parentPath.toArray(new Long[parentPath.size()]);
    }

	//递归查找父节点id
    public List<Long> findParentPath(Long catelogId,List<Long> paths){
        //1、收集当前节点id
        CategoryEntity byId = this.getById(catelogId);
        if (byId.getParentCid() != 0){
            findParentPath(byId.getParentCid(), paths);
        }
        paths.add(catelogId);
        return paths;
    }

5、 attrgroup-add-or-update.vue 当对话框关闭时,清空数据,防止不合理回显

  <el-dialog
    :title="!dataForm.attrGroupId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible"
    @closed="dialogClose" //关闭时,绑定方法dialogClose
  >
        
  //新增方法
    dialogClose(){
      this.dataForm.catelogPath = [];
    },

6、选择框加上搜索功能:filterable, 显示提示信息placeholder="试试搜索:手机"

        <el-cascader
          placeholder="试试搜索:手机"
          filterable
          v-model="dataForm.catelogPath"
          :options="categroys"
          :props="props"
        ></el-cascader>

6.3.7 实现分页-引入插件

发现自动生成的分页条不好使,原因是没有引入mybatis-plus的分页插件。新建配置类,引入如下配置

image-20221107100012879

image-20221107100050301

1、新建mybatisplus配置文件,引入并配置分页插件

@Configuration
public class MybatisPlusConfig {

    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }

}

上面这个是最新版本的分页插件的配置信息。

下面这个是老师课件上的配置信息

@Configuration
@EnableTransactionManagement   //开启事务
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {
    //引入分页插件   显示页码
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false继续请求,默认false
        paginationInterceptor.setOverflow(true);
        //设置最大单页限制数量,默认500条,-1不受限制
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}

6.3.8 关联分类

1、前端代码拷贝

这一部分前端代码我们直接将老师发的课件的全部复制到我们自己的renren-fast-vue文件夹下.

image-20221107211750143

image-20221107211904066

  1. 一个手机品牌下可能包含手机、电器等分类,同理手机分类下可能包含小米、华为等多个品牌,即品牌与分类之间是多对多的关系,表pms_category_brand_relation保存品牌与分类的多对多关系。

image-20221107212434984

2、获取品牌关联的分类

根据传过来的brandId,查找所有的分类信息

image-20221107212747968

CategoryBrandRelationController


/**
     * 获取品牌关联的分类
     */
    @GetMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId") Long brandId){
        List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
                new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId)
        );

        return R.ok().put("data",data);
    }

3、新增品牌与分类关联关系:

image-20221107213042355

保存的时候,前端传过来brandid和categoryid,存储的时候还要存brandName和categoryName,所以在保存之前进行查找.

  • CategoryBrandRelationController
 /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
		categoryBrandRelationService.saveDetail(categoryBrandRelation);

        return R.ok();
    }
  • CategoryBrandRelationService
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
  • CategoryBrandRelationServiceImpl
 @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
        Long brandId = categoryBrandRelation.getBrandId();
        Long catelogId = categoryBrandRelation.getCatelogId();
        //查询详细名字和分类名字
        BrandEntity brandEntity = brandDao.selectById(brandId);
        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        categoryBrandRelation.setBrandName(brandEntity.getName());
        categoryBrandRelation.setCatelogName(categoryEntity.getName());
        this.save(categoryBrandRelation);

    }

4、要对品牌(分类)名字进行修改时,品牌分类关系表之中的名字也要进行修改

  • 品牌名字修改同时修改关系表数据

BrandController

    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@Validated(value = {UpdateGroup.class})@RequestBody BrandEntity brand){
		brandService.updateByIdDetail(brand);

        return R.ok();
    }

brandServiceImpl

	@Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Transactional
    @Override
    public void updateByIdDetail(BrandEntity brand) {
        //保证冗余字段的数据一致
        this.updateById(brand);
        if (!StringUtils.isEmpty(brand.getName())){
            //如果修改了名字,则品牌分类关系表之中的名字也要修改
            categoryBrandRelationService.updateBrand(brand.getBrandId(), brand.getName());
            //TODO 更新其他关联
        }
    }

CategoryBrandRelationServiceImpl

    @Override
    public void updateBrand(Long brandId, String name) {
        CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
        categoryBrandRelationEntity.setBrandName(name);
        categoryBrandRelationEntity.setBrandId(brandId);
        this.update(categoryBrandRelationEntity, new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId));
    }

  • 分类名字修改同时修改关系表数据

CategoryController

    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity category){
		categoryService.updateCascade(category);

        return R.ok();
    }

CategroyServiceImpl

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;
	
	@Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

CategoryBrandRelationDao

void updateCategroy(@Param("catId") Long catId,@Param("name") String name);

CateBrandRelationDao.xml

    <update id="updateCategroy">
        UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId}
    </update>

6.4 规格参数

模糊查询全部

image-20221107215222691

6.4.1 规格参数新增

1、流程梳理

  • 规格参数新增时,请求的URL:Request URL:/product/attr/save 现在的情况是,它在保存的时候,只是保存了attr,并没有保存attrgroup,为了解决这个问题,我们新建了一个vo/AttrVo,在原AttrEntity基础上增加了attrGroupId字段,使得保存新增数据的时候,也保存了它们之间的关系。

    通过" BeanUtils.copyProperties(attr,attrEntity);"能够实现在两个Bean之间拷贝数据,但是两个Bean的字段要相同

  • 当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范。比较规范的做法是,新建一个vo文件夹,将每种不同的对象,按照它的功能进行了划分。

  • 查看前端返回的数据,发现比数据库中的attr多了attrGroupId字段, 所以新建AttrVo

image-20221107215728567

image-20221107220224176

image-20221107220749049

image-20221107221203038

上面的保存的save方法只是逆向生成代码中最简单的,没有任何附加作用,所以我们进行修改。

2、创建Vo 包

@Data
public class AttrVo extends AttrEntity {
    private Long attrGroupId;
}

3、修改AttrController

    @RequestMapping("/save")
    public R save(@RequestBody AttrVo attr){
		attrService.saveAttr(attr);

        return R.ok();
    }

4、AttrServiceImpl

	@Autowired
    private AttrAttrgroupRelationDao attrAttrgroupRelationDao;

   @Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        //1.保存基本数据  保存attrEntity
        //利用attr的属性给attrEntity的属性赋值,前提是他俩的属性名一致
        BeanUtils.copyProperties(attr,attrEntity);   //因为一个一个属性设置太麻烦了
        this.save(attrEntity);
        //2.保存关联关系  保存AttrGroupId信息
        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
        relationEntity.setAttrGroupId(attr.getAttrGroupId());
        relationEntity.setAttrId(attr.getAttrId());
        attrAttrgroupRelationDao.insert(relationEntity);
    }

5、知识点补充:

image-20221109192550628

image-20221109192607816

6.4.2 获取分类规格参数

image-20221107222826873

1、新建AttrRespVo

@Data
public class AttrRespVo extends AttrVo {
    private String catelogName;
    private String groupName;
}

2、AttrController

    @RequestMapping("/base/list/{catelogId}")
    public R baseList(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId){
        PageUtils page = attrService.queryBaseAttrPage(params, catelogId);
        return R.ok().put("page", page);
    }

3、AttrServiceImpl

 	@Autowired
    private CategoryDao categoryDao;

    @Autowired
    private AttrGroupDao attrGroupDao;


	@Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<>();
        //模糊查询
        if (catelogId != 0){
            queryWrapper.eq("catelog_id", catelogId);
        }
        String key = (String) params.get("key");
        if (!StringUtils.isEmpty(key)){
            //attr_id  attr_name
            queryWrapper.and((wrapper) -> {
                wrapper.eq("attr_id", key).or().like("attr_name", key);
            });
        }
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),	
                queryWrapper
        );
    	//封装分页数据
        PageUtils pageUtils = new PageUtils(page);

        //查出新增的属性
        List<AttrEntity> records = page.getRecords();
        List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
            AttrRespVo attrRespVo = new AttrRespVo();
            BeanUtils.copyProperties(attrEntity, attrRespVo);
            CategoryEntity categoryEntity = categoryDao.selectOne(new QueryWrapper<CategoryEntity>().eq("cat_id", attrEntity.getCatelogId()));
            if (categoryEntity != null) {
                attrRespVo.setCatelogName(categoryEntity.getName());
            }

            AttrAttrgroupRelationEntity attrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
            if (attrgroupRelationEntity != null) {
                AttrGroupEntity attrGroupEntity = attrGroupDao.selectOne(new QueryWrapper<AttrGroupEntity>().eq("attr_group_id", attrgroupRelationEntity.getAttrGroupId()));
                attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }

            return attrRespVo;
        }).collect(Collectors.toList());
        // 把新的数据传送过去
        pageUtils.setList(respVos);
        return pageUtils;
    }

6.4.3 查询属性详情

image-20221107232117864

1、修改AttrRespVo

@Data
public class AttrRespVo extends AttrVo {
    private String catelogName;
    private String groupName;
    private Long[] catelogPath;
}

2、AttrController

    @RequestMapping("/info/{attrId}")
    public R info(@PathVariable("attrId") Long attrId){
		AttrRespVo attrRespVo = attrService.getAttrInfo(attrId);

        return R.ok().put("attr", attrRespVo);
    }

3、AttrServiceImpl

 @Override
    public AttrRespVo getAttrInfo(Long attrId) {
        AttrRespVo respVo = new AttrRespVo();
        AttrEntity attrEntity = this.getById(attrId);
        BeanUtils.copyProperties(attrEntity,respVo);

        //1.设置分组信息
        AttrAttrgroupRelationEntity attrAttrgroupRelation = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>()
                .eq("attr_id", attrId));
        if(attrAttrgroupRelation != null){
            respVo.setAttrGroupId(attrAttrgroupRelation.getAttrGroupId());
            AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelation.getAttrId());
            if(attrGroupEntity!= null){
                respVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }
        }
        //2.设置分类信息
       Long catelogId =  attrEntity.getCatelogId();
        Long[] catelogPath = categoryService.findCatelogPath(catelogId);
        respVo.setCatelogPath(catelogPath);

        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        if(categoryEntity!=null){
            respVo.setCatelogName(categoryEntity.getName());

        }
        return respVo;
    }

4、未解决问题:

规格参数处不显示所属分组。数据库中已经修改。如下图:

image-20221107232315108

image-20221111133103210

所属分组修改成功,数据库中已经成功,但是页面不显示的问题?

目前暂时解决办法是在:规格参数那新增的时候选择到销售属性。在规格参数中新增的时候选择到销售属性即可。

image-20221115194359273

6.4.4 修改属性

image-20221109231123060

image-20221116105723063

1、在AttrController中我们进行新增相应的方法

    @RequestMapping("/update")
    public R update(@RequestBody AttrVo attr){
		attrService.updateAttr(attr);

        return R.ok();
    }

2、AttrServiceImpl

	//保存时,要修改两张表   attr这张表和attrattrgroupRelationEntity这两个表
	@Transactional
    @Override
    public void updateAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr, attrEntity);
        this.updateById(attrEntity);

        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
        relationEntity.setAttrGroupId(attr.getAttrGroupId());
        relationEntity.setAttrId(attr.getAttrId());

        //判断是新增还是删除
        Integer count = attrAttrgroupRelationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
        if (count > 0){
            attrAttrgroupRelationDao.update(relationEntity, new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
        }else{
            attrAttrgroupRelationDao.insert(relationEntity);
        }

    }

6.5 平台属性

6.5.1 获取分类销售属性

image-20221116111518804

我们不难发现获取销售属性和规格参数的差别就是sale和base的区别,其他后面的都是差不多的,所以我们可以在路径变量中添加{attrType},使得同时用一个方法查询销售属性和规格参数。

image-20221111144455791

注意点:销售属性,没有分组信息,所以我们在复用方法的时候要进行判断到底是销售属性还是规格参数。

1、AttrController

@RequestMapping("/{attrType}/list/{catelogId}")
    public R baseList(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId,
                      @PathVariable("attrType") String attrType){
        PageUtils page = attrService.queryBaseAttrPage(params, catelogId, attrType);
        return R.ok().put("page", page);
    }

2、AttrServiceImpl

@Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(attrType)?1:0);
        if (catelogId != 0){
            queryWrapper.eq("catelog_id", catelogId);
        }
        
        String key = (String) params.get("key");
        if (!StringUtils.isEmpty(key)){
            //attr_id  attr_name
            queryWrapper.and((wrapper) -> {
                //根据id或者名称进行查询
                wrapper.eq("attr_id", key).or().like("attr_name", key);
            });
        }
        
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                queryWrapper
        );
        //封装分页数据
        PageUtils pageUtils = new PageUtils(page);
	//	查出新增的属性
        List<AttrEntity> records = page.getRecords();
        List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
            AttrRespVo attrRespVo = new AttrRespVo();
            BeanUtils.copyProperties(attrEntity, attrRespVo);
            //1.设置分类和分组的名字
            if ("base".equalsIgnoreCase(attrType)){
                AttrAttrgroupRelationEntity attrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
                if (attrgroupRelationEntity != null) {
                    AttrGroupEntity attrGroupEntity = attrGroupDao.selectOne(new QueryWrapper<AttrGroupEntity>().eq("attr_group_id", attrgroupRelationEntity.getAttrGroupId()));
                    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }
            }
            CategoryEntity categoryEntity = categoryDao.selectOne(new QueryWrapper<CategoryEntity>().eq("cat_id", attrEntity.getCatelogId()));
            if (categoryEntity != null) {
                attrRespVo.setCatelogName(categoryEntity.getName());
            }


            return attrRespVo;
        }).collect(Collectors.toList());
        // 把新的数据传送过去
        pageUtils.setList(respVos);
        return pageUtils;
    }

通过中间表pms_attr-attrgroup_relation去查pms_attr表。

image-20221116165945021

3、判断

  • 当新增/修改规格参数时,会在attrAttrGroupRelation表之中新增数据,但是销售属性没有分组信息。所以在新增/修改时,进行判断

  • 在Common中新建类ProductConstant,用来商品服务中的保存常量

public class ProductConstant {
    public enum AttrEnum{
       ATTR_TYPE_BASE(1,"基本属性"), ATTR_TYPE_SALE(0,"销售属性");

        private int code;
        private String msg;

        AttrEnum(int code, String msg){
            this.code = code;
            this.msg = msg;
        }
    }
}

  • saveAttr,getAttrInfo,updateAttr中,设计分组信息之前做判断
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode())//如果是规格参数,再操作分组信息

6.5.2 获取属性分组的关联的所有属性

image-20221116113107210

1、attrgroupController

    @GetMapping("/{attrgroupId}/attr/relation")
    public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId){
        List<AttrEntity> data = attrService.getRelationAttr(attrgroupId);
        return R.ok().put("data", data);
    }

2、attrServiceImpl

/**
     * 根据分组id查找关联的所有基本属性
     * @param attrgroupId
     * @return
     */
    @Override
    public List<AttrEntity> getRelationAttr(Long attrgroupId) {
        //通过pms_attr_attrgroup_relation中间表中的attr_group_id找到attrgroupId,然后去attr表中找到对应信息即可
        List<AttrAttrgroupRelationEntity> entities = relationDao.selectList(
                new QueryWrapper<AttrAttrgroupRelationEntity>().
                        eq("attr_group_id", attrgroupId));

        //找到所有的attrId的集合
        List<Long> attrIds = entities.stream().map((attr) -> {
            return attr.getAttrId();
        }).collect(Collectors.toList());

        //解决空指针异常问题
        if(attrIds == null || attrIds.size() == 0){
            return null;
        }
        //通过listByIds找到所有的attr实体类
        Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
        //返回
        return (List<AttrEntity>) attrEntities;

    }

6.5.3 删除属性与分组的关联关系

image-20221116113227271

1、新建AttrGroupRelationVo

@Data
public class AttrGroupRelationVo {

    private Long attrId;
    private Long attrGroupId;
}

2、AttrGroupController

    @PostMapping("/attr/relation/delete")
    public R attrRelationDelete(@RequestBody AttrGroupRelationVo[] vos){
        attrService.deleteRelation(vos);
        return R.ok();
    }

3、AttrServiceImpl

    @Override
    public void deleteRelation(AttrGroupRelationVo[] vos) {
        //将数组转换为集合list
        List<AttrGroupRelationVo> relationVos = Arrays.asList(vos);

//         AttrAttrgroupRelationDao relationDao 我们希望使用的是relationDao中使用的AttrAttrgroupRelationEntity这个实体类,所以我们转换
        List<AttrAttrgroupRelationEntity> entities = relationVos.stream().map((relationVo) -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            BeanUtils.copyProperties(relationVo, relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());


        //根据attrId,attrGroupId批量删除关联关系
        relationDao.deleteBatchRelation(entities);
    }

4、AttrAttrgroupRelationDao.xml

    <delete id="deleteBatchRelation">
        DELETE FROM `pms_attr_attrgroup_relation` WHERE
        <!--遍历循环来实现动态取值删除-->
        <foreach collection="entities" item="item" separator=" OR ">
            (attr_id=#{item.attrId} AND attr_group_id=#{item.attrGroupId})
        </foreach>
    </delete>

6.5.4 获取属性分组没有关联的其他属性

image-20221116113506657

1、AttrGroupController

    @GetMapping("/{attrgroupId}/noattr/relation")
    public R attrNoRelation(@PathVariable("attrgroupId") Long attrgroupId, @RequestParam Map<String, Object> params){
        PageUtils page = attrService.getNoRelationAttr(params, attrgroupId);
        return R.ok().put("page", page);
    }

2、AttrServiceImpl

  • 当前分组只能关联自己所属分类里面的所有属性
  • 当前分组只能关联别的分组没有引用的属性

​ - 当前分类下的其他分组

​ - 这些分组关联的属性

​ - 从当前分类的所有属性中移除这些属性


    /**
     * 获取当前分组没有关联的所有属性
     * @param params
     * @param attrgroupId
     * @return
     */
    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
        //1.当前分组只能关联自己所属分类里面的所有属性
        AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
        //得到分类id
        Long catelogId = attrGroupEntity.getCatelogId();
        //2.当前分组只能关联别的分组没有引用的属性
        //2.1当前分类下的其他分组
        List<AttrGroupEntity> attrGroupEntities = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
        List<Long> collect = attrGroupEntities.stream().map(attrGroupEntity1 -> {
            return attrGroupEntity1.getAttrGroupId();
        }).collect(Collectors.toList());
        //2.2 这些分组关联的属性
        List<AttrAttrgroupRelationEntity> relationEntities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collect));
        //属性id的集合
        List<Long> attrIds = relationEntities.stream().map((relationEntity) -> {
            return relationEntity.getAttrId();
        }).collect(Collectors.toList());
        //从当前分类的所有属性中移除这些属性
        QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type", ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
        if(attrIds != null && attrIds.size() > 0){
            wrapper.notIn("attr_id",attrIds);
        }
        //模糊查询
        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            wrapper.and((w) ->{
                w.eq("attr_id",key).or().like("attr_name",key);
            });
        }
        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);
        return new PageUtils(page);

    }

6.5.5 添加属性与分组关联关系

image-20221116113732517

1、AttrGroupController

    @PostMapping("/attr/relation")
    public R attrRelation(@RequestBody List<AttrGroupRelationVo> vos){
        relationService.saveBatch(vos);
        return R.ok();
    }

2、AttrAttrgroupRelationServiceImpl

    @Override
    public void saveBatch(List<AttrGroupRelationVo> vos) {
        List<AttrAttrgroupRelationEntity> entities = vos.stream().map((vo) -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            //属性对拷
            BeanUtils.copyProperties(vo, relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());
        this.saveBatch(entities);
    }

6.6 新增商品

6.6.1 调试会员等级相关接口

1、前端代码拷贝

  • 我们将老师给的课件中有关于前端的所有代码复制到前端项目中,其实后面很多的操作中最难的部分是前端页面的编写。我们最主要的还是后端,所以说直接将老师所给的代码直接复制,关注后端代码的编写即可。

image-20221115231347499

image-20221116183028294

image-20221117102015817

2、微服务注册及相应网关配置

  • 其实逆向生成的代码中已经有这个接口的完整方法,我们要做的事就是将gulimall-member这个微服务注册到nacos服务注册中心,并且配置相应的网关路由

注册到服务注册中心的套路和配置相应的网关路由前面已经详细讲解过,这里不再赘述。

        - id: member_route
          uri: lb://gulimall-member
          predicates:
             - Path=/api/member/**
          filters:
             - RewritePath=/api/(?<segment>.*),/$\{segment}

3、添加一些数据进行测试

image-20221116182953901

在用户系统中的会员等级中添加上面的几个等级名称。

6.6.2 获取分类关联的品牌

1、思路梳理

  • 新增商品时,点击商品的分类,要获取与该分类关联的所有品牌

image-20221116183802756

  • 请求参数catId不是路径变量,所以我们使用@RequestParam("catId")从请求中获取。

  • 因为响应数据不是page分页数据,所以我们直接使用list即可。

  • 返回的数据不是entity中的所有数据,我们就可以通过封装为vo来进行解决返回数据的问题。

2、新建BrandVo

@Data
public class BrandVo {
    private Long brandId;
    private String brandName;
}

3、CategoryBrandRelationController

/**
     * value="catId",required = true   这样写即为设置必须要传入这个参数
     *
     * controller套路:  --- 接收处理数据,传给service,接受service处理完的数据
     * 1.controller:处理请求,接受和校验数据
     * 2.service接受controller传来的数据,进行业务处理
     * 3.controller接受service处理完的数据,封装页面指定的vo
     *
     * @param catId
     * @return
     */
    @GetMapping("/brands/list")
    public R relationBrandsList(@RequestParam(value="catId",required = true) Long catId){
       List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);

        /**
         * 有一个疑问,这个地方的item为什么是BrandEntity?
         * 解释:java.util.stream.Stream<T>
         *       public abstract <R> Stream<R> map(java.util.function.Function<? super T, ? extends R> mapper)
         */
        List<BrandVo> collect = vos.stream().map(item -> {
            BrandVo brandVo = new BrandVo();
            brandVo.setBrandId(item.getBrandId());
            //因为brandentity中的品牌名是name,不是BrandName,所以不能进行属性对拷
            brandVo.setBrandName(item.getName());
            return brandVo;
        }).collect(Collectors.toList());

        //返回的数据是"key:data",这个地方put(key,value).
        return  R.ok().put("data",collect);

4、CategoryBrandRelationServiceImpl

@Autowired
BrandService brandService;

@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
    List<CategoryBrandRelationEntity> catelogId = this.baseMapper.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
    List<BrandEntity> collect = catelogId.stream().map(item -> {
        Long brandId = item.getBrandId();
        BrandEntity byId = brandService.getById(brandId);
        return byId;
    }).collect(Collectors.toList());
    return collect;
}

5、测试

我们上面的代码已经完全没有问题了,但是为什么进行测试的收回出现错误呢?

前端项目中没有引入pubsub.js,这个是只要按照老师进度,都会范的错误,接下来进行解决。

image-20221116192725424

6、踩坑及解决办法

下图是翻自b站评论区的一个解决方法, 注意这个地方仍然会有坑,那就是node.js以及node-sass这个版本的问题。

image-20221116193328700

  1. vscode终端中执行 npm install --save pubsub-js报错

image-20221116193420457

  1. 报错原因是:node.js版本太高,我们需要降级为和老师课件一致的版本才行。即node.js:10.16.3

image-20221116193959643

  1. 发现是Nodejs的版本问题导致的一直安装失败,我们选择最简单的使用nvm来管理nodejs版本,方便切换。

附:nvm最新下载地址:https://github.com/coreybutler/nvm-windows/releases

  • 下载.exe安装包进行安装

  • image-20221117104456681

  • 查看nvm版本image-20221116195250929

  1. 使用nvm进行nodejs版本切换

image-20221116195846495

  1. 切换版本之后将本地前端项目renren-fast-vue中的node-modules这个文件夹给删除就好了。后面这个node-modules文件夹中的内容会在你进行任何Npm操作时自动生成。

  2. 进行npm install --save pubsub-js 之后继续报错,查看报错原因:node-sass版本问题。

image-20221116200746303

  • 解决办法:

image-20221116200642963

  • 先进行npm uninstall node-sass,然后安装指定版本: npm install node-sass@4.9.0 ,最后进行npm install --save pubsub-js.

image-20221116201106794

再次执行安装之后不报错了。

  1. 在前端项目renren-fast-vue的src的main.js中引用:
  • import PubSub from 'pubsub-js'
  • Vue.prototype.PubSub = PubSub

如下图:

image-20221116201312836

  1. 重新运行前端项目,成功解决。

image-20221116201440604

  1. 还有一个坑,就是在前面我们复制的前端组件中有一些方法是缺失的,缺少某些方法,我们可以重新进行复制。

image-20221116192944568

image-20221116192951133

  • 将老师课件中的压缩包gulimall-admin-vue解压,将里面的modules下的文件全部重新复制到前端项目中。

image-20221116193009856

image-20221116193222206

6.6.3 获取分类下所有分组以及属性

image-20221117105718004

image-20221116202606518

1、数据处理

  1. 在这个地方老师的课件的数据是如下图的这些数据,所以我们可以尝试将这一部分的数据进行修改。

image-20221116203819272

image-20221116213208422

image-20221116213220837

image-20221116214538943

这里面的快速展示我们可以不需要全部都选,因为有一些无关紧要的设置我们可以不在商品介绍页面进行展示。还有一些销售属性的attrType是2,既是销售属性也是规格参数,所以有些可以选,有些可以不选。

  1. 将数据库中的部分数据换成和老师课件中一样的。

注意

因为我们在数据库中设计某些表的时候都是设置了主键的,我们拿pms_attr这张表为例。如果我们后期想要进行数据的添加(直接导入老师sql中的数据,避免我们自己手动添加出错),但是因为设置了主键的原因,你的attr_id这个主键是会不连续的。这个即使删除数据也没办法。那么如何解决下面的这个问题呢?

image-20221117093038243

image-20221117093138250

  1. sql文件
//从表中去掉这个主键这一列
ALTER TABLE `pms_attr` DROP attr_id;  

//重新加上主键这一列并设置 为主键
ALTER TABLE `pms_attr` ADD attr_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST;  

-- ----------------------------
-- Records of pms_attr
-- ----------------------------

INSERT INTO `pms_attr` VALUES ('7', '入网型号', '0', '0', 'xxx', 'A2217;C3J;以官网信息为准', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('8', '上市年份', '0', '0', 'xxx', '2018;2019', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('9', '颜色', '0', '0', 'xxx', '黑色;白色;蓝色', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('10', '内存', '0', '0', 'xxx', '4GB;6GB;8GB;12GB', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('11', '机身颜色', '0', '0', 'xxx', '黑色;白色', '1', '1', '225', '1');
INSERT INTO `pms_attr` VALUES ('12', '版本', '0', '0', 'xxx', '', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('13', '机身长度(mm)', '0', '0', 'xx', '158.3;135.9', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('14', '机身材质工艺', '0', '1', 'xxx', '以官网信息为准;陶瓷;玻璃', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('15', 'CPU品牌', '1', '0', 'xxx', '高通(Qualcomm);海思(Hisilicon);以官网信息为准', '1', '1', '225', '1');
INSERT INTO `pms_attr` VALUES ('16', 'CPU型号', '1', '0', 'xxx', '骁龙665;骁龙845;骁龙855;骁龙730;HUAWEI Kirin 980;HUAWEI Kirin 970', '1', '1', '225', '0');

-- ----------------------------
-- Records of pms_attr_attrgroup_relation
-- ----------------------------
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('23', '7', '1', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('24', '8', '1', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('26', '11', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('27', '13', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('28', '14', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('29', '15', '7', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('30', '16', '7', NULL);


-- ----------------------------
-- Records of pms_attr_group
-- ----------------------------
INSERT INTO `pms_attr_group` VALUES ('1', '主体', '0', '主体', 'dd', '225');
INSERT INTO `pms_attr_group` VALUES ('2', '基本信息', '0', '基本信息', 'xx', '225');
INSERT INTO `pms_attr_group` VALUES ('4', '屏幕', '0', '屏幕', 'xx', '233');
INSERT INTO `pms_attr_group` VALUES ('7', '主芯片', '0', '主芯片', 'xx', '225');


-- ----------------------------
-- Records of pms_category_brand_relation
-- ----------------------------
INSERT INTO `pms_category_brand_relation` VALUES ('13', '9', '225', '华为', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('15', '9', '250', '华为', '平板电视');
INSERT INTO `pms_category_brand_relation` VALUES ('16', '9', '449', '华为', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('17', '10', '449', '小米', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('18', '10', '225', '小米', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('19', '10', '231', '小米', '移动电源');
INSERT INTO `pms_category_brand_relation` VALUES ('20', '10', '233', '小米', '蓝牙耳机');
INSERT INTO `pms_category_brand_relation` VALUES ('21', '10', '250', '小米', '平板电视');
INSERT INTO `pms_category_brand_relation` VALUES ('22', '10', '449', '小米', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('23', '11', '225', 'oppo', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('24', '11', '227', 'oppo', '合约机');
INSERT INTO `pms_category_brand_relation` VALUES ('25', '12', '225', 'Apple', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('26', '12', '243', 'Apple', 'iPhone 配件');
INSERT INTO `pms_category_brand_relation` VALUES ('27', '12', '366', 'Apple', '智能手表');

-- ----------------------------
-- Records of pms_product_attr_value
-- ----------------------------
INSERT INTO `pms_product_attr_value` VALUES ('55', '13', '7', '入网型号', 'A2217', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('56', '13', '8', '上市年份', '2018', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('57', '13', '13', '机身长度(mm)', '158.3', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('58', '13', '14', '机身材质工艺', '以官网信息为准', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('59', '13', '15', 'CPU品牌', '以官网信息为准', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('60', '13', '16', 'CPU型号', 'A13仿生', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('61', '11', '7', '入网型号', 'LIO-AL00', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('62', '11', '8', '上市年份', '2019', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('63', '11', '11', '机身颜色', '黑色', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('64', '11', '13', '机身长度(mm)', '158.3', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('65', '11', '14', '机身材质工艺', '玻璃;陶瓷', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('66', '11', '15', 'CPU品牌', '海思(Hisilicon)', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('67', '11', '16', 'CPU型号', 'HUAWEI Kirin 970', NULL, '1');

上面这些都是可以自己按照老师课件来进行设置的,但是对于图集这些是因为使用的我们自己的oss云存储服务,所以我们这块需要自己进行设置了。

2、新建AttrGroupWithAttrsVo

从接口文档中的响应数据来看,我们发现返回的data数据中有一个attrs[]数组列表,返回的数据是AttrGroupEntity上增加了一个attrEntity.所以我们新建一个AttrGroupWithAttrsVo

@Data
public class AttrGroupWithAttrsVo {
    /**
     * 分组id
     */
    private Long attrGroupId;
    /**
     * 组名
     */
    private String attrGroupName;
    /**
     * 排序
     */
    private Integer sort;
    /**
     * 描述
     */
    private String descript;
    /**
     * 组图标
     */
    private String icon;
    /**
     * 所属分类id
     */
    private Long catelogId;

    private List<AttrEntity> attrs;
}

3、AttrGroupController

@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId){
    //1、查出当前分类下的所有属性分组
    //2、查出每个属性分组的所有属性
    List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);
    return R.ok().put("data", vos);

}

4、AttrGroupServiceImpl

/*
*根据分类id查出所有的分组以及这些分组里面的属性
*@param:[catelogId]
*@return:java.util.List<com.xmh.gulimall.product.vo.AttrGroupWithAttrsVo>
*@date: 2021/8/16 21:13
*/
@Override
public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
    //所有分组
    List<AttrGroupEntity> attrGroupEntities = this.baseMapper.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
    List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(item -> {
        AttrGroupWithAttrsVo attrsVo = new AttrGroupWithAttrsVo();
        BeanUtils.copyProperties(item, attrsVo);
        //属性
        List<AttrEntity> relationAttr = attrService.getRelationAttr(item.getAttrGroupId());
        attrsVo.setAttrs(relationAttr);
        return attrsVo;
    }).collect(Collectors.toList());
    return collect;
}

6.6.4 新增商品

1、举例

在商品系统--商品维护--发布商品中添加商品。本次课堂案例以华为 HUAWEI Mate 30 PRO为例

image-20221117120238867

我们可以在发布商品的时候设置一些折扣优惠信息。

2、json转java实体类

当发布商品的所有信息都选择好之后,我们查看控制台,得到一串json数据。将其拷贝,通过json工具将我们得到的数据进行抽取,按照我们指定的包名和总的vo名,生成相应的java实体类。

json格式化工具:https://www.bejson.com/,json生成java类:https://www.bejson.com/json2javapojo/new/

image-20221117121519111

{"spuName":"华为 HUAWEI Mate 30 PRO","spuDescription":"华为 HUAWEI Mate 30 PRO","catalogId":225,"brandId":9,"weight":0.198,"publishStatus":0,"decript":["https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/cc346fa8-da32-47e4-8e17-34a4581022ee_b094601548ddcb1b.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/e3ab0c02-cc48-4405-89a2-7e9141ff2b41_528211b97272d88a.jpg"],"images":["https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18137163-649c-4120-86a4-e1a8b4c6f4c6_1f15cdbcf9e1273c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/e80ada95-24cf-4cbf-8320-9e5049b67627_1f15cdbcf9e1273c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/8851510f-3925-462d-87ce-8c1305f8a609_2b1837c6c50add30.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/25b7a3a3-9f1c-4516-8b90-8ccf6b861733_3c24f9cd69534030.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/56f7d57e-0cc1-4999-8a4c-fc4b63a32e35_a0b8774404ae8a2c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg"],"bounds":{"buyBounds":500,"growBounds":500},"baseAttrs":[{"attrId":7,"attrValues":"LIO-AL00","showDesc":1},{"attrId":8,"attrValues":"2019","showDesc":1},{"attrId":13,"attrValues":"158.3","showDesc":0},{"attrId":14,"attrValues":"其他","showDesc":0},{"attrId":15,"attrValues":"海思(Hisilicon)","showDesc":1},{"attrId":16,"attrValues":"HUAWEI Kirin 980","showDesc":1}],"skus":[{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"亮黑色"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 亮黑色 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 亮黑色 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg","defaultImg":0}],"descar":["亮黑色","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"亮黑色"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 亮黑色 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 亮黑色 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg","defaultImg":0}],"descar":["亮黑色","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"翡翠冷"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 翡翠冷 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 翡冷翠 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["翡翠冷","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"翡翠冷"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 翡翠冷 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["翡翠冷","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"罗兰紫"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 罗兰紫 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["罗兰紫","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"罗兰紫"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 罗兰紫 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":1},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["罗兰紫","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"星河银"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 星河银 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 星河银 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["星河银","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"星河银"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 星河银 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 星河银 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["星河银","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]}]}

image-20221117121548515

image-20221117121850954

  • 解压复制到项目中。

image-20221117121945780

image-20221117143726931

  • 微调vo:我们查看生成的代码,发现有get和set方法,我们这个地方可以将这些方法进行去掉,然后使用lombok中的@Data注解即可。把所有id字段改成Long类型,把所有double类型改成BigDecimal类型。

  • 真实项目中需要进行校验,校验是当传过来的数据不符合之后,给前端返回相应的异常错误代码。

  • 这个地方为了防止和老师所讲代码有差别,直接将老师课件中的代码复制到product包下的vo包中即可。

3、接口文档!

image-20221117122338623

4、思路梳理

保存商品涉及到多个表之间的关系。我们先要搞清楚到底需要保存哪些东西?

  • 保存spu基本信息 --- pms_spu_info

    • 保存spu的描述图片 --- pms_spu_info_desc

    • 保存spu的图片集 --- pms_spu_images

    • 保存spu的规格参数 --- pms_product_attr_value

    • 保存spu的积分信息 --- gulimall_sms->sms_spu_bounds

    • 保存spu对应的所有sku信息

​ - sku的基本信息 --- pms_sku_info

​ - sku的图片信息 --- pms_sku_images

​ - sku的销售属性信息 --- pms_sku_sale_attr_value

​ - sku的优惠、满减等信息 --- gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price

5、具体实现

这些基础是要将数据库中表的字段要熟悉

SpuInfoController

  /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody SpuSaveVo vo){
//		spuInfoService.save(spuInfo);
        spuInfoService.saveSpuInfo(vo);
        return R.ok();
    }

6、SpuInfoServiceImpl

(此处代码是下一步debug之后完整可正常运行的代码,和老师讲课正常章节有所出入)

/**
 * //TODO  高级部分再来完善
 */
@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {
    @Autowired
    SpuInfoDescService spuInfoDescService;

    @Autowired
    SpuImagesService imagesService;

    @Autowired
    AttrService attrService;

    @Autowired
    ProductAttrValueService   attrValueService;

    @Autowired
    SkuInfoService skuInfoService;

    @Autowired
    SkuImagesService skuImagesService;

    @Autowired
    SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    CouponFeignService couponFeignService;
 
    @Transactional
    @Override
    public void saveSpuInfo(SpuSaveVo vo) {
        //1、保存spu基本信息`pms_spu_info`
        SpuInfoEntity infoEntity = new SpuInfoEntity();
        //将vo中携带的数据复制给infoEntity  属性对拷
        BeanUtils.copyProperties(vo,infoEntity);
        //infoEntity中还需要有CreateTime,UpdateTime,属性对拷未复制完全的我们可以自己设置
        infoEntity.setCreateTime(new Date());
        infoEntity.setUpdateTime(new Date());
        this.save(infoEntity);

        //2、保存spu的描述图片`pms_spu_info_desc`
        List<String> decript = vo.getDecript();
        SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
        descEntity.setSpuId(infoEntity.getId());
        //使用,进行分割拼接,因为图片都被转换为了链接
        descEntity.setDecript(String.join(",",decript));
        spuInfoDescService.saveSpuInfoDesc(descEntity);

        //3、保存spu的图片集`pms_spu_images`
        List<String> images = vo.getImages();
        imagesService.saveImages(infoEntity.getId(),images);


        //4、保存spu的规格参数`pms_product_attr_value`
        List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
        List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
            valueEntity.setAttrId(attr.getAttrId());
            // spu_id :13  attr_id:7    attr_name:入网型号     attr_value:A2217    quick_show :0
            AttrEntity id = attrService.getById(attr.getAttrId());
            valueEntity.setAttrName(id.getAttrName());
            valueEntity.setAttrValue(attr.getAttrValues());
            valueEntity.setQuickShow(attr.getShowDesc());
            valueEntity.setSpuId(infoEntity.getId());
            return valueEntity;
        }).collect(Collectors.toList());
        attrValueService.saveProductAttr(collect);


        //5、保存spu的积分信息`gulimall_sms`->`sms_spu_bounds`
        Bounds bounds = vo.getBounds();
        SpuBoundTo spuBoundTo = new SpuBoundTo();
        BeanUtils.copyProperties(bounds,spuBoundTo);
        spuBoundTo.setSpuId(infoEntity.getId());
        R r = couponFeignService.saveSpuBounds(spuBoundTo);
        if(r.getCode() != 0){
            log.error("远程保存spu积分信息失败");
        }

        //6、保存spu对应的所有sku信息
        List<Skus> skus = vo.getSkus();
        if(skus != null && skus.size()>0){
            skus.forEach(item ->{
                String defaultImage = "";
                //查找出默认图片
                for (Images image: item.getImages()) {
                    //0 不是默认图   1:是默认图
                    if(image.getDefaultImg() == 1){
                        defaultImage  = image.getImgUrl();

                    }
                }
                //private String skuName;
                //private BigDecimal price;
                //private string skuTitle;
                //private string skuSubtitle;
                
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(item,skuInfoEntity);
                skuInfoEntity.setSpuId(infoEntity.getId());
                skuInfoEntity.setBrandId(infoEntity.getBrandId());
                skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                skuInfoEntity.setSkuDefaultImg(defaultImage);
                //6.1、sku的基本信息`pms_sku_info`
                skuInfoService.saveSkuInfo(skuInfoEntity);

                //6.2、sku的图片信息`pms_sku_images`
                Long skuId = skuInfoEntity.getSkuId();
                List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    skuImagesEntity.setSkuId(skuId);
                    skuImagesEntity.setImgUrl(img.getImgUrl());
                    skuImagesEntity.setDefaultImg(img.getDefaultImg());
                    return skuImagesEntity;
                }).filter(entity ->{
                    //返回true就是需要,false就是剔除   这个地方是鉴别有些没有图片路径的就不需要进行保存
                    return !StringUtils.isEmpty(entity.getImgUrl());
                }).collect(Collectors.toList());
                skuImagesService.saveBatch(imagesEntities);

                //6.3、sku的销售属性信息`pms_sku_sale_attr_value`
                List<Attr> attr = item.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
                    SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(a, attrValueEntity);
                    attrValueEntity.setSkuId(skuId);
                    return attrValueEntity;
                }).collect(Collectors.toList());
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

                //6.4、sku的优惠、满减等信息`gulimall_sms`->`sms_sku_ladder`/`sms_sku_full_reduction`/`sms_member_price`
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(item,skuReductionTo);
                skuReductionTo.setSkuId(skuId);
                if(skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
                    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
                    if(r1.getCode() != 0){
                        log.error("远程保存sku优惠信息失败");
                    }
                }
            });
        }
    }
}

其中一些自动注入的service方法的实现,主要是使用的保存方法。我们使用service方法自动注入,因为其比dao更加全一些。

1. SpuInfoDescServiceImpl
    
     @Override
    public void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {
        this.baseMapper.insert(descEntity);
    }

2. SpuImagesServiceImpl
    
     @Override
    public void saveImages(Long id, List<String> images) {
        if(images == null || images.size() == 0){

        }else {
            List<SpuImagesEntity> collect = images.stream().map(img -> {
                SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
                spuImagesEntity.setSpuId(id);
                spuImagesEntity.setImgUrl(img);
                return spuImagesEntity;
            }).collect(Collectors.toList());
            this.saveBatch(collect);
        }
    }

3. ProductAttrValueServiceImpl
    
    @Override
    public void saveProductAttr(List<ProductAttrValueEntity> collect) {
        this.saveBatch(collect);
    }

4. SkuInfoServiceImpl
    
    @Override
    public void saveSkuInfo(SkuInfoEntity skuInfoEntity) {
        this.baseMapper.insert(skuInfoEntity);
    }

7、创建TO

对于远程调用,其实就是跨表操作。我们可以创建一个TO来做远程调用

image-20221118105439441

  • 在common微服务中创建一个SkuReductionTo用作远程调用
@Data
public class SkuReductionTo {
    private Long skuId;
    private int fullCount;
    private BigDecimal discount;
    private int countStatus;
    private BigDecimal fullPrice;
    private BigDecimal reducePrice;
    private int priceStatus;
    private List<MemberPrice> memberPrice;
}
  • 同时将json自动生成的memberPrice这个vo也复制到common中

    @Data
    public class MemberPrice {
    
        private Long id;
        private String name;
        private BigDecimal price;
        
    }
    

    image-20221118105718050

8、新建CouponFeignService

product包中新建fegin.CouponFeignService用来远程调用Coupon服务

一共调用了两个服务"coupon/spubounds/save""coupon/skufullreduction/saveInfo"

image-20221118105858637

  • 第一个服务使用自动生成,直接调用即可
@FeignClient("gulimall-coupon")
public interface CouponFeignService {

    /**
    *1、CouponFeginService.saveSpuBounds(spuBoudnTo)的实现步骤:
     *  1)、@RequestBody 将传入的这个对象转化为json
     *  2)、找到gulimall-coupon服务,给coupon/spubounds/save发送请求
     *      将上一步转的json放在请求体位置,发送数据
     *  3)、对方服务接受请求,请求体里面有json数据
     *      public R save(@RequestBody SpuBoundsEntity spuBounds);
     *      将请求体的json转化为SpuBoundsEntity;
     * 只要json数据模型是兼容的。双方无需使用同一个to
    *@param:[spuBoundTo]
    *@return:com.xmh.common.utils.R
    *@date: 2021/8/18 1:28
    */
    @PostMapping("coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);

    @PostMapping("coupon/skufullreduction/saveInfo")
    R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

image-20221117211639194

  • 第二个服务在SkuFullReductionController中新建方法
    @PostMapping("/saveInfo")
    public R saveInfo(@RequestBody SkuReductionTo skuReductionTo){
        skuFullReductionService.saveSkuReduction(skuReductionTo);
        return R.ok();
    }

  • SkuFullReductionServiceImpl中实现

@Service("skuFullReductionService")
public class SkuFullReductionServiceImpl extends ServiceImpl<SkuFullReductionDao, SkuFullReductionEntity> implements SkuFullReductionService {

    @Autowired
    SkuLadderService skuLadderService;

    @Autowired
    MemberPriceService memberPriceService;
   
    @Override
    public void saveSkuReduction(SkuReductionTo reductionTo) {
        //6.4、sku的优惠、满减等信息`gulimall_sms`->`sms_sku_ladder`/`sms_sku_full_reduction`/`sms_member_price`

        //sms_sku_ladder   保存满减打折,会员价
        SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
        skuLadderEntity.setSkuId(reductionTo.getSkuId());
        skuLadderEntity.setFullCount(reductionTo.getFullCount());
        skuLadderEntity.setDiscount(reductionTo.getDiscount());
        skuLadderEntity.setAddOther(reductionTo.getCountStatus());
        if(reductionTo.getFullCount() > 0){
            skuLadderService.save(skuLadderEntity);
        }

        skuLadderService.save(skuLadderEntity);

        //sms_sku_full_reduction  保存满减信息 
        SkuFullReductionEntity reductionEntity = new SkuFullReductionEntity();
        BeanUtils.copyProperties(reductionTo,reductionEntity);
        if(reductionEntity.getFullPrice().compareTo(new BigDecimal("0")) == 1){
            this.save(reductionEntity);

        }

        //sms_member_price  保存会员价格 
        List<MemberPrice> memberPrice = reductionTo.getMemberPrice();
        List<MemberPriceEntity> collect = memberPrice.stream().map(item -> {
            MemberPriceEntity priceEntity = new MemberPriceEntity();
            priceEntity.setSkuId(reductionTo.getSkuId());
            priceEntity.setMemberLevelId(item.getId());
            priceEntity.setMemberLevelName(item.getName());
            priceEntity.setMemberPrice(item.getPrice());
            //设置默认叠加优惠
            priceEntity.setAddOther(1);
            return priceEntity;
        }).filter(item -> {
            return item.getMemberPrice().compareTo(new BigDecimal("0")) == 1;
        }).collect(Collectors.toList());


        memberPriceService.saveBatch(collect);
    }

}
  • 在给前端返回的R这个类中添加一个getCode方法,方便判断远程调用是否成功(注意这里有坑,后面debug的时候修正)
public Integer getCode(){
   return Integer.parseInt((String) this.get("code"));
}

6.6.5 内存调优及一键启停

1、新建Compound

image-20221118110533760

image-20221118110550224

2、把服务添加到新建的compound里

image-20221118110611134

3、设置每个项目最大占用内存为100M

image-20221118110626688

这样可以大大减少内存占用。

6.6.6 商品保存debug

1、隔离级别

我们在进行debug的时候,因为我们在上面设置了事务的原因,而mysql默认是可重复读(REPEATABLE READ),所以我们可以暂时设置隔离级别。

如果我们使用@Transactional,不指定隔离级别,就会使用数据库的默认隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

2、问题及解决

  1. 出现问题SpuInfoDescEntity,mybatis默认主键为自增的,而SpuInfoDescEntity中的主键为自己输入的,所以修改主键注释

image-20221118111021507

  1. 抛出异常,修改R中的getCode方法
	public Integer getCode(){
		return (Integer) this.get("code");
	}

  1. 出现问题,保存sku图片时,有些图片是没有路径的,没有路径的图片,无需保存。

解决办法:在收集图片的时候进行过滤

                List<SkuImagesEntity> skuImagesEntities = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    skuImagesEntity.setSkuId(skuId);
                    skuImagesEntity.setImgUrl(img.getImgUrl());
                    skuImagesEntity.setDefaultImg(img.getDefaultImg());
                    return skuImagesEntity;
                }).filter(entity -> {
                    //返回true是需要,返回false是过滤掉
                    return !StringUtils.isNullOrEmpty(entity.getImgUrl());
                }).collect(Collectors.toList());
                skuImagesService.saveBatch(skuImagesEntities);

  1. 保存折扣信息的时候,满0元打0折这种都是无意义的,要过滤掉

image-20221118111306471

解决方法:在保存之前做判断,过滤掉小于等于0的无意义信息(不贴代码了),要注意的是判断BigDecimal进行判断时,要用compareTo函数。如果是普通的,就不需要做判断。

下面举例:

if(reductionTo.getFullCount() > 0){
            skuLadderService.save(skuLadderEntity);
        }
                if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
                    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
                    if (r1.getCode() != 0){
                        log.error("远程保存优惠信息失败");
                    }
                }
  1. 保存失败,原因【系统未知异常】的原因及解决办法

image-20221117231459629

image-20221117231436093

保存的时候出现上面这个原因,我们去控制台中查看得知是调用远程服务超时导致。因为会去nacos中进行寻找,我们所要做的就是等待feign稳定即可。

3、新增apple 11 完善数据库

按照华为mate30pro新增方法,新增一个apple 11到数据库中。

image-20221118112233657

6.7 商品管理

6.7.1 spu检索

image-20221118151951662

1、SpuInfoController.java

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = spuInfoService.queryPageByCondition(params);

        return R.ok().put("page", page);
    }

2、SpuInfoServiceImpl

 @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();

        /**
         * status:2
         * key:
         * brandId:9
         * catelogId:225
         */
        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            wrapper.and((w)->{
                w.eq("id",key).or().like("spu_name",key);
            });
        }

        String status = (String) params.get("status");
        if(!StringUtils.isEmpty(status)){
            wrapper.eq("publish_status",status);
        }

        String brandId = (String) params.get("brandId");
        if(!StringUtils.isEmpty(brandId)){
            wrapper.eq("brand_id",brandId);
        }


        String catelogId = (String) params.get("catelogId");
       if(!StringUtils.isEmpty(catelogId)){
            wrapper.eq("catalog_id",catelogId);
        }


        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                wrapper

        );

        return new PageUtils(page);
    }

3、测试

image-20221118170624511

成功。

2022-11-18 17:07:36.366 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage  : ==>  Preparing: SELECT COUNT(1) FROM pms_spu_info WHERE (((id = ? OR spu_name LIKE ?)) AND publish_status = ? AND brand_id = ? AND catalog_id = ?) 
2022-11-18 17:07:36.366 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage  : ==> Parameters: 华为(String), %华为%(String), 0(String), 9(String), 225(String)
2022-11-18 17:07:36.368 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage  : ==>  Preparing: SELECT id,spu_description,spu_name,catalog_id,create_time,brand_id,weight,update_time,publish_status FROM pms_spu_info WHERE (( (id = ? OR spu_name LIKE ?) ) AND publish_status = ? AND brand_id = ? AND catalog_id = ?) LIMIT ?,? 
2022-11-18 17:07:36.368 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage  : ==> Parameters: 华为(String), %华为%(String), 0(String), 9(String), 225(String), 0(Long), 10(Long)
2022-11-18 17:07:36.370 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage  : <==      Total: 1

4、时间格式问题解决

测试发现时间格式不对,如下图:

image-20221118155345409

我们可以在配置文件中进行设置:

image-20221118170909772

重启进行测试,结果如下,时间格式正确。

image-20221118155358904

6.7.2 sku检索

image-20221119102505952

1、SkuInfoController

    /**
     * 列表
     */
    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = skuInfoService.queryPageByCondition(params);

        return R.ok().put("page", page);
    }

2、SkuInfoServiceImpl(这里的代码对老师代码进行简写,测试可以正常通过)

 @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<SkuInfoEntity> wrapper = new QueryWrapper<>();

        String key = (String) params.get("key");
        if (!StringUtils.isNullOrEmpty(key)){
            wrapper.and((w) -> {
                w.eq("sku_id", key).or().like("sku_name", key);
            });
        }

        String catelogId = (String) params.get("catelogId");
        if (!StringUtils.isNullOrEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)){
            wrapper.eq("catalog_id", catelogId);
        }

        String brandId = (String) params.get("brandId");
        if (!StringUtils.isNullOrEmpty(brandId) && !"0".equalsIgnoreCase(brandId)){
            wrapper.eq("brand_id", brandId);
        }

        String min = (String) params.get("min");
        if (!StringUtils.isNullOrEmpty(min) && !"0".equalsIgnoreCase(min)){
            wrapper.ge("price", min);
        }

        String max = (String) params.get("max");
        if (!StringUtils.isNullOrEmpty(max) && !"0".equalsIgnoreCase(max)){
            wrapper.le("price", max);
        }


        IPage<SkuInfoEntity> page = this.page(
                new Query<SkuInfoEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);

    }

6.8 仓储服务之仓库管理

6.8.1 整合ware服务获取仓库列表

1、数据库表的说明

  • 仓库维护 wms_ware_info

image-20221119104340452

image-20221119104408154

  • 商品库存 wms_ware_sku

image-20221119104445408

image-20221119104505716

2、将仓库服务注册到nacos中

image-20221119105144271

3、微服务网关路由重写

否则点击相应的菜单报404错误

        - id: ware_route
          uri: lb://gulimall-ware
          predicates:
            - Path=/api/ware/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

4、启动进行测试

  • 浏览器输入 127.0.0.1:8848/nacos/进行测试访问,我们查看到确实已经在nacos中有相应服务了。

image-20221118190207569

5、完成仓库模糊查询功能

我们打开控制台,点击查询,查看发出的url

http://localhost:88/api/ware/wareinfo/list?t=1633696575331&page=1&limit=10&key=

然后根据这个url我们进行仓库模糊查询功能的实现。

  • WareInfoController.java
    @RequestMapping("/list")
    //@RequiresPermissions("ware:wareinfo:list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = wareInfoService.queryPageByCondition(params);

        return R.ok().put("page", page);
    }
  • WareInfoServiceImpl.java(模糊查询)
    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<WareInfoEntity> wrapper = new QueryWrapper<>();
        String key = (String) params.get("key");
        if (!StringUtils.isNullOrEmpty(key)){
            wrapper.eq("id", key).or().like("name", key).or().like("address", key).or().like("areacode", key);
        }

        IPage<WareInfoEntity> page = this.page(
                new Query<WareInfoEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }
  • 设置日志输出级别,方便查看sql语句
logging:
  level:
    com.xmh: debug

6、进行测试

测试成功,控制台中的sql如下:

2022-11-18 19:18:08.476 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage    : ==>  Preparing: SELECT id,address,name,areacode FROM wms_ware_info WHERE (id = ? OR name LIKE ? OR address LIKE ? OR areacode LIKE ?) 
2022-11-18 19:18:08.476 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage    : ==> Parameters: 上海(String), %上海%(String), %上海%(String), %上海%(String)
2022-11-18 19:18:08.477 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage    : <==      Total: 1

6.8.2 查询库存的模糊查询

1、代码实现

image-20221119110510343

  • WareSkuServiceImpl

    /**
         * skuId : 1
         * wareId : 2
         * @param params
         * @return
         */
        @Override
        public PageUtils queryPage(Map<String, Object> params) {
            QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();
            String skuId = (String) params.get("skuId");
            if(!StringUtils.isEmpty(skuId)){
                queryWrapper.eq("sku_id",skuId);
            }
            String wareId = (String) params.get("wareId");
            if(!StringUtils.isEmpty(wareId)){
                queryWrapper.eq("ware_id",wareId);
            }
            IPage<WareSkuEntity> page = this.page(
                    new Query<WareSkuEntity>().getPage(params),
                    queryWrapper
            );
    
            return new PageUtils(page);
        }
    
    

    这个模糊查询比较简单,自动生成的方法不需要重新修改,只需要在方法实现类中添加一些查询即可。

6.8.3 采购需求的模糊查询

1、PurchaseDetailController

 /**
     * 列表
     */
    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = purchaseDetailService.queryPage(params);

        return R.ok().put("page", page);
    }

2、PurchaseDetailServiceImpl

@Service("purchaseDetailService")
public class PurchaseDetailServiceImpl extends ServiceImpl<PurchaseDetailDao, PurchaseDetailEntity> implements PurchaseDetailService {

    /**
     * status: 0,//状态
     * wareId: 1,//仓库id
     *
     * @param params
     * @return
     */
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<>();

        //key是模糊检索的字段
        String key = (String) params.get("key");
        if (!StringUtils.isEmpty(key)) {
            //purchase_id  sku_id
            queryWrapper.and(w -> {
                w.eq("purchase_id", key).or().eq("sku_id", key);
            });
        }

        String status = (String) params.get("status");
        if (!StringUtils.isEmpty(status)) {
            //purchase_id  sku_id
            queryWrapper.eq("status", status);
        }


        String wareId = (String) params.get("wareId");
        if (!StringUtils.isEmpty(wareId)) {
            //purchase_id  sku_id
            queryWrapper.eq("ware_id", wareId);
        }


        IPage<PurchaseDetailEntity> page = this.page(
                new Query<PurchaseDetailEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

}

6.8.5 合并采购流程

1、思路梳理

image-20221119140314713

采购单可以通过采购需求进行创建,一种是指定某个采购人员(在系统管理-管理员中创建),一种是不指定,直接将多个采购需求自动合并成采购单。

image-20221119140640822

image-20221119140703049

6.8.6 查询未领取的采购单

image-20221118202447160

image-20221119141106405

1、PurchaseController

///ware/purchase/unreceive/list
    /**
     * 查询未领取的采购单
     */
    @RequestMapping("/unreceive/list")
    public R unreceivelist(@RequestParam Map<String, Object> params){
        PageUtils page = purchaseService.queryPageUnreceivePurchase(params);

        return R.ok().put("page", page);
    }

2、新建常量枚举类constant.WareConstant

public class WareConstant {
    /** 采购单状态枚举 */
    public enum PurchaseStatusEnum{
        CREATED(0,"新建"),ASSIGNED(1,"已分配"),
        RECEIVE(2,"已领取"),FINISH(3,"已完成"),
        HASERROR(4,"有异常");

        private int code;
        private String msg;

        PurchaseStatusEnum(int code, String msg){
            this.code = code;
            this.msg = msg;
        }

        public int getCode(){
            return code;
        }

        public String getMsg(){
            return msg;
        }
    }

    /** 采购需求枚举 */
    public enum  PurchaseDetailStatusEnum{
        CREATED(0,"新建"),ASSIGNED(1,"已分配"),
        BUYING(2,"正在采购"),FINISH(3,"已完成"),
        HASERROR(4,"采购失败");
        private int code;
        private String msg;

        PurchaseDetailStatusEnum(int code,String msg){
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
            return code;
        }

        public String getMsg() {
            return msg;
        }
    }
}

3、PurchaseServiceImpl

    @Override
    public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
//        IPage<PurchaseEntity> page = this.page(
//                new Query<PurchaseEntity>().getPage(params),
//                new QueryWrapper<PurchaseEntity>().eq("status",0).eq("status",1)
//        );

        QueryWrapper<PurchaseEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("status",WareConstant.PurchaseStatusEnum.CREATED.getCode())
                .or()
                .eq("status",WareConstant.PurchaseStatusEnum.ASSIGNED.getCode());
        IPage<PurchaseEntity> page = this.page(
                new Query<PurchaseEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }

4、测试

image-20221119141802990

测试成功

6.8.7 合并采购需求

image-20221118204657733

可以选择需要合并的采购需求,合并到整单,也可以不选择整单id,自动创建新的采购单。

1、新建MergerVo

@Data
public class MergerVo {
    private Long purchaseId; //整单id
    private List<Long> items; //合并项集合
}

2、思路梳理

分配,就是修改【采购需求】里对应的【采购单id、采购需求状态】,即purchase_detail表

并且不能重复分配采购需求给不同的采购单,如果还没去采购,或者采购失败,就可以修改

  • PurchaseController
    @PostMapping("/merge")
    //@RequiresPermissions("ware:purchase:list")
    public R merge(@RequestBody MergeVo mergeVo){
        purchaseService.mergePurchase(mergeVo);
        return R.ok();
    }

  • PurchaseServiceImpl
@Transactional
    @Override
    public void mergePurchase(MergeVo mergeVo) {
        //获取采购单id
        Long purchaseId = mergeVo.getPurchaseId();
        //如果采购id为null 说明没选择采购单
        if(purchaseId == null){
            //没有选择就新建采购单
            PurchaseEntity purchaseEntity = new PurchaseEntity();
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
            this.save(purchaseEntity);
            purchaseId = purchaseEntity.getId();
        }

        //合并采购需求   (多个采购需求的集合 items[])
        List<Long> items = mergeVo.getItems();

        Long  finalPurchaseId = purchaseId;

        // PurchaseDetailEntity    采购需求
        List<PurchaseDetailEntity> list = detailService.getBaseMapper().selectBatchIds(items).stream().filter(entity -> {
            //如果还没去采购,或者采购失败,就可以修改    <= 4
            return entity.getStatus() < WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()
                    || entity.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode();
        }).map(entity -> {
            //修改状态,以及采购id
            entity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
            entity.setPurchaseId(finalPurchaseId);
            return entity;
        }).collect(Collectors.toList());

        detailService.updateBatchById(list);

        //设置创建时间和更新时间
        PurchaseEntity purchaseEntity1 = new PurchaseEntity();
        purchaseEntity1.setId(purchaseId);
        purchaseEntity1.setCreateTime(new Date());
        purchaseEntity1.setUpdateTime(new Date());
        this.updateById(purchaseEntity1);

    }

在修改测试后,发现时间如下图对这样,我们可以重新设置下时间。

image-20221118220741900

在配置文件中对json进行格式化

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

测试成功:

image-20221118230233292

6.8.8 领取采购单

1、思路梳理

采购单分配给了采购人员,采购人员在手机端领取采购单,此时的采购单应该为新建已分配状态,在采购人员领取后采购单的状态变为已领取采购需求的状态变为正在采购

image-20221119143037477

我们可以使用自己的接口调试工具进行测试。本次调试使用apifox.

image-20221118224023522

2、PurchaseController

    /**
     * 领取采购单/ware/purchase/received
     */
    @PostMapping("/received")
    //@RequiresPermissions("ware:purchase:list")
    public R received(@RequestBody List<Long> ids){
        purchaseService.received(ids);
        return R.ok();
    }

3、PurchaseServiceImpl

 @Transactional
    @Override
    public void received(List<Long> ids) {
        //没有采购需求直接返回,否则会破坏采购单
        if(ids == null || ids.size() == 0){
            return ;
        }

        List<PurchaseEntity> collect = this.getBaseMapper().selectBatchIds(ids).stream().filter(entity -> {
            //确保采购单的状态是新建或者已分配
            return entity.getStatus() <= WareConstant.PurchaseStatusEnum.ASSIGNED.getCode();
        }).map(entity -> {
            //修改采购单的状态为已领取
            entity.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
            return  entity;
        }).collect(Collectors.toList());
        this.updateBatchById(collect);


        //修改该采购单下的所有采购需求的状态为正在采购
        UpdateWrapper<PurchaseDetailEntity> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("purchase_id",ids);
        PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
        purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
        detailService.update(purchaseDetailEntity,updateWrapper);
    }

4、测试

image-20221118230210331

image-20221118230223572

测试成功。

6.8.9 完成采购

image-20221119150017412

1、完成采购的步骤:

  • 判断所有采购需求的状态,采购需求全部完成时,采购单状态才为完成
  • 采购项完成的时候,增加库存(调用远程获取skuName)
  • 加上分页插件

2、新建PurchaseItemDoneVo

@Data
public class PurchaseItemDoneVo {
    private Long itemId;
    private Integer status;
    private String reason;
}

PurchaseDoneVo

@Data
public class PurchaseDoneVo {
    private Long id;
    private List<PurchaseItemDoneVo> items;
}

3、PurchaseController

/**
 * 完成采购
 */
@PostMapping("/done")
//@RequiresPermissions("ware:purchase:list")
public R received(@RequestBody PurchaseDoneVo vo){
    purchaseService.done(vo);
    return R.ok();
}

4、PurchaseServiceImpl

    @Autowired
    private WareSkuService wareSkuService;

    @Autowired
    private ProductFeignService feignService;


@Override
    public void done(PurchaseDoneVo vo) {

        //1.根据前端发过来的信息,更新采购需求的状态
        List<PurchaseItemDoneVo> items = vo.getItems();
        ArrayList<PurchaseDetailEntity> updateList = new ArrayList<>();
        boolean flag = true;

        for (PurchaseItemDoneVo item:items) {
            Long detailId = item.getItemId();
            PurchaseDetailEntity detailEntity = detailService.getById(detailId);
            detailEntity.setStatus(item.getStatus());
            //采购需求失败
            if (item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()){
                flag = false;
            }else{
                //3.根据采购需求的状态,更新库存
                /**
                 * sku_id,sku_num,ware_id,
                 * sku_id,ware_id,stock sku_name(调用远程服务获取),stock_locked(先获取已经有的库存,在加上新购买的数量)
                 *
                 */
                String skuName = "";
                try{

                    R info = feignService.info(detailEntity.getSkuId());
                    if(info.getCode() == 0){
                      Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
                        skuName = (String) data.get("skuName");
                    }
                }catch (Exception e){

                }
                //更新库存
                wareSkuService.addStock(detailEntity.getSkuId(),detailEntity.getWareId(),skuName,detailEntity.getSkuNum());
            }
            updateList.add(detailEntity);

        }
        //保存采购需求
        detailService.updateBatchById(updateList);

        //2.根据采购需求的状态,更新采购单的状态
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setId(vo.getId());
        purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatusEnum.FINISH.getCode() : WareConstant.PurchaseStatusEnum.HASERROR.getCode());
        this.updateById(purchaseEntity);
    }

5、新建feign.ProductFeignService接口,用来远程获取skuName

  • ProductFeignService
@FeignClient("gulimall-product")
public interface ProductFeignService {
    @RequestMapping("/product/skuinfo/info/{skuId}")
    R info(@PathVariable("skuId") Long skuId);
}

image-20221119194347346

  • 对于远程调用微服务,路径写法有两种,一种是过网关,则路径前面加api,而去微服务是网关;一种是正常的商品服务,商品服务完整路径。

因为这个方法我们在gulimall-product微服务中已经写过了,所以我们直接使用最简单的方法(直接复制controller中的方法)进行远程调用。

  • 要想使用远程调用,我们还需要在主启动类上加入@EnableFeignClients注解,否则是无法调用远程服务的。

6、新增addStock

对于第四步中的更新库存这个地方,我们使用需要新增一个addStock方法。

  • WareSkuService
void addStock(Long skuId, Long wareId, String skuName, Integer skuNum);
  • WareSkuDao
void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);
  • WareSkuDao.xml
 <update id="addStock">
        UPDATE  `wms_ware_sku` SET  stock = stock+#{skuNum} WHERE sku_id=#{skuId} AND ware_id = #{wareId}
    </update>
  • WareSkuServiceImpl 实现入库操作
  @Autowired
     private  WareSkuDao wareSkuDao;

    @Autowired
   private ProductFeignService productFeignService;
    @Override
    public void addStock(Long skuId, Long wareId, String skuName, Integer skuNum) {
        //1.判断如果还没有这个库存记录 就新增
        List<WareSkuEntity> entities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(entities == null || entities.size() == 0){
        WareSkuEntity skuEntity = new WareSkuEntity();
        skuEntity.setSkuId(skuId);
        skuEntity.setStock(skuNum);
        skuEntity.setWareId(wareId);
        skuEntity.setStockLocked(0);   //设置一个默认锁定库存
        //TODO  远程查询sku的名字,如果失败,整个事务无需回滚,因为就简简单单的因为名字没有获取到,就将整个事务进行回滚的话,太浪费数据库性能了。
        // 使用catch异常
        //TODO 还可以使用什么办法让异常出现后不回滚  高级篇
        try {
            R info = productFeignService.info(skuId);
            Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");

            if(info.getCode() == 0){
                //设置skuName
                skuEntity.setSkuName((String) data.get("skuName"));
            }
        } catch (Exception e) {

        }
        wareSkuDao.insert(skuEntity);
    }else {
            wareSkuDao.addStock(skuId,wareId,skuNum);

        }
    }

7、添加分页插件

  • 这个可以直接复制product中的即可。(我们可以将@EnableTransactionManagement //开启事务 @MapperScan("com.atguigu.gulimall.ware.dao")这两个在主启动类中加上的注解都放到分页插件的类中)
package com.atguigu.gulimall.ware.config;

@Configuration
@EnableTransactionManagement   //开启事务
@MapperScan("com.atguigu.gulimall.ware.dao")
public class MyBatisConfig {
    //引入分页插件   显示页码
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false继续请求,默认false
        paginationInterceptor.setOverflow(true);
        //设置最大单页限制数量,默认500条,-1不受限制
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}

8、进行测试

  • 先新增几个采购需求,然后不进行任何操作,自动合并成一个新的采购单。我们可以从下图中看出采购单id为12.
  • 为采购单进行人员分配,然后让这个采购人员领取采购单。

image-20221120101802639

image-20221119193407442

image-20221119193348780

image-20221119193429005

image-20221119193437232

  • 让采购人员完成采购

image-20221120101647032

image-20221119193736717

  • 完成采购

image-20221119193747340

  • 来到商品库存菜单,查看商品库存,如图,库存已增加

image-20221119193757504

6.8.10 获取spu规格

image-20221120102605111

image-20221120102613466

image-20221120102019388

1、AttrController

@Autowired
private ProductAttrValueService productAttrValueService;

@GetMapping("/base/listforspu/{spuId}")
public R baseListforspu(@PathVariable("spuId") Long spuId){
    List<ProductAttrValueEntity> entityList = productAttrValueService.baseAttrlistForSpu(spuId);

    return R.ok().put("data", entityList);
}

2、ProductAttrValueServiceImpl

@Override
public List<ProductAttrValueEntity> baseAttrlistForSpu(Long spuId) {
    List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
    return entities;
}

3、测试(踩坑及解决办法)

  • 当我们点击规格的时候出现400页面。

image-20221119210252370

  • 解决办法如下图:

image-20221119211451776

image-20221119211531842

image-20221119211551584

  • 再次进行测试,测试成功。

6.8.11 修改商品规格

当我们点击规格后,不仅会回显原来的规格参数,我们可能还需要队某些规格参数进行修改。

image-20221119211645746

1、AttrController

@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId, @RequestBody List<ProductAttrValueEntity> entities){
    productAttrValueService.updateSpuAttr(spuId, entities);

    return R.ok();
}

2、ProductAttrValueServiceImpl

因为修改的时候,有新增有修改有删除。 所以就先把spuId对应的所有属性都删了,再新增.

    @Override
    public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
        //1、删除这个spuId对应的所有属性
        this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
        //2、新增回去
        for (ProductAttrValueEntity entity : entities){
            entity.setSpuId(spuId);
        }
        this.saveBatch(entities);
    }

3、测试

image-20221119213046874

修改成功。

7 分布式基础总结

image-20221120105330482


posted @ 2022-11-20 11:38  hxld  阅读(336)  评论(0编辑  收藏  举报