项目简介

项目背景

电商模式

  • 市面上有5种常见的电商模式:B2B、B2C、C2B、C2C、O2O;
    1. B2B模式:B2B(Business to Business),是指商家与商家建立的商业关系。如:阿里巴巴
    2. B2C模式:B2C(Business to Consumer),就是我们经常看到的供应商直接把商品卖给用户、即商对客模式,也就是通常说的商业零售,直接面向消费者销售产品和服务。如:京东、天猫、苏宁
    3. C2B模式:C2B(Consumer to Business),即消费者对企业。先有消费者需求产生,而后有企业生成,即现有消费者提出需求,后有生产企业按照需求组织生产
    4. C2C模式:C2C(Consumer to Consumer),即客户对客户,客户之间自己把东西放到网上去卖。如:淘宝、咸鱼
    5. O2O模式:O2O(Online to Offline),即将线下上午的机会与互联网结合在了一起,让互联网成为线下交易的前台。线上快速支付,线下优质服务。如:饿了么,美团,京东到家

谷粒商城

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

项目架构图

微服务架构图

微服务划分图

项目技术&特色

  • 前后端分离开发,并开发基于vue的后台管理系统
  • SpringCloud全新的解决方案
  • 应用监控、限流、网关、熔断降级等分布式方案
  • 透彻讲解分布式事务、分布式锁等分布式系统的难点
  • 分析高并发场景的编码方式,线程池,异步编排等使用
  • 压力测试与性能哟花
  • 各种集群技术的区别以及使用
  • CI/CD使用

项目前置要求

分布式基础概念

微服务

  • 微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务功能来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理
  • 简言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行

集群&分布式&节点

  • 集群是个物理形态,分布式是个工作方式
  • 只要是一堆机器,就可以叫集群,但他们是不是一起协同操作,这个就不清楚了

    《分布式系统原理与范型》定义:
    “分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像是单个相关系统”
    分布式系统(distributed system)是建立在网络之上的软件系统

  • 分布式是指将不同的业务分布在不同的地方
  • 集群是指将几台服务器集中在一起,实现同一业务

    例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群。每一个小的业务,比如用户系统,访问压力大的时候,一台服务器是不够的。我们就应该将用户系统部署到多个服务器,也就是每一个业务系统也可以做集群化

  • 分布式中的每一个节点都可以做集群。而集群并不一定是分布式的。
    • 节点:集群中的一个服务器

远程调用

  • 在分布式系统中,各个服务可能处于不同主机,但是服务间不可避免的需要相互调用,我们称为远程调用
  • SpringCloud中使用HTTP+JSON的方式完成远程调用

负载均衡

  • 分布式系统中,A服务需要调用B服务,B服务在多台机器中都存在,A调用任意一个服务器均可完成功能
  • 为了使每一个服务器都不要太忙或者太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性
  • 常见的负载均衡算法
    • 轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环
    • 最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下,可以考虑选择这种方式
    • 散列:根据请求源的IP的散列(hash)来选择要转发的服务器,这种方式可以一定程度上保持特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采用这种方式

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

  • A服务调用B服务,A服务并不知道B服务当前在哪几台服务器有,哪些是正常的,哪些服务已经下线。解决这个问题,可以引入注册中心
  • 如果某些服务下线,我们其他人可以实时感知到其他服务的状态,从而避免调用不可用的服务

配置中心

  • 配置中心用来集中管理微服务的配置信息
  • 每个服务最终都由大量的配置,并且每个服务都可能部署在多台机器上。我们经常徐亚变更配置,我们可以让每个服务在配置中心来获取自己的配置

服务熔断&服务降级

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

  • 服务熔断

    • 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据
  • 服务降级
    • 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。
      • 降级:某些业务不处理,或者简单处理(抛异常、返回NULL、调用Mock数、调用Fallback处理逻辑)

API网关

  • 在微服务架构中,API Gateway作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡、服务自动熔断、灰度发布、统一认证、日记统计等丰富的功能,帮助我们劫很多API管理难题

项目环境搭建

安装虚拟机

  • 我这里是VMWare + CentOS7

安装docker

docker安装mysql

  • 拉取镜像,这里使用的是5.7版本,尽量不要用8.0版本,不然后面可能会出现各种奇奇怪怪的问题

    1
    docker pull mysql:5.7
  • 创建实例并启动

    1
    2
    3
    4
    5
    6
    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/log:/var/log/mysql:将日志文件夹挂载到主机
    • -v /mydata/mysql/data:/var/lib/mysql:将数据文件夹挂载到主机
    • -v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
    • -e MYSQL_ROOT_PASSWORD=root:初始化root用户的密码
  • MySQL配置,编辑配置文件,插入以下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    1
    docker restart mysql
  • 使用Navicat远程连接MySQL

我们可以使用命令让mysql开机自启

1
docker update mysql --restart=always

docker安装redis

  • 拉取镜像
    1
    docker pull redis
  • 创建实例并启动
    1
    2
    3
    4
    5
    6
    7
    8
    // 事先创建好配置目录和文件夹
    mkdir -p /mydata/redis/conf
    touch /mydata/redis/conf/redis.conf

    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
  • 使用远程工具连接(我这里用的RDM)

我们可以使用命令让redis开机自启

1
docker update redis --restart=always

开发环境统一

  • maven配置阿里云镜像
    1
    2
    3
    4
    5
    6
    7
    8
    <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>
  • IDEA安装lombokmybatisx插件
  • Vscode安装开发必备插件

    • Vetur — 语法高亮、智能感知、Emmet 等
    • 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 Snippetshtml快速自动补全
    • Open in browser — 浏览器快速打开
    • Live Server — 以内嵌服务器方式打开
    • Chinese (Simplified) Language Pack for Visual Studio Code — 中文语言包
  • 安装配置Git,站内教程如下

创建项目微服务

  • 在GitHub上新建一个仓库—GuliMall
  • 然后在IDEA中新建项目,基于git创建
  • 暂时先创建5个微服务:商品服务(product)、仓储服务(ware)、订单服务(order)、优惠券服务(coupon)、用户服务(member)
    • 注意勾选SpringWeb和Openfeign这两个依赖
    • 包名:com.cyborg2077.gulimail.product/ware/order..
    • 模块名:gulimail-product/ware/order..
  • 在GuliMall的gitignore文件中新增以下内容,这些文件不需要纳入版本控制
    1
    2
    3
    4
    5
    6
    **/mvnw
    **/mvnw.cmd
    **/.mvn
    **/target/
    **/.gitignore
    .idea
  • 同时在GuliMall目录下新建pom.xml,作为5个微服务的聚合服务,内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cyborg2077.gulimall</groupId>
    <artifactId>gulimall</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall</name>
    <description>聚合服务</description>
    <packaging>pom</packaging>

    <modules>
    <module>gulimall-coupon</module>
    <module>gulimall-member</module>
    <module>gulimall-order</module>
    <module>gulimall-product</module>
    <module>gulimall-ware</module>
    </modules>

    </project>

初始化数据库

  • docker中启动mysql,使用Navicat连接,之后开始初始化数据库
    • 每一个微服务都对应着一张表
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      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 '退款信息';
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      251
      252
      253
      254
      255
      256
      257
      258
      259
      260
      261
      262
      263
      264
      265
      266
      267
      268
      269
      270
      271
      272
      273
      274
      275
      276
      277
      278
      279
      280
      281
      282
      283
      284
      285
      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信息介绍';
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      251
      252
      253
      254
      255
      256
      257
      258
      259
      260
      261
      262
      263
      264
      265
      266
      267
      268
      269
      270
      271
      272
      273
      274
      275
      276
      277
      278
      279
      280
      281
      282
      283
      284
      285
      286
      287
      288
      289
      290
      291
      292
      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积分设置';
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      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 '会员统计信息';
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      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 '商品库存';

快速开发

  • 这里使用renren-fast来帮助我们快速开发
    • 开发文档:https://www.renren.io/guide
    • Git仓库:https://gitee.com/renrenio/renren-fast
    • 项目说明
    • 具有如下特点
      友好的代码结构及注释,便于阅读及二次开发
      实现前后端分离,通过token进行数据交互,前端再也不用关注后端技术
      灵活的权限控制,可控制到页面或按钮,满足绝大部分的权限需求
      页面交互使用Vue2.x,极大的提高了开发效率
      完善的代码生成机制,可在线生成entity、xml、dao、service、vue、sql代码,减少70%以上的开发任务
      引入quartz定时任务,可动态完成任务的添加、修改、删除、暂停、恢复及日志查看等功能
      引入API模板,根据token作为登录令牌,极大的方便了APP接口开发
      引入Hibernate Validator校验框架,轻松实现后端校验
      引入云存储服务,已支持:七牛云、阿里云、腾讯云等
      引入swagger文档支持,方便编写API接口文档

搭建后台管理系统

  1. 我们先将renren-fast克隆到本地
    1
    git clone https://gitee.com/renrenio/renren-fast.git
  2. 删除renren-fast中的.git文件(默认是隐藏的,修改电脑设置显示隐藏文件)
  3. 然后将整个文件夹拷贝进父工程,同时修改父工程的pom.xml,将renren-fast加入到父工程
    1
    2
    3
    4
    5
    6
    7
    8
       <modules>
    <module>gulimall-coupon</module>
    <module>gulimall-member</module>
    <module>gulimall-order</module>
    <module>gulimall-product</module>
    <module>gulimall-ware</module>
    + <module>renren-fast</module>
    </modules>
  4. 修改renren-fast中的pom.xml
    • 因为这里会报错'parent.relativePath' of POM io.renren:renren-fast:3.0.0 (D:\Workspace\backend\GuliMall\renren-fast\pom.xml) points at com.cyborg2077.gulimall:gulimall instead of org.springframework.boot:spring-boot-starter-parent, please verify your project structure
    • relativePath的默认值是..pom.xml,会从本地路径中获取parentpom,但是我们使用springboot的时候,不需要从父工程中获取,而是从本地仓库中获取,所以我们要配置relativePath为空值
      1
      2
      3
      4
      5
      6
         <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.6.6</version>
      + <relativePath></relativePath>
      </parent>
  5. 创建数据库gulimall_admin,运行renren-fast提供好的sql语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    -- 菜单
    CREATE TABLE `sys_menu` (
    `menu_id` bigint NOT NULL AUTO_INCREMENT,
    `parent_id` bigint COMMENT '父菜单ID,一级菜单为0',
    `name` varchar(50) COMMENT '菜单名称',
    `url` varchar(200) COMMENT '菜单URL',
    `perms` varchar(500) COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
    `type` int COMMENT '类型 0:目录 1:菜单 2:按钮',
    `icon` varchar(50) COMMENT '菜单图标',
    `order_num` int COMMENT '排序',
    PRIMARY KEY (`menu_id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='菜单管理';

    -- 系统用户
    CREATE TABLE `sys_user` (
    `user_id` bigint NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL COMMENT '用户名',
    `password` varchar(100) COMMENT '密码',
    `salt` varchar(20) COMMENT '盐',
    `email` varchar(100) COMMENT '邮箱',
    `mobile` varchar(100) COMMENT '手机号',
    `status` tinyint COMMENT '状态 0:禁用 1:正常',
    `create_user_id` bigint(20) COMMENT '创建者ID',
    `create_time` datetime COMMENT '创建时间',
    PRIMARY KEY (`user_id`),
    UNIQUE INDEX (`username`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='系统用户';

    -- 系统用户Token
    CREATE TABLE `sys_user_token` (
    `user_id` bigint(20) NOT NULL,
    `token` varchar(100) NOT NULL COMMENT 'token',
    `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`user_id`),
    UNIQUE KEY `token` (`token`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='系统用户Token';

    -- 系统验证码
    CREATE TABLE `sys_captcha` (
    `uuid` char(36) NOT NULL COMMENT 'uuid',
    `code` varchar(6) NOT NULL COMMENT '验证码',
    `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
    PRIMARY KEY (`uuid`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='系统验证码';

    -- 角色
    CREATE TABLE `sys_role` (
    `role_id` bigint NOT NULL AUTO_INCREMENT,
    `role_name` varchar(100) COMMENT '角色名称',
    `remark` varchar(100) COMMENT '备注',
    `create_user_id` bigint(20) COMMENT '创建者ID',
    `create_time` datetime COMMENT '创建时间',
    PRIMARY KEY (`role_id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='角色';

    -- 用户与角色对应关系
    CREATE TABLE `sys_user_role` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `user_id` bigint COMMENT '用户ID',
    `role_id` bigint COMMENT '角色ID',
    PRIMARY KEY (`id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='用户与角色对应关系';

    -- 角色与菜单对应关系
    CREATE TABLE `sys_role_menu` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `role_id` bigint COMMENT '角色ID',
    `menu_id` bigint COMMENT '菜单ID',
    PRIMARY KEY (`id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='角色与菜单对应关系';

    -- 系统配置信息
    CREATE TABLE `sys_config` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `param_key` varchar(50) COMMENT 'key',
    `param_value` varchar(2000) COMMENT 'value',
    `status` tinyint DEFAULT 1 COMMENT '状态 0:隐藏 1:显示',
    `remark` varchar(500) COMMENT '备注',
    PRIMARY KEY (`id`),
    UNIQUE INDEX (`param_key`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='系统配置信息表';


    -- 系统日志
    CREATE TABLE `sys_log` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `username` varchar(50) COMMENT '用户名',
    `operation` varchar(50) COMMENT '用户操作',
    `method` varchar(200) COMMENT '请求方法',
    `params` varchar(5000) COMMENT '请求参数',
    `time` bigint NOT NULL COMMENT '执行时长(毫秒)',
    `ip` varchar(64) COMMENT 'IP地址',
    `create_date` datetime COMMENT '创建时间',
    PRIMARY KEY (`id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='系统日志';


    -- 文件上传
    CREATE TABLE `sys_oss` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `url` varchar(200) COMMENT 'URL地址',
    `create_date` datetime COMMENT '创建时间',
    PRIMARY KEY (`id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='文件上传';


    -- 定时任务
    CREATE TABLE `schedule_job` (
    `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id',
    `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
    `params` varchar(2000) DEFAULT NULL COMMENT '参数',
    `cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式',
    `status` tinyint(4) DEFAULT NULL COMMENT '任务状态 0:正常 1:暂停',
    `remark` varchar(255) DEFAULT NULL COMMENT '备注',
    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`job_id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='定时任务';

    -- 定时任务日志
    CREATE TABLE `schedule_job_log` (
    `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
    `job_id` bigint(20) NOT NULL COMMENT '任务id',
    `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
    `params` varchar(2000) DEFAULT NULL COMMENT '参数',
    `status` tinyint(4) NOT NULL COMMENT '任务状态 0:成功 1:失败',
    `error` varchar(2000) DEFAULT NULL COMMENT '失败信息',
    `times` int(11) NOT NULL COMMENT '耗时(单位:毫秒)',
    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`log_id`),
    KEY `job_id` (`job_id`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='定时任务日志';



    -- 用户表
    CREATE TABLE `tb_user` (
    `user_id` bigint NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL COMMENT '用户名',
    `mobile` varchar(20) NOT NULL COMMENT '手机号',
    `password` varchar(64) COMMENT '密码',
    `create_time` datetime COMMENT '创建时间',
    PRIMARY KEY (`user_id`),
    UNIQUE INDEX (`username`)
    ) ENGINE=`InnoDB` DEFAULT CHARACTER SET utf8mb4 COMMENT='用户';


    -- 初始数据
    INSERT INTO `sys_user` (`user_id`, `username`, `password`, `salt`, `email`, `mobile`, `status`, `create_user_id`, `create_time`) VALUES ('1', 'admin', '9ec9750e709431dad22365cabc5c625482e574c74adaebba7dd02f1129e4ce1d', 'YzcmCZNvbXocrsz9dm8e', 'root@renren.io', '13612345678', '1', '1', '2016-11-11 11:11:11');

    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (1, 0, '系统管理', NULL, NULL, 0, 'system', 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (2, 1, '管理员列表', 'sys/user', NULL, 1, 'admin', 1);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (3, 1, '角色管理', 'sys/role', NULL, 1, 'role', 2);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (4, 1, '菜单管理', 'sys/menu', NULL, 1, 'menu', 3);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (5, 1, 'SQL监控', 'http://localhost:8080/renren-fast/druid/sql.html', NULL, 1, 'sql', 4);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (6, 1, '定时任务', 'job/schedule', NULL, 1, 'job', 5);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (7, 6, '查看', NULL, 'sys:schedule:list,sys:schedule:info', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (8, 6, '新增', NULL, 'sys:schedule:save', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (9, 6, '修改', NULL, 'sys:schedule:update', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (10, 6, '删除', NULL, 'sys:schedule:delete', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (11, 6, '暂停', NULL, 'sys:schedule:pause', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (12, 6, '恢复', NULL, 'sys:schedule:resume', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (13, 6, '立即执行', NULL, 'sys:schedule:run', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (14, 6, '日志列表', NULL, 'sys:schedule:log', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (15, 2, '查看', NULL, 'sys:user:list,sys:user:info', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (16, 2, '新增', NULL, 'sys:user:save,sys:role:select', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (17, 2, '修改', NULL, 'sys:user:update,sys:role:select', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (18, 2, '删除', NULL, 'sys:user:delete', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (19, 3, '查看', NULL, 'sys:role:list,sys:role:info', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (20, 3, '新增', NULL, 'sys:role:save,sys:menu:list', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (21, 3, '修改', NULL, 'sys:role:update,sys:menu:list', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (22, 3, '删除', NULL, 'sys:role:delete', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (23, 4, '查看', NULL, 'sys:menu:list,sys:menu:info', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (24, 4, '新增', NULL, 'sys:menu:save,sys:menu:select', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (25, 4, '修改', NULL, 'sys:menu:update,sys:menu:select', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (26, 4, '删除', NULL, 'sys:menu:delete', 2, NULL, 0);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (27, 1, '参数管理', 'sys/config', 'sys:config:list,sys:config:info,sys:config:save,sys:config:update,sys:config:delete', 1, 'config', 6);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (29, 1, '系统日志', 'sys/log', 'sys:log:list', 1, 'log', 7);
    INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`) VALUES (30, 1, '文件上传', 'oss/oss', 'sys:oss:all', 1, 'oss', 6);

    INSERT INTO `sys_config` (`param_key`, `param_value`, `status`, `remark`) VALUES ('CLOUD_STORAGE_CONFIG_KEY', '{\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"aliyunDomain\":\"\",\"aliyunEndPoint\":\"\",\"aliyunPrefix\":\"\",\"qcloudBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qiniuAccessKey\":\"NrgMfABZxWLo5B-YYSjoE8-AZ1EISdi1Z3ubLOeZ\",\"qiniuBucketName\":\"ios-app\",\"qiniuDomain\":\"http://7xqbwh.dl1.z0.glb.clouddn.com\",\"qiniuPrefix\":\"upload\",\"qiniuSecretKey\":\"uIwJHevMRWU0VLxFvgy0tAcOdGqasdtVlJkdy6vV\",\"type\":1}', '0', '云存储配置信息');
    INSERT INTO `schedule_job` (`bean_name`, `params`, `cron_expression`, `status`, `remark`, `create_time`) VALUES ('testTask', 'renren', '0 0/30 * * * ?', '0', '参数测试', now());


    -- 账号:13612345678 密码:admin
    INSERT INTO `tb_user` (`username`, `mobile`, `password`, `create_time`) VALUES ('mark', '13612345678', '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918', '2017-03-23 22:37:41');

    -- quartz自带表结构
    CREATE TABLE QRTZ_JOB_DETAILS(
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
    REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_CRON_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(120) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_SIMPROP_TRIGGERS
    (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_BLOB_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_CALENDARS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_FIRED_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_SCHEDULER_STATE (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE QRTZ_LOCKS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME))
    ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
    CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

    CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
    CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
    CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

    CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
    CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
    CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
    CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
  6. 修改renren-fast的配置文件、配置数据库,将数据库连接信息修改为自己的配置

    • 查看application.yml,发现当前环境为dev
      1
      2
      3
      4
      spring:
      ## 环境 dev|test|prod
      profiles:
      active: dev
    • 那么去application-dev.yml中修改配置,将数据库连接信息改为自己的配置就好了
  7. 然后就可以启动后端项目了,访问地址:http://localhost:8080/renren-fast/

    • 看到如下json数据就说明启动成功
      1
      {"msg":"invalid token","code":401}
  8. 将renren-fast-vue克隆到本地

    1
    git clone https://gitee.com/renrenio/renren-fast-vue.git
  9. 前端联调
    • 官网下载node,安装后检查是否安装成功
      1
      2
      node -v
      // 出现版本号则安装成功
    • 配置淘宝镜像源
      1
      npm config set registry http://registry.npm.taobao.org/
  10. 使用VSCode打开renren-fast-vue,执行命令npm install加载项目依赖,然后执行命令npm run dev运行项目,访问http://localhost:8001/#/login, 即可看到管理界面
  11. 默认账号和密码是admin,同时点击验证码,也会在后端重新生成
  12. 成功登录后的效果如下图,

逆向工程搭建&使用

  1. 克隆renren-generator到本地,该项目可以根据数据库表来生成对应的实体类,Controller层,Service层等代码
    1
    git clone https://gitee.com/renrenio/renren-generator.git
    • 项目说明
      • renren-generator是人人开源项目的代码生成器,可在线生成entity、xml、dao、service、html、js、sql代码,减少70%以上的开发任务
    • 本地部署
      • 通过git下载源码
      • 修改application.yml,更新MySQL账号和密码、数据库名称
      • Eclipse、IDEA运行RenrenApplication.java,则可启动项目
      • 项目访问路径:http://localhost
  2. 删除renren-generator中的.git文件,并将其复制到父工程中
  3. 修改父工程的pom.xml文件,添加renren-generator到父工程
    1
    2
    3
    4
    5
    6
    7
    8
    9
       <modules>
    <module>gulimall-coupon</module>
    <module>gulimall-member</module>
    <module>gulimall-order</module>
    <module>gulimall-product</module>
    <module>gulimall-ware</module>
    <module>renren-fast</module>
    + <module>renren-generator</module>
    </modules>
  4. 修改renren-generator的pom.xml文件
    1
    2
    3
    4
    5
    6
       <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    + <relativePath></relativePath>
    </parent>
  5. 修改代码生成器配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #代码生成器,配置信息

    mainPath=com.cyborg2077
    #包名
    package=com.cyborg.gulimall
    moduleName=product
    #作者
    author=cyborg2077
    #Email
    email=cyderpunk2077@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=pms_
  6. 修改renren-generator的配置文件application.yml,配置自己的数据源,这里先将数据库设置为gulimall_pms,先为商品服务生成对应的CRUD代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ## mysql
    spring:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  7. 修改代码生成器模板
    • 修改src/main/resources/template/Controller.java.vm
    • 删除导入shiro的语句
    • 同时将@RequiresPermissions都注释掉,此处的注释是Apache Shiro提供的,我们以后要用Spring Security
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
         package ${package}.${moduleName}.controller;

      import java.util.Arrays;
      import java.util.Map;

      - import org.apache.shiro.authz.annotation.RequiresPermissions;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;

      import ${package}.${moduleName}.entity.${className}Entity;
      import ${package}.${moduleName}.service.${className}Service;
      import ${mainPath}.common.utils.PageUtils;
      import ${mainPath}.common.utils.R;



      /**
      * ${comments}
      *
      * @author ${author}
      * @email ${email}
      * @date ${datetime}
      */
      @RestController
      @RequestMapping("${moduleName}/${pathName}")
      public class ${className}Controller {
      @Autowired
      private ${className}Service ${classname}Service;

      /**
      * 列表
      */
      @RequestMapping("/list")
      + //@RequiresPermissions("${moduleName}:${pathName}:list")
      public R list(@RequestParam Map<String, Object> params){
      PageUtils page = ${classname}Service.queryPage(params);

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


      /**
      * 信息
      */
      @RequestMapping("/info/{${pk.attrname}}")
      + //@RequiresPermissions("${moduleName}:${pathName}:info")
      public R info(@PathVariable("${pk.attrname}") ${pk.attrType} ${pk.attrname}){
      ${className}Entity ${classname} = ${classname}Service.getById(${pk.attrname});

      return R.ok().put("${classname}", ${classname});
      }

      /**
      * 保存
      */
      @RequestMapping("/save")
      + //@RequiresPermissions("${moduleName}:${pathName}:save")
      public R save(@RequestBody ${className}Entity ${classname}){
      ${classname}Service.save(${classname});

      return R.ok();
      }

      /**
      * 修改
      */
      @RequestMapping("/update")
      + //@RequiresPermissions("${moduleName}:${pathName}:update")
      public R update(@RequestBody ${className}Entity ${classname}){
      ${classname}Service.updateById(${classname});

      return R.ok();
      }

      /**
      * 删除
      */
      @RequestMapping("/delete")
      + //@RequiresPermissions("${moduleName}:${pathName}:delete")
      public R delete(@RequestBody ${pk.attrType}[] ${pk.attrname}s){
      ${classname}Service.removeByIds(Arrays.asList(${pk.attrname}s));

      return R.ok();
      }

      }
  8. 运行项目,访问localhost:80,选中所有表(注意这里配置的数据库是gulimall_pms),生成代码
  9. 解压生成的代码文件,将main文件夹复制到gulimall-product/src目录下
    • 其中/resources/src/views/modules/product,是生成的前端文件,暂时用不到,可以直接删掉
  10. 解决生成的代码报错
    • 生成的代码中需要导入一些其他的工具类等
      • 包括MP、lombok和一些人人开源自带的工具类等
    • 那么我们这里就新建一个gulimall-common模块,每一个微服务公共的类和公共的依赖都放在common里,然后让其他微服务依赖common就好了
    • 在父工程中加入gulimall-common模块
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <modules>
      <module>gulimall-coupon</module>
      <module>gulimall-member</module>
      <module>gulimall-order</module>
      <module>gulimall-product</module>
      <module>gulimall-ware</module>
      <module>renren-fast</module>
      <module>renren-generator</module>
      + <module>gulimall-common</module>
      </modules>
    • 在gulimall-common的pom.xml引入公共依赖
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      <dependencies>
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.3.1</version>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.4</version>
      </dependency>
      <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpcore</artifactId>
      <version>4.4.16</version>
      </dependency>
      <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
      </dependency>
      <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
      </dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
      </dependency>
      </dependencies>
    • 之后再gulimall-common中按照要求创建对应的包,然后去gulimall-fast中找到对应的类,复制到common中对应的包下,包括PageUtils、Query、R、Constant、xss、AliyunGroup、QiniuGroup、QcloudGroup等
    • 如果还缺少其他的依赖或者类,继续导入直至不报错为止

测试基本的CRUD功能

  1. 在gulimall-product中新建application.yml配置文件,用于配置数据库连接信息和MP的一些设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    server:
    port: 10000

    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_pms?useSSL=false
    username: root
    password: root

    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml ## 配置mapper映射文件的位置,当前类路径下的mapper下任意文件下的xml结尾的文件
    global-config:
    db-config:
    id-type: auto ## 主键自增
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ## 打印日志
  2. 编写测试类并进行测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class GulimallProductApplicationTests {
    @Autowired
    private BrandService brandService;

    @Test
    public void saveTest() {
    BrandEntity brandEntity = new BrandEntity();
    brandEntity.setName("测试品牌");
    brandEntity.setDescript("这里是测试品牌的描述..");
    brandService.save(brandEntity);
    System.out.printf("保存成功");
    }

    @Test
    public void updateTest() {
    BrandEntity brandEntity = new BrandEntity();
    brandEntity.setBrandId(1L);
    brandEntity.setDescript("这里修改了品牌描述..");
    brandService.updateById(brandEntity);
    System.out.println("修改成功");
    }

    @Test
    public void queryTest() {
    LambdaQueryWrapper<BrandEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(BrandEntity::getName, "测试品牌");
    List<BrandEntity> list = brandService.list(queryWrapper);
    if (list.isEmpty()) {
    System.out.println("未查询到数据");
    } else {
    list.forEach((item) -> System.out.println(item));
    }
    }

    @Test
    public void deleteTest() {
    LambdaQueryWrapper<BrandEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(BrandEntity::getName, "测试品牌");
    brandService.remove(queryWrapper);
    System.out.println("删除成功");
    }
    }

生成其他微服务的基本CRUD

  • 首先给每个微服务配置端口,按字母顺序coupon/member/order/product/ware分别为7000/8000/9000/10000/11000
  • 这里先以gulimall-order为例生成基本的CRUD
  1. 修改代码生成器的properties配置文件,将模块名和表前缀修改为符合gulimall-order
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mainPath=com.cyborg2077
    #包名
    package=com.cyborg2077.gulimall
    moduleName=order ## 主要修改模块名
    #作者
    author=cyborg2077
    #Email
    email=cyderpunk2077@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=oms_ ## 和表前缀
  2. 修改代码生成器的yml配置文件,修改连接的数据库为gulimall-order对应的数据库
    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  3. 启动代码生成器,生成代码,复制到gulimall-order中,并在gulimall-order中引入gulimall-common依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.cyborg2077.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </dependency>
  4. 创建gulimall-order的配置文件application.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_oms?useSSL=false
    username: root
    password: root

    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml ## 配置mapper映射文件的位置,当前类路径下的mapper下任意文件下的xml结尾的文件
    global-config:
    db-config:
    id-type: auto ## 主键自增
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ## 打印日志
  5. 在启动类上添加@MapperScan注解,启动,访问http://localhost:10001/order/order/list, 可以看到返回的空数据,则配置成功
    1
    2
    3
    4
    5
    6
    7
    8
    9
       @SpringBootApplication
    + @MapperScan("com.cyborg2077.gulimall.order.dao")
    public class GulimallOrderApplication {

    public static void main(String[] args) {
    SpringApplication.run(GulimallOrderApplication.class, args);
    }

    }

分布式组件

Nacos注册中心

Nacos下载安装启动

  • Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件,相比于Eureka,功能更加丰富,在国内受欢迎程度较高
  • 在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
  • 下载好了之后,将文件解压到非中文路径下的任意目录,目录说明:
    • bin:启动脚本
    • conf:配置文件
  • Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
    • 如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件application.properties中的server.port
  • Nacos的启动非常简单,进入bin目录,打开cmd窗口执行以下命令即可
    1
    startup.cmd -m standalone

    注意:双击运行是集群部署,这里命令行是单点部署standalone

  • 之后在浏览器访问http://localhost:8848/nacos 即可,默认的登录账号和密码都是nacos

将微服务注册到Nacos

这里先以gulimall-coupon为例

  1. 在gulimall-coupon的pom.xml中引入Nacos依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.1.0.RELEASE</version>
    </dependency>
  2. 在gulimall-coupon的application.yml中配置Nacos地址和服务名
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        server:
    port: 7000
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_sms?useSSL=false
    username: root
    password: root
    + cloud:
    + nacos:
    + discovery:
    + server-addr: 127.0.0.1:8848
    + application:
    + name: gulimall-coupon

    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml ## 配置mapper映射文件的位置,当前类路径下的mapper下任意文件下的xml结尾的文件
    global-config:
    db-config:
    id-type: auto ## 主键自增
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ## 打印日志
  3. 在gulimall-coupon的主启动类上使用@EnableDiscoveryClient注解,开启服务注册发现功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        @SpringBootApplication
    + @EnableDiscoveryClient
    @MapperScan("com.cyborg2077.gulimall.coupon.dao")
    public class GulimallCouponApplication {

    public static void main(String[] args) {
    SpringApplication.run(GulimallCouponApplication.class, args);
    }

    }
  4. 启动gulimall-coupon,观察Nacos服务列表中是否已经注册了该服务
  • 那么我们现在同样的步骤对其他几个微服务做同样的配置

OpenFeign远程调用

  • Feign是一个声明式的HTTP客户端,他的目的就是让远程调用更加简单。给远程服务发的是HTTP请求
  • 会员服务要想远程调用优惠券服务,只需要在会员服务中引入openfeign依赖,这样会员服务就有了远程调用其他服务的能力。具体步骤如下
    1. gulimall-member中引入依赖(之前创建这5个微服务的时候已经引入了)
      1
      2
      3
      4
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
    2. 在gulimall-member的主启动类上添加@EnableFeignClients注解
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
          @SpringBootApplication
      @EnableDiscoveryClient
      + @EnableFeignClients
      @MapperScan("com.cyborg2077.gulimall.member.dao")
      public class GulimallMemberApplication {

      public static void main(String[] args) {
      SpringApplication.run(GulimallMemberApplication.class, args);
      }

      }
    3. member服务调用coupon服务
      • gulimall-coupon的CouponController中添加测试方法
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
            @RestController
        @RequestMapping("coupon/coupon")
        public class CouponController {
        @Autowired
        private CouponService couponService;

        + @RequestMapping("/member/list")
        + public R memberCoupons() {
        + // 模拟去数据库中查询用户的优惠券
        + CouponEntity couponEntity = new CouponEntity();
        + // 这里是假数据,假装查到了一个满9999减1的优惠券
        + couponEntity.setCouponName("满9999减1");
        + // 所有的返回值都是R类型,R其实是一个Map
        + return R.ok().put("coupons",Arrays.asList(couponEntity));
        + }

        /**
        * 列表
        */
        @RequestMapping("/list")
        public R list(@RequestParam Map<String, Object> params){
        PageUtils page = couponService.queryPage(params);

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


        /**
        * 信息
        */
        @RequestMapping("/info/{id}")
        public R info(@PathVariable("id") Long id){
        CouponEntity coupon = couponService.getById(id);

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

        /**
        * 保存
        */
        @RequestMapping("/save")
        public R save(@RequestBody CouponEntity coupon){
        couponService.save(coupon);

        return R.ok();
        }

        /**
        * 修改
        */
        @RequestMapping("/update")
        public R update(@RequestBody CouponEntity coupon){
        couponService.updateById(coupon);

        return R.ok();
        }

        /**
        * 删除
        */
        @RequestMapping("/delete")
        public R delete(@RequestBody Long[] ids){
        couponService.removeByIds(Arrays.asList(ids));

        return R.ok();
        }

        }
      • gulimall-member下新建包com.cyborg2077.gulimall.member.feign,这个包存放各种远程调用接口
      • 现在在这个包下新建CouponFeignService接口,接口的声明与我们刚刚创建的测试方法完全一致,然后feign就会根据远程调用的信息来发送HTTP请求,例如这里就是调用gulimall-coupon服务的/coupon/coupon/member/list路径下的方法
        1
        2
        3
        4
        5
        @FeignClient("gulimall-coupon") // 需要与Nacos注册中心的服务名保持一致,也就是application.yml中配置的服务名
        public interface CouponFeignService {
        @RequestMapping("/coupon/coupon/member/list") // 记得补全路径名
        R memberCoupons();
        }
      • 在member的MemberController中写一个测试方法
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        @Autowired
        private CouponFeignService couponFeignService;

        @RequestMapping("/coupons")
        public R coupons(){
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setNickname("Cyborg2077");
        R memberCoupons = couponFeignService.memberCoupons();
        return R.ok().put("member", memberEntity).put("coupons", memberCoupons.get("coupons"));
        }
      • 启动gulimall-member和gulimall-coupon,访问http://localhost:8000/member/member/coupons, 可以获取到我们模拟的优惠券数据

Nacos配置中心

  • Nacos除了可以做注册中心,同样还可以做配置中心

统一配置管理

  • 当微服务部署的实例越来越多,达到数十、数百时,诸葛修改微服务配置就会让人抓狂,而且容易出错,所以我们需要一种统一配置管理方案,可以集中管理所有实例的配置
  • Nacos一方面可以将配置集中管理,另一方面可以在配置变更时,及时通知微服务,实现配置的热更新

    注意:只有需要热更新的配置才有放到Nacos管理的必要,基本不会变更的一些配置,还是保存到微服务本地比较好(例如数据库连接配置等)

  1. common中添加依赖nacos配置中心
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.1.0.RELEASE</version>
    </dependency>
  2. coupons项目中创建/src/main/resources/bootstrap.yml,优先级比application.yml
    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    application:
    name: gulimall-coupon ## 服务名称
    cloud:
    nacos:
    config:
    server-addr: 127.0.0.1:8848 ## nacos地址
    file-extension: yaml ## 文件后缀名
  3. 在nacos中新建配置

  4. 在controller中编写测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Value("${coupon.user.name}")
    private String name;

    @Value("${coupon.user.age}")
    private int age;

    @RequestMapping("/nacos")
    public R nacos(){
    return R.ok().put("name",name).put("age",age);
    }
  5. 启动服务,访问http://localhost:7000/coupon/coupon/nacos 进行测试
    1
    2
    3
    4
    5
    6
    {
    "msg": "success",
    "code": 0,
    "name": "kyle vvv",
    "age": 13
    }
  6. 正确获取到我们的配置信息,但是现在还不是热部署,当我们在配置中心修改配置之后,需要重启服务才能加载新配置
    • 为了解决这个问题,我们要在对应的Controller上添加@RefreshScope注解
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
          @RefreshScope
      + @RestController
      @RequestMapping("coupon/coupon")
      public class CouponController {
      @Autowired
      private CouponService couponService;

      @Value("${coupon.user.name}")
      private String name;

      @Value("${coupon.user.age}")
      private int age;

      @RequestMapping("/nacos")
      public R nacos(){
      return R.ok().put("name",name).put("age",age);
      }
      }
  7. 重启服务,继续测试,当我们在配置中心随意修改配置时,不用再重启服务,直接刷新页面,就可以看到新的配置信息

环境隔离

  • Nacos提供了namespace来实现环境隔离功能

    • nacos中可以有多个namespace
    • namespace下可以由group、service等
    • 不同的namespace之间相互隔离,例如不同的namespace的服务互相不可见
  • 创建namespace

    • 默认情况下,所有的service、data、group都是在同一个namespace,名为public
    • 我们点击命名空间 -> 新建命名空间 -> 填写表单,可以创建一个新的namespace
  • 给微服务配置namespace

    • 给微服务配置namespace只能通过修改配置来实现
    • 例如修改coupon服务下的bootstrap.yml来声明namespace
      1
      2
      3
      4
      5
      6
      7
      8
      9
          spring:
      application:
      name: gulimall-coupon
      cloud:
      nacos:
      config:
      server-addr: 127.0.0.1:8848
      file-extension: yaml
      + namespace: f5a97d8e-2598-4475-adda-d9e66f06acea
  • 在每个namespace中,又可以有不同的group,默认都是在DEFAULT_GROUP组中。

    • 在配置文件中指明group
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
          spring:
      application:
      name: gulimall-coupon
      cloud:
      nacos:
      config:
      server-addr: 127.0.0.1:8848
      file-extension: yaml
      namespace: b834847d-dbb2-4f39-bb75-5f325943192b ## 配置命名空间
      + group: prod ## 配置group
  • 那么每个微服务创建自己的namespace来做服务隔离(coupon、ware、product等),每个namespace中创建不同的组来做环境隔离(dev、beta、prod等)

加载多配置集

  • 先来看一下我们现在的application.yml中配置了多少东西
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    server:
    port: 7000
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_sms?useSSL=false
    username: root
    password: root
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    application:
    name: gulimall-coupon

    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml ## 配置mapper映射文件的位置,当前类路径下的mapper下任意文件下的xml结尾的文件
    global-config:
    db-config:
    id-type: auto ## 主键自增
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ## 打印日志
  • 因为我们目前把所有的配置信息都写到了这里,所以看起来又多又乱,不好维护
  • Nacos支持我们读取多个配置文件,那么我们现在要做的就是将这些配置拆分开来

    1
    2
    3
    4
    5
    6
    7
    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml ## 配置mapper映射文件的位置,当前类路径下的mapper下任意文件下的xml结尾的文件
    global-config:
    db-config:
    id-type: auto ## 主键自增
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ## 打印日志
    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.96.128:3306/gulimall_sms?useSSL=false
    username: root
    password: root
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 7000

    spring:
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    application:
    name: gulimall-coupon

  • 然后在bootstrap.yml中加载这些配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    spring:
    application:
    name: gulimall-coupon
    cloud:
    nacos:
    config:
    server-addr: 127.0.0.1:8848
    file-extension: yaml
    namespace: b834847d-dbb2-4f39-bb75-5f325943192b ## 配置命名空间
    group: prod ## 配置group
    + ext-config[0]:
    + data-id: mybatis.yaml
    + group: dev
    + refresh: true
    + ext-config[1]:
    + data-id: datasource.yaml
    + group: dev
    + refresh: true
    + ext-config[2]:
    + data-id: other.yaml
    + group: dev
    + refresh: true
  • 我们现在将原来的application.yml中的内容全部注释掉,重启服务,功能不受影响

GateWay服务网关

为什么需要网关

  • Gateway网关是我们服务的守门神,是所有微服务的统一入口
  • 网关的核心功能特性
    1. 请求路由
    2. 权限控制
    3. 限流
  • 架构图如下
  • 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫路由。当然路由的目标服务有多个时,还需要做负载均衡
  • 权限控制:网关作为微服务的入口,需要校验用户是否有请求资格,如果没有则拦截
  • 限流:当请求量过高时,在网关中按照微服务能够接受的速度来放行请求,避免服务压力过大
  • 在SpringCloud中网关的实现包括两种
    1. gateway
    2. zuul
  • Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能

快速入门

这里就结合我们的实际需要来说

  1. 创建网关微服务gulimall-gateway,引入nacos和gateway依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-nacos-config</artifactId>
    <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
  2. 在主启动类上添加@EnableDiscoveryClient注解,将网关也注册为一个服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    +   @EnableDiscoveryClient
    @SpringBootApplication
    public class GulimallGatewayApplication {

    public static void main(String[] args) {
    SpringApplication.run(GulimallGatewayApplication.class, args);
    }

    }
  3. 在nacos中给网关服务创建命名空间gulimall-gateway
  4. 在gulimall-gateway命名空间下新增配置gulimall-gateway.yaml(当然你也可以把配置写在本地的application.yml中),内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    server:
    port: 88
    spring:
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    gateway:
    routes:
    - id: baidu_route ## 路由的名字,唯一即可
    uri: http://www.baidu.com ## 匹配后提供服务的路由地址
    predicates: ## 断言规则
    - Query=url,coupon ## 如果url参数等于coupon,则符合断言,转到uri
    application:
    name: gulimall-gateway

  5. 创建bootstrap.yml,并写入以下内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    cloud:
    nacos:
    config:
    server-addr: 127.0.0.1:8848
    namespace: 760a376a-5430-465d-a927-d6977373d181
    file-extension: yaml
    group: dev
    application:
    name: gulimall-gateway
  • 那么到此为止,我们就可以正常启动网关服务了,在nacos中也可以看到我们注册的服务,访问http://localhost:88/?url=coupon, 可以跳转到百度

前端基础

ES6基础

简介

  • ECMAScript 6.0(以下简称 ES6,ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商协会,英文名称是 European Computer Manufacturers Association)通过 ECMA-262 标准化的脚本程序设计语言)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了,并且从 ECMAScript 6 开始,开始采用年号来做版本。即 ECMAScript 2015,就是 ECMAScript6。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。每年一个新版本

什么是ECMAScript

  • 先来看下前端的发展历程:

    • web1.0 时代:
      • 最初的网页以 HTML 为主,是纯静态的网页。网页是只读的,信息流只能从服务的到客户端单向流通。开发人员也只关心页面的样式和内容即可。
    • web2.0 时代:
      • 1995 年,网景工程师 Brendan Eich 花了 10 天时间设计了 JavaScript 语言。
      • 1996 年,微软发布了 JScript,其实是 JavaScript 的逆向工程实现。
      • 1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。
      • 1997 年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。JavaScript 和 JScript 都是ECMAScript的标准实现者,随后各大浏览器厂商纷纷实现了ECMAScript标准
  • 所以,ECMAScript 是浏览器脚本语言的规范,而各种我们熟知的 js 语言,如 JavaScript 则是规范的具体实现

ES6新特性

  1. let声明变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 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
    3
    4
    // 1. 声明之后不允许改变
    // 2. 一但声明必须初始化,否则会报错
    const a = 1;
    a = 3; //Uncaught TypeError: Assignment to constant variable.
  3. 解构表达式

    • 数组解构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      let arr = [1,2,3];
      //以前我们想获取其中的值,只能通过角标。
      let a = arr[0];
      let b = arr[1];
      let c = arr[2];

      //ES6 可以这样:
      const [x,y,z] = arr;// x,y,z 将与 arr 中的每个位置对应来取值
      // 然后打印
      console.log(x,y,z)
    • 对象解构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      const person = {
      name: "Kyle",
      age: 19,
      language: ['java', 'python', 'matlab']
      }
      // 解构表达式获取值,将 person 里面每一个属性和左边对应赋值
      const { name, age, language } = person;
      // 等价于下面的写法
      const name = person.name;
      const age = person.age;
      const language = person.language;
      // 打印输出
      console.log(name, age, language);

      // 如果想要将 name 的值赋值给其他变量,可以如下书写,first_name 是新的变量名
      const { name: first_name, age, language } = person;
      console.log(first_name, age, language);
  4. 字符串扩展
    • ES6为字符串扩展提供了几个新的API
      • includes():返回布尔值,表示是否找到了参数字符串
      • startsWith():返回布尔值,鸟事参数字符串是否在原字符串的头部
      • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部
        1
        2
        3
        4
        5
        let str = "hello.vue";
        console.log(str.startsWith("hello")); //true
        console.log(str.endsWith("vue")); //true
        console.log(str.includes("ll")); //true
        console.log(str.startsWith("hello")); //true
        • 字符串模板
      • 字符串模板相当于加强版的字符串,用着重号`,除了作为普通字符串,还可以用来定义多行字符串,也可以在字符串中加入变量和表达式
        1. 定义多行字符串
          1
          2
          3
          4
          5
          let str = `
          <div>
          <span>hello vue</span>
          <div>`;
          console.log(str);
        2. 在字符串中插入变量和表达式,变量名写在${}中,${}中可以放JS表达式
          1
          2
          3
          4
          let name = "张三";
          let age = "19";
          let info = `我叫${name},今年${age}岁` // 我叫张三,今年19岁
          console.log(info);
        3. 字符串中调用函数
          1
          2
          3
          4
          function fun(){
          return "这是一个函数";
          }
          console.log(`hey bro,${fun()}`); //hey bro,这是一个函数
  5. 函数优化
    • 函数参数默认值
      • 在ES6以前,我们无法给一个函数参数设置默认值,只能采用变通写法
        1
        2
        3
        4
        5
        6
        7
        // 返回 a + b 的结果
        function fun(a, b) {
        b = b || 1;
        return a + b;
        }
        // 只传递一个参数
        console.log(fun(3)); // 3 + 1 = 4
      • 现在我们可以这么写:直接给参数写上默认值,没传参数则自动使用默认值
        1
        2
        3
        4
        function fun(a, b = 2) {
        return a * b;
        }
        console.log(fun(3)); // 2 * 3 = 6
    • 不定参数
      • 补丁参数用来表示不确定参数个数,形如...变量名,由...加上一个具名参数标识符组成。具名参数只能放到参数列表的最后,并且有且只能有一个不定参数
        1
        2
        3
        4
        5
        function fun(...values) {
        console.log(values.length);
        }
        fun(1, 2); // 2
        fun(1, 2, 3); // 3
    • 箭头函数
      • 类似于Java8里的lambda表达式,简化函数书写
        • 单个参数
          1
          2
          3
          4
          5
          6
          // 以前声明一个方法
          function fun(obj) {
          console.log(obj);
          }
          // 可以简写为
          let fun = obj => console.log(obj);
        • 多个参数
          1
          2
          3
          4
          5
          6
          7
          // 以前的写法
          function multiply(a, b) {
          return a * b;
          }
          // 可以简写为
          let multiply = (a, b) => a * b;
          console.log(multiply(4, 5)); // 20
        • 如果方法体不止一行,可以用{}括起来
          1
          2
          3
          4
          5
          let fun = (a, b) => {
          let c = a * b
          return c * a;
          };
          console.log(fun(4, 5)); // 80
    • 箭头函数结合解构表达式
      • 需求:声明一个对象,hello方法需要对象的个别属性
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        let person = {
        name: "Kyle",
        age: 19,
        language: ['java', 'python', 'matlab']
        }

        // 以前的写法
        function hello1(person) {
        console.log("hello, " + person.name);
        }
        // 现在的写法
        let hello2 = ({name}) => console.log("hello, " + name);

        hello1(person); // hello, Kyle
        hello2(person); // hello, Kyle
  6. 对象优化
    • 新增的API
      • ES6给Object拓展了许多新的方法,如:
        • keys(obj):获取对象的所有key组成的数组
        • values(obj):获取对象所有的value组成的数组
        • entries(obj):获取对象的所有key和value形成的二维数组。格式[[k1,v1],[k2,v2],...]
        • assign(dest, …src):将多个src对象的值拷贝到dest中
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          let person = {
          name: "Kyle",
          age: 19,
          language: ['java', 'python', 'matlab']
          }
          console.log(Object.keys(person)); // ['name', 'age', 'language']
          console.log(Object.values(person)); // ['Kyle', 19, ['java', 'python', 'matlab']]
          console.log(Object.entries(person)); // [['name', 'Kyle'], ['age', 19], ['language', Array(3)]

          let target = { a: 1 };
          let source1 = { b: 1 };
          let source2 = { c: 1 };
          Object.assign(target, source1, source2);
          console.log(target); // {a: 1, b: 1, c: 1}
    • 声明对象简写
      • 传统方式
        1
        2
        3
        4
        let name = "Kyle";
        let age = 19;
        let person1 = { name: name, age: age };
        console.log(person1); // {name: 'Kyle', age: 19}
      • ES6:属性名与属性值变量名一致,可以省略
        1
        2
        3
        4
        let name = "Kyle";
        let age = 19;
        let person2 = {name, age};
        console.log(person2); // {name: 'Kyle', age: 19}
    • 对象的函数属性简写
      • 传统写法
        1
        2
        3
        4
        5
        6
        let person = {
        name: "jack",
        eat: function (food) {
        console.log(this.name + "在吃" + food);
        }
        }
      • 箭头函数版:这里拿不到 this
        1
        2
        3
        4
        let person = {
        name: "jack",
        eat2: food => console.log(person.name + "在吃" + food),
        }
      • 简写版:
        1
        2
        3
        4
        5
        6
        7
        let person = {
        name: "jack",
        eat3(food) {
        console.log(this.name + "在吃" + food);
        }
        }
        person.eat("apple");
    • 对象拓展运算符
      • 拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // 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:map()接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回
      1
      2
      3
      let arr = ['1', '3', '5', '7'];
      arr = arr.map((item) => item * 2);
      console.log(arr); // [2, 6, 10, 14]
    • reduce:reduce为数组中的每一个元素依次执行回调函数,不包括数组张被删除或从未被复制的元素,接收4个参数
      1. 初始值(或者上一次回调函数的返回值)
      2. 当前元素值
      3. 当前索引
      4. 调用reduce的数组
      • callback(执行数组中每个值的函数,包括4个参数)
        1. previousValue(上一次调用回调返回的值,或者是提供的初始值)
        2. currentValue(数组中当前被处理的元素)
        3. index(当前元素在数组中的索引)
        4. array(调用reduce的数组)
      • 示例
        1
        2
        3
        let arr = ['1', '3', '5', '7'];
        let result = arr.reduce((a, b) => a * b);
        console.log(result); // 1 * 3 * 5 * 7 = 105
  8. Promise
    • 在JavaScript中,所有的代码都是单线程执行。由于这个缺陷,导致JavaScript的所有网络操作,浏览器时间,都必须是异步执行。
    • 异步执行可以用回调函数实现,一旦有一连串的ajax请求,那么后面的请求都依赖前面的请求结果,就需要层层嵌套。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      if (true) {
      //TODO
      if (true) {
      //TODO
      if (true) {

      } else {

      }
      } else {

      }
      } else {

      }
    • 这种缩进和层层嵌套的方式,非常容易造成上下文代码混乱,我们不得不非常小心的处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度会进一步加剧
      • 需求:用户登录,冰战士该用户的各科成绩。
        1. 查询用户,查询成功说明可以登录
        2. 根据登录用户id,查询科目
        3. 根据科目的查询结果,获取成绩
      • 分析:后台应该提供3个接口,一个提供用户查询接口,一个提供科目的接口,一个提供各科成绩的接口,为了渲染方便,最好响应json数据。这里就用json文件提供假数据,模拟后台接口了
      1
      2
      3
      4
      5
      {
      "id": 1,
      "name": "zhangsan",
      "password": "123456"
      }
      1
      2
      3
      4
      {
      "id": 10,
      "name": "chinese"
      }
      1
      2
      3
      4
      {
      "id": 100,
      "score": 90
      }
    • 回调函数嵌套的噩梦:层层嵌套
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      $.ajax({
      url: "user.json",
      success(data) {
      console.log("查询用户", data);
      $.ajax({
      url: `user_corse_${data.id}.json`,
      success(data) {
      console.log("查询到课程", data);
      $.ajax({
      url: `corse_score_${data.id}.json`,
      success(data) {
      console.log("查询到分数", data)
      },
      error(err) {
      console.log("出现异常:", err)
      }
      })
      },
      error(err) {
      console.log("出现异常:", err);
      }
      })
      },
      error(err) {
      console.log("出现异常:", err);
      }
      })
    • 我们可以用Promise解决以上问题
      • Promise语法
        1
        2
        3
        4
        5
        6
        7
        const promise = new Promise(function (resolve, reject) {
        if (/*异步调用成功*/) {
        resolve(value); // 调用reslove,代表Promise将返回成功的结果
        } else {
        reject(error); //调用reject,代表Promise会返回失败结果
        }
        });
      • 使用箭头函数可以简写为
        1
        2
        3
        4
        5
        6
        7
        const promise = new Promise((resolve, reject) => {
        if (/*异步调用成功*/) {
        resolve(value); // 调用reslove,代表Promise将返回成功的结果
        } else {
        reject(error); //调用reject,代表Promise会返回失败结果
        }
        });
      • 这样在Promise中就封装了一段异步执行的结果
      • 处理异步结果:如果我们想要等待异步执行完成,做一些事情,我们可以通过promise的then方法来实现,如果想要处理promise异步执行失败的事件,还可以跟上catch
        1
        2
        3
        4
        5
        promise.then(function (value) {
        // 异步执行成功后的回调
        }).catch(function (error) {
        // 异步执行失败后的回调
        })
      • Promise改造以前的嵌套方式
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        const promise = new Promise((resolve, reject) => {
        $.ajax({
        url: "user.json",
        success(data) {
        console.log("查询用户", data);
        resolve(data.id);
        },
        error(err) {
        reject(err);
        }
        })
        }).then((userId) => {
        return new Promise((resolve, reject) => {
        $.ajax({
        url: `user_corse_${userId}.json`,
        success(data) {
        console.log("查询到课程", data);
        resolve(data.id);
        },
        error(err) {
        reject(err);
        }
        })
        })
        }).then((corseId) => {
        $.ajax({
        url: `corse_score_${corseId}.json`,
        success(data) {
        console.log("查询到分数:", data);
        },
        error(err) {
        console.log("出现异常:", err);
        }
        })
        });
    • 但是现在代码不太优雅,那这里优化一下:将promise封装成通用方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      let get = function (url, data) {
      return new Promise((resolve, reject) => {
      $.ajax({
      url: url,
      type: "GET",
      data: data,
      success(result) {
      resolve(result);
      },
      error(err) {
      reject(err);
      }
      })
      });
      }
      // 使用封装的get方法,实现查询分数
      get("user.json").then((result) => {
      console.log("查询用户:", result);
      return get(`user_corse_${result.id}.json`);
      }).then((result) => {
      console.log("查询到课程:", result);
      return get(`corse_score_${result.id}.json`);
      }).then((result) => {
      console.log("查询到成绩:", result);
      }).catch((err) => {
      console.log("出现异常:", err);
      })
  9. 模块化
    • 什么是模块化:模块化就是把代码进行拆分,方便重复利用。类似Java中的包,要使用一个包,就必须先导包。而JS中没有包的概念,换来的是模块
    • 模块功能主要由两个命令构成:expoetimport
      • export命令用于规定模块的对外接口
      • import命令用于导入其他模块提供的功能

Vue基础