文章目录
- 前言
- 一、项目环境搭建
- 1.1、安装virtualbox以及vagrant
- 1.2、Docker安装MySQL与Redis
- 1.3、前后端开发工具统一配置
- 1.4、Git工具安装与配置
- 1.5、Gitee创建仓库与IDEA导入
- 1.6、构建微服务模块
- 1.7、编写.gitignore文件(忽略上传gitee文件配置)
- 1.8、数据库初始化
- 1.9、逆向工程(生成代码)
- 1.9.1、renren-fast快速构建后端管理页面及服务
- 1.9.2、renren-generator快速生成微服务业务代码(营销服务、用户服务、订单服务、商品服务、库存服务)
- 最终生成模块展示
- 示例:在gulimall-product模块中添加逆向工程代码
- 二、搭建分布式环境
- 前提:gulimall-common进行springcloud alibaba版本控制依赖
- 2.1、集成Nacos注册中心
- gulimall-common统一引入注册发现依赖
- 启动Nacos-server服务
- 所有模块服务集成注册发现
- 2.2、集成Openfeign组件
- 2.3、集成Nacos配置中心
- 2.4、集成网关服务
- 三、商品服务
- 3.1、三级分类API
- 三级分类介绍
- 3.1.1、查询—产品分类递归树型结构数据获取(后端服务)
- 3.1.2、配置管理服务renren-fast网关路由(后端+前端配置)
- 后端服务(网关动态路由、集成跨域服务)
- 前端服务
- 测试
- 3.1.3、产品分类查询(前端)
- 前端产品分类页面创建
- 配置商品服务gulimall-product的动态路由地址
- 前端产品分类接口实现(集成3.1.1查询接口)
- 3.1.4、删除单个分类(后端+前端实现)
- 效果展示及思路
- 后端代码(逻辑删除)
- 前端代码
- 3.1.5、新增单个分类(后端+前端实现)
- 效果展示及思路
- 后端代码
- 前端代码
- 3.1.6、编辑单个分类(后端+前端实现)
前言
目前博主正在学习谷粒商城项目中,正在不断更新中…
所有博客文件目录索引:博客目录索引(持续更新)
一、项目环境搭建
1.1、安装virtualbox以及vagrant
在virtualbox创建centos7环境使用的是vagrant虚拟运行环境管理工具来进行构建的,可见该篇博客:虚拟运行环境管理工具Vagrant详细使用教程。
效果如下:
使用vagrant构建的centos7环境,默认的root没有设置密码,我们需要进行设置:
sudo passwd root
快速构建Docker环境可见我之前博客(环境安装部分):快速使用Docker部署MySQL、Redis、Nginx
1.2、Docker安装MySQL与Redis
Docker构建MySQL:
在/mydata/mysql/conf
目录下创建mysqld.cnf
文件:
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
#log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
接着去拉取镜像并启动:
docker pull library/mysql:5.7.36
docker run -p 3306:3306 --name mysql \
-v /etc/localtime:/etc/localtime \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql/mysql.conf.d \
-e MYSQL_ROOT_PASSWORD=root \
-d library/mysql:5.7.36
# 使用mysql容器中的命令行
docker exec -it mysql /bin/bash
# 使用MySQL命令打开客户端:
mysql -uroot -proot --default-character-set=utf8
# 接着创建一个账户,该账号所有ip都能够访问
# 用户名:root 密码:root
grant all privileges on *.* to 'root' @'%' identified by 'root';
flush privileges;
Docker构建Redis:
docker pull redis:5
在mydata/redis目录下创建redis.conf
:文件如下
链接:https://pan.baidu.com/s/1tT3SWnnL0OylWA5So_V9Mw
提取码:9xko
其中配置已经修改了,包含如下:
# 配置文件中修改说明
#bind 127.0.0.1 要注释掉,允许对外连接
appendonly yes:开启持久化
# daemonize yes 一定要要注释,注释,注释!!!
启动redis镜像,关键是设置其中的密码:
docker pull redis:5
# –restart=always 总是开机启动
# –log是日志方面的
# –appendonly yes 开启redis 持久化
# –requirepass xxx 设置密码
docker run --log-opt max-size=100m --log-opt max-file=2 \
-p 6379:6379 --name redis \
-v /etc/localtime:/etc/localtime \
-v /mydata/redis/redis.conf:/etc/redis/redis.conf \
-v /mydata/redis/data:/data \
-d redis:5 redis-server /etc/redis/redis.conf \
--appendonly yes \
--requirepass 123456
# 进入redis-cli测试
docker exec -it redis redis-cli
1.3、前后端开发工具统一配置
后端
后端开发工具:idea
maven配置:Maven快速配置—配置文件
idea插件安装:lombok、mybatisx、Gitee、jrebel(Java代码修改无需重启)插件和ResetfulTool(一套 RESTful 服务开发辅助工具集)
- JRebel插件使用详解(激活教程)
前端
前端:vscode
安装插件:
Vetur —— 语法高亮、智能感知、Emmet 等
包含格式化功能, Alt+Shift+F (格式化全文),Ctrl+K Ctrl+F(格式化选中代码,两个 Ctrl
需要同时按着)
EsLint —— 语法纠错
Auto Close Tag —— 自动闭合 HTML/XML 标签
Auto Rename Tag —— 自动完成另一侧标签的同步修改
JavaScript(ES6) code snippets — — ES6 语 法 智 能 提 示 以 及 快 速 输 入 , 除 js 外 还 支持.ts,.jsx,.tsx,.html,.vue,省去了配置其支持各种包含 js 代码文件的时间
HTML CSS Support —— 让 html 标签上写 class 智能提示当前项目所支持的样式
HTML Snippets —— html 快速自动补全
Open in browser —— 浏览器快速打开
Live Server —— 以内嵌服务器方式打开
Chinese (Simplified) Language Pack for Visual Studio Code —— 中文语言包
1.4、Git工具安装与配置
Git的下载与配置可见我的博文(详细):Git详细使用指南(含详细命令、实操)
包含添加密钥到码云。
1.5、Gitee创建仓库与IDEA导入
Gitee创建仓库:
仓库名称、初始化仓库以及选择分支模型(生产/开发模型):
IDEA导入仓库:
创建完成如下:
1.6、构建微服务模块
创建模块:商品服务(product)、仓储服务(ware)、订单服务(order)、优惠券服务(coupon)、用户服务(member)
多模块相同组件:Springboot版本2.1.8.RELEASE、SpringCloud版本Greenwich.SR3
1)、引入依赖web、openfeign
2)、每一个服务,包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member)
3)、模块名:gulimall-xx
分组:com.atguigu.gulimall
坐标:gulimall-xxx
包名:com.atguigu.gulimall.xxx
构建完我们所需要的几个模块如下图(单个模块构建过程往下翻有示例):
在当前目录下创建一个pom.xml(从之前模块中移过来),进行统一管理模块:
<?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.atguigu.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>谷粒商城聚合服务</description>
<!-- 不打包程jar包的指定pom -->
<packaging>pom</packaging>
<!-- 统一管理模块 -->
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-coupon</module>
</modules>
</project>
如何导入这个聚合模块呢?右边Maven+号添加这个pom.xml即可:
此时就会出现一个总的管理root:
创建gulimall-product示例(版本说明)
创建商品模块:
组件选择:
- 后来修正为视频一样的版本2.1.8
注意一下springboot与springcloud的版本:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
1.7、编写.gitignore文件(忽略上传gitee文件配置)
上面模块都构建好了之后,我们可以看到当前的版本控制管理有134个文件没有被管理:
部分文件如.idea文件下的、target目录等我们在代码完成之后无需上传代码仓库,此时就需要在当前目录下的.gitignore中进行配置:
一些配置信息如下:
# 所有子模块下的.gitignore
**/.gitignore
# 工程目录下文件
**/mvnw
**/mvnw.cmd
**/.mvn
# idea配置文件
.idea
# 编译目录
**/target
*.iml
# 热编译工具
rebel.xml
添加到版本控制操作:
添加版本控制(add):
提交到本地仓库(及push推送)
OK,此时我们看下Gitee仓库状态,所有的代码已经进行了上传:
1.8、数据库初始化
下载安装数据库表设计工具
PowerDesigner16.5:PowerDesigner安装+破解+汉化、PowerDesigner 安装破解
链接:https://pan.baidu.com/s/1HoifsS4ywFjyNO-jyhZy5A
提取码:ihrj
数据库设计源文件
链接:https://pan.baidu.com/s/1Jv3ImyMHQEpay5RzwUK9Ug
提取码:p2on
下面是五个服务的表:
创建数据库与表
对应每一个微服务都操作一个数据库
oms 订单数据库
pms 商品数据库
sms 营销数据库
ums 用户数据库
wms 库存数据库
以商品系统为准,创建utf8mb4字符集的数据库:
依次创建好数据库之后导入预先准备的sql(注意不要走navicat的直接导入可能会出现字段提示乱码问题,我们需要打开某个sql全选后在GUI中复制执行!):
sql文件
链接:https://pan.baidu.com/s/1IE12fJsyS6THG9xug1nmBg
提取码:f4qr
1.9、逆向工程(生成代码)
对于代码生成我们使用的是人人开源的项目:https://gitee.com/renrenio
- renren-fast作为后台管理系统后端服务
- renren-fast-vue作为后台管理系统页面
- renren-generator进行代码生成
1.9.1、renren-fast快速构建后端管理页面及服务
后端系统服务
renren-fast开源地址:https://gitee.com/renrenio/renren-fast
将renren-fast添加到gulimall目录下:
数据库创建``gulimall_admin`,并且导入项目中的db目录下的mysql.sql:
将该模块项目导入到IDEA中,接着我们修改renren-fast中的application.yml文件中的数据库连接地址和用户名与密码:
接着启动项目,启动成功如下图所示:
在网页上进行测试一下:http://localhost:8080/renren-fast/
为什么会有renren-fast后缀?主要是在application.yaml中配置了server.servlet.context-path=/renren-fast
。
前端服务
renren-fast-vue开源地址:https://gitee.com/renrenio/renren-fast-vue
下载克隆下来之后我们进入根目录下输入命令去导入依赖:
- 关于node.js下载与npm包配置可见我的博客:Node.js学习笔记 认识Node.js以及npm使用
npm install
全部添加成功如下图所示:
你可以在config目录下index.js里看到已经配置好了对应我们renren-fast也就是admin后台系统的api地址,我们无需进行改动:
接着去运行命令启动前端服务并进行测试访问下后台管理系统:
npm run dev
启动成功之后我们来访问下该页面:http://localhost:8001/
# 用户名与密码
admin
admin
成功登录后的效果如下所示:
1.9.2、renren-generator快速生成微服务业务代码(营销服务、用户服务、订单服务、商品服务、库存服务)
最终生成模块展示
renren-generator开源地址:https://gitee.com/renrenio/renren-generator
下载克隆下来之后添加到gulimall中
这个项目实际上提供了一个web服务,其能够连接对应的数据库通过页面中勾选指定的数据库来生成预先配置好的后端前端代码。
对应逆向生成代码的操作可见下面示例章节!
五个模块对应我们设置的开放端口为:
微服务模块 端口 数据库表 注释
gulimall-coupon 7000 gulimall_sms 营销数据库 √
gulimall-member 8000 gulimall_ums 用户数据库 √
gulimall-order 9000 gulimall_oms 订单数据库 √
gulimall-product 10000 gulimall_pms 商品数据库 √
gulimall-ware 11000 gulimall_wms 库存数据库 √
初步搭建好的服务模块如下,右边的RestServices包含所有的服务:
当前构建的环境代码:
链接:https://pan.baidu.com/s/1g0E-cbXrhLZPGIg0z83ykA
提取码:ep7s
示例:在gulimall-product模块中添加逆向工程代码
application.yml:修改连接数据库的ip地址与数据库名、用户名与密码
generator.properties:代码生成的配置项
- 这个
mainPath
指的是引入的自定义工具类的包路径,如PageUtils、R等等。
启动该服务:http://localhost:80
进入到网页之后,我们需要进行选中所有的表,然后进行生成代码:注意可能一个数据库会超过10条,那么我们选择一页显示50条,然后统一勾选
生成代码的目录如下:
我们将整个main
目录复制到gulimall-product
工程的src目录下(去除掉resources下的src目录也就是前端代码):
此时我们能够很明显的看到工程目录下java代码文件都是爆红,主要原因就是生成的代码中引入了一些当前工程没有引入的依赖(对于这这类可以生成不同模块的代码我们单独使用一个common包来进行引入一些第三方依赖):
对于生成的代码解决方案:
1、构建gulimall-common模块,引入生成代码所必须的一些工具类。
当前的gulimall-common工程包
链接:https://pan.baidu.com/s/1DZ0vtrAyHTFnHZue2rzTOg
提取码:8mvi
pom.xml需要引入的第三方类:
<dependencies>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- 导入mysql驱动 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
②修改代码生成器中的controller代码模板(去除掉controller部分的shiro权限代码,谷粒商城将会使用的是springsecurity)
2、针对当前模块自定义配置准备
上面两步是针对之后所有逆向生成模块的额外步骤,接着我们来针对某个模块来进行配置,我们重新生成一下product代码放入到product模块中,接着做如下操作:
①pom.xml引入gulimall-common
<!-- 引入公共模块 -->
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
②使用renren-generator来生成对应服务的逆向代码直接复制到src目录下
③在SpringBoot启动器上添加MapperScan包扫描路径
@MapperScan("com.atguigu.gulimall.product.dao")
④编写配置application.yml
,主要数据库源以及mp的配置:
server:
port: 10000
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.3.137:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto # 主键自增
logic-delete-value: 1
logic-not-delete-value: 0
二、搭建分布式环境
前提:gulimall-common进行springcloud alibaba版本控制依赖
根据当前使用的springboot版本、Springcloud版本来选择对应的springcloud alibaba版本:Alibaba-SpringCloud Alibaba 版本说明
对于2.1版本的springboot,我们需要选择2.1.2的springcloud alibaba,在谷粒商城视频里选择的是2.1.0,那我们这也就选择使用2.1.0,将其添加到gulimall-common
模块中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.1、集成Nacos注册中心
gulimall-common统一引入注册发现依赖
由于每一个服务都需要添加到注册中心,那么将nacos注册发现统一集成到common模块当中:
<!-- 服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
启动Nacos-server服务
本地启动nacos服务,下载nacos-server1.1.3:https://github.com/alibaba/nacos/releases?page=4
链接:https://pan.baidu.com/s/12R8-kB6drpWSxmrnJ4gwvQ
提取码:xgct
进入到bin目录,直接双击startup.cmd来进行启动:
nacos访问服务端口默认是在8848端口:http://localhost:8848/nacos/
nacos
nacos
所有模块服务集成注册发现
最终集成的效果:
查看下最终的nacos注册中心:
示例:guli-coupon集成过程
①配置application.yaml
在应用的 /src/main/resources/application.yaml配置文件中配置 Nacos Server 地址以及application.name也就是应用名称(才能够注册上):
spring:
application:
name: gulimall-coupon # 服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
②在启动器上开启服务注册发现注解:
@EnableDiscoveryClient //开启服务发现
启动gulimall-coupon服务:
查看下nacos注册中心是否已经注册上:
2.2、集成Openfeign组件
实战目标:在营销服务中编写一个接口,接着在用户服务中编写一个用户优惠券接口,在这个接口中我们将会进行远程调用营销服务中的接口并进行返回(openfeign完成远程调用)。
①在gulimall-coupon服务中添加一个接口
@RequestMapping("/member/list")
public R memberCoupons() {
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");
return R.ok().put("coupons", Arrays.asList(couponEntity));
}
②在gulimall-member服务中编写远程调用接口
首先需要给gulimall-member服务中集成openfeign并进行开启feign包扫描进行加强:
pom.xml引入:
<!-- 远程调用:openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写对应的feign接口:
package com.atguigu.gulimall.member.feign;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Description:
* @Author: changlu
* @Date: 9:59 PM
*/
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
@RequestMapping("/coupon/coupon/member/list")
public R memberCoupons();
}
在启动器上添加自动包扫描:
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign") //开启feign客户端
最后就是在控制器上添加一个接口其中就包含远程调用:
@Autowired
CouponFeignService couponFeignService;
@RequestMapping("/coupons")
public R test() {
//当前服务的用户
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("长路");
//远程调用获取优惠券
R membercoupons = couponFeignService.memberCoupons();
//响应用户与优惠券信息
return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
}
测试下接口:
2.3、集成Nacos配置中心
介绍
命名空间:可基于环境进行隔离、也可以根据单个微服务来进行隔离。
配置集:指的就是单个命名空间下对应的group分组,场景如:单个服务在双11、618来进行分组隔离。
实战
由于我们的所有微服务都需要使用到配置中心,所以我们需要在gulimall-common模块中进行配置:
<!-- 配置中心来做配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
接着我们就需要在对应需要使用配置中心配置信息的服务模块中添加bootstrap.properties文件:
遵守的规则如下:
- 当前谷粒商城规范:按照每个微服务来创建自己的命名空间,接着使用配置分组来区分环境如dev、test、prod。
- 加载多配置集:对数据源、框架相关的配置来统一拆分成不同的配置文件。
spring.application.name=gulimall-coupon
# nacos配置中心地址(若是只有一条,默认namespace是public, Group是DEFAULT_GROUP,data-id是gulimall-coupon.properties)
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=cf8005b8-b2c3-47cd-b344-18661e5cba70
spring.cloud.nacos.config.group=prod
# 多配置级,将初始配置来进行拆分
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true
对应在nacos管理面板上,我们需要进行添加一个单独服务的命名空间,并且去配置对应的配置文件其中包含有多环境的以及多配置信息文件:
- 多环境:gulimall-coupon.properties。
- 多配置(拆分):数据库、框架、单个微服务的相关配置。
# gulimall-coupon.properties dev
coupon.user.name=changlu-dev
coupon.user.age=29
# gulimall-coupon.properties prod
coupon.user.name=changlu-prod
coupon.user.age=28
# datasource.yml dev
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.3.137:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
# mybatis.yml dev
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto # 主键自增
logic-delete-value: 1
logic-not-delete-value: 0
# other.yml dev
server:
port: 7000
spring:
application:
name: gulimall-coupon # 服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
对于上面多环境配置设置的目的就是我们当前来进行测试,对应测试的代码在controller中:
启动nacos与gulimall-coupon来进行测试dev与pro环境:
提示:上线之后我们可以配到配置中心去,线下写代码就直接配在本地即可(由于我使用了RestServices插件,读取配置中心的话对应的一些端口就需要我们自己去手动修改,同时也会很麻烦)。
2.4、集成网关服务
创建guilimall-gateway模块:
①pom.xml中引入公共模块
<!-- 引入公共模块 -->
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
由于引入了common模块,在common模块中还带了mybatis的依赖,所以需要对其进行排除(在启动器上):
②排除common组件的依赖
//由于引入了gulimall-common,其中包含mp的自动导入数据源,所以这里需要进行排除
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient //开启注册发现
③编写注册中心的配置项
bootstrap.properties:
server.port=88
spring.application.name=gulimall-gateway
# 服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 配置中心
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=6a7b6a2f-871b-46a9-9805-7caf19e65b09
④编写断言匹配url
application.yml:
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
对于其中的gateway断言我们进行了处理,之后我们来对其进行测试:
localhost:88?url=qq
localhost:88?url=baidu
三、商品服务
3.1、三级分类API
三级分类介绍
在一般的电商平台上就包含有三级分类:
在谷粒商城中,三级分类表是属于商品服务数据库中的表:
- 核心字段:parent_cid(父类id)、sort(排序字段)
- 我们的多级分类是根据父类id来进行划分的,一级分类就是为0,对应二级分类的parent_id就是0,同理三级分类的parent_id就是1。
默认在表中是没有数据的,我们需要来进行导入一些初始数据:
将课件中的数据sql代码执行:
3.1.1、查询—产品分类递归树型结构数据获取(后端服务)
完成效果
目标:在gulimall-product模块中编写一个产品三级分类的递归数据结构获取的API接口。
接口:http://localhost:10000/product/category/list/tree
实现过程
主要代码包含gulimall-product模块下:
CategoryController.java
:控制器,对应产品分类的树型分类接口
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 查询出所有的分类以及子分类,以树型结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
}
CategoryEntity
:产品分类实体中添加一个children集合属性,用于封装直接响应给前端,但是注意了这个属性我们需要加上@Field注解表示其不是数据库表中自带的属性,避免在使用mybatisplus查询时出现异常
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
//...
//子分类
@TableField(exist = false) //表示其不存在数据库表
private List<CategoryEntity> children;
}
CategoryService.java
:定义接口方法
public interface CategoryService extends IService<CategoryEntity> {
List<CategoryEntity> listWithTree();
}
CategoryService.java
:树型分类业务代码,其中就有涉及到一个递归处理操作
package com.atguigu.gulimall.product.service.impl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public List<CategoryEntity> listWithTree() {
//1、查询到所有的分类
List<CategoryEntity> entities = baseMapper.selectList(null);
//2、组装成父子的树型结构
List<CategoryEntity> ans = entities.stream()
.filter((menu) -> menu.getParentCid() == 0)
.map((menu) -> {
menu.setChildren(getChildren(menu, entities));
return menu;
})
.sorted((menu1, menu2) -> {
if (menu1.getSort() == null || menu2.getSort() == null) return 0;
return menu1.getSort() - menu2.getSort();
})
.collect(Collectors.toList());
return ans;
}
/**
* 递归处理获取子分类
* @param parent 父分类
* @param all 所有分类
* @return 已经获取到子分类的分类
*/
public List<CategoryEntity> getChildren(CategoryEntity parent, List<CategoryEntity> all) {
List<CategoryEntity> ans = all.stream()
.filter((menu) -> menu.getParentCid().equals(parent.getCatId())) //Long类型比较需要进行equals
.map((menu) -> {
menu.setChildren(getChildren(menu, all));
return menu;
})
.sorted((menu1, menu2) -> {
if (menu1.getSort() == null || menu2.getSort() == null) return 0;
return menu1.getSort() - menu2.getSort();
})
.collect(Collectors.toList());
return ans;
}
}
其中的这个树型递归流程使用的是stream流来进行处理,filter过滤+map(填充子列表)+sorted(排序)最终使用collect来进行聚合。
3.1.2、配置管理服务renren-fast网关路由(后端+前端配置)
对于后台管理系统,原先是通过直接去访问renren-fast的服务地址来进行验证码、登录以及后台管理的一系列操作。
对于分布式项目所有的请求都来通过统一的一个网关来进行路由到不同的服务,在这里我们首先来配置一下网关路由!
后端服务(网关动态路由、集成跨域服务)
renren-fast集成配置:
首先将后台管理服务renren-fast也集成gulimall-common模块,让其也拥有服务注册的能力:
pom.xml
:
<!-- 引入公共模块 -->
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
编写一个bootstrap.properties
文件:用于进行服务注册以及配置中心地址配置、应用名
spring.application.name=renren-fast
# 服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 配置中心
spring.cloud.nacos.config.server-addr=localhost:8848
在启动器上开启服务发现:
@EnableDiscoveryClient //开启服务发现
gulimall-gateway集成配置:
准备操作好了之后,我们就来进行gulimall-gateway
服务模块的动态路由部分的编写,application.yml
配置如下:
spring:
cloud:
gateway:
routes:
# 自定义后台管理服务的动态路由
# 路由转换:http://localhost:88/api/renren-fast => http://localhost:8080/renren-fast
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 覆盖重写路径
由于所有的请求都会走我们的网关,那么我们就需要在网关处来进行跨域处理,我们编写一个跨域配置类GulimallCorsConfiguration
:
package com.atguigu.gulimall.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
/**
* @Description: 网关统一处理跨域请求配置类
* @Author: changlu
* @Date: 8:02 PM
*/
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
//基于url跨域,选择reactive包下
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//跨域配置信息
CorsConfiguration corsConfiguration = new CorsConfiguration();
//允许跨域的头
corsConfiguration.addAllowedHeader("*");
//允许跨域的请求方式
corsConfiguration.addAllowedMethod("*");
//允许跨域的请求来源
corsConfiguration.addAllowedOrigin("*");
//是否允许携带cookie跨域
corsConfiguration.setAllowCredentials(true);
//任意url都需要进行跨域请求
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
在网关处集成了跨域请求之后,我们需要去查看下网关路由的那些服务是否也有跨域请求,因为若是其他服务也有跨域请求处理的话,那么就会在响应头部分添加两次允许跨域的响应头信息,在renren-fast服务中就有,我们需要将其进行注释掉:
至此,我们的后端服务就暂且集成完毕!
前端服务
对于前端的话无需做很大的改动,只需要将全局的请求路径进行修改即可:
http://localhost:8080/renren-fast => http://localhost:88/api
对于后端服务来说,若是匹配到/api/**就会去进行动态路由匹配转发到后端管理服务去进行操作!
测试
启动nacos注册中心、网关服务、后台管理服务:
此时查看下nacos注册中心,看一下是否已经上线:
没有问题,那么我们就启动前端管理系统项目,来进行一个验证码接口和登录接口的测试:
验证码请求:
login登录请求:
3.1.3、产品分类查询(前端)
前端产品分类页面创建
创建目录:
创建一级菜单:
看一下当前的分类效果:
本质实际上就是在sys_menu表中加了两条记录:
对应product-category
实际就会映射到product/category.vue
这个文件:
配置商品服务gulimall-product的动态路由地址
在gulimall-product服务中application.yml中配置商品服务的动态路由:
spring:
cloud:
gateway:
routes:
# 商品服务路由
# 路由转换:http://localhost:88/api/product/category/list/tree => http://localhost:10000/product/category/list/tree
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/(?<segment>.*),/product/$\{segment}
提示:你可以看到对于产品服务的动态路由是配置到了renren-fast后台管理服务的路由之上,这是因为renren-fast的路由匹配是从/api/**
就开始的,在gateway中去进行匹配是根据你配置的上下顺来来进行匹配转发的,而我们的商品服务动态路由则是匹配的/api/product/**
,很明显商品服务的路由是后台管理服务的子路由,所以应该需要进行优先匹配,至此需要放置到其上面!
简而言之:精确路由放在高优先级,模糊路由放在低优先级。
前端产品分类接口实现(集成3.1.1查询接口)
对于树型组件的展示我们可以直接使用element ui的树型组件:
代码如下:
<template>
<div>
<el-tree
:data="menus"
show-checkbox
node-key="id"
:default-expanded-keys="[2, 3]"
:default-checked-keys="[5]"
:props="defaultProps">
</el-tree>
</div>
</template>
methods: {
//获取树型菜单数据
getMenus() {
this.$http({
url: this.$http.adornUrl('/product/category/list/tree'),
method: 'get'
}).then(({data}) => {
console.log("成功获取到菜单数据...", data.data)
this.menus = data.data;
})
}
},
// 生命周期 - 创建完成(可以访问当前this实例)
created () {
this.getMenus();
},
效果如下:
接着我们要实现一个前端需求:
每个分类右边都有一个新增与删除的按钮,并且对于新增与删除按钮的显示也有要求:
①新增Append显示要求:只有1级2级分类才具备新增选项。
②删除delete显示要求:只有当子分类数量为0时才允许被删除
效果如下:
如何能够去添加右边的Append与Delete呢?这就使用到了vue中的插槽语法,修整后的代码如下:
<el-tree
:data="menus"
show-checkbox
node-key="catId"
:expand-on-click-node="false"
:props="defaultProps"
:default-expanded-keys="expandedKey"
>
<!-- 插槽传的值:node表示该结点的属性(组件原生) data表示我们实际的值 -->
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<!-- 只有一级二级分类才能够显示 node.level是组件原生的属性-->
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
Append
</el-button>
<!-- 若是没有子节点分类时,就可以进行删除 node.childNodes就是组件自带的node节点 -->
<el-button
v-if="node.childNodes.length == 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
</el-tree>
3.1.4、删除单个分类(后端+前端实现)
效果展示及思路
思路逻辑:
1、点击delete,进入message选择框。
2、点击message选择框确认,发起删除请求,最后就是刷新菜单。
- 注意:成功删除之后,原本展开那个分栏框依旧展开。(使用到了el-tree中的default-expanded-keys属性,只需要在刷新菜单后进行绑定即可)
后端代码(逻辑删除)
配置逻辑删除
gulimall-product中的productEntity配置mybatisplus的逻辑删除注解:
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
配置好了之后,我们去使用mp中baseMapper的查询与删除,默认会走的是逻辑删除及查询对应status=0的所有记录!
删除代码逻辑
CategoryController.java
:
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 删除分类标签
*/
@PostMapping("/delete")
//@RequiresPermissions("product:category:delete")
public R delete(@RequestBody Long[] catIds){
categoryService.removeMenuByIds(Arrays.asList(catIds));
return R.ok();
}
}
CategoryServiceImpl.java
:当前仅仅直接实现了批量删除,对于引用的代码并没有进行编写
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public void removeMenuByIds(List<Long> asList) {
//TODO 1、检查当前删除的菜单,是否被其他地方引用
//2、逻辑删除
baseMapper.deleteBatchIds(asList);
}
}
前端代码
<!-- node-key:后端传过来节点中的id名称(这里就是catId) show-checkbox:展示勾选框 expand-on-click-node:需要点击向下箭头才展开 props:自定义组件中显示的名称
default-expanded-keys 默认展开的节点,接收的是数组(绑定对应的catId)
-->
<el-tree
:data="menus"
show-checkbox
node-key="catId"
:expand-on-click-node="false"
:props="defaultProps"
:default-expanded-keys="expandedKey"
>
<!-- 插槽传的值:node表示该结点的属性(组件原生) data表示我们实际的值 -->
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<!-- 只有一级二级分类才能够显示 node.level是组件原生的属性-->
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
Append
</el-button>
<!-- 若是没有子节点分类时,就可以进行删除 node.childNodes就是组件自带的node节点 -->
<el-button
v-if="node.childNodes.length == 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
</el-tree>
// 方法集合
methods: {
//删除单个分类
remove(node, data) {
console.log(node, data);
this.$confirm(`此操作将刪除分类[${node.label}], 是否继续?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
//构成id
var ids = [data.catId];
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "删除成功!"
});
console.log("刪除成功!");
//刷新菜單
this.getMenus();
//设置展开的是当前删除节点的父catId
this.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {});
},
}
3.1.5、新增单个分类(后端+前端实现)
效果展示及思路
思路:
1、点击Append,出现弹窗,输入内容。
2、点击确定,发起新增请求,请求结束刷新菜单,显示已经展开的分栏框。
后端代码
直接就是之前逆向生成的代码,我们无需进行修改:
@RestController
@RequestMapping("product/category")
public class CategoryController {
/**
* 保存
*/
@PostMapping("/save")
//@RequiresPermissions("product:category:save")
public R save(@RequestBody CategoryEntity category){
categoryService.save(category);
return R.ok();
}
}
前端代码
<el-dialog
title="新增分类"
:visible.sync="dialogVisible"
width="30%"
:close-on-click-modal="false"
>
<el-form :model="category">
<el-form-item label="分类名称">
<el-input v-model="category.name" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitData">确 定</el-button>
</span>
</el-dialog>
export default {
mehtods: {
//el-tree组件调用的append方法
append(data) {
console.log("---append---,data", data);
//打开窗口
this.dialogVisible = true;
this.category.parentCid = data.catId; //父分类id
this.category.catLevel = data.catLevel * 1 + 1; //分类id
this.category.catId = null; //待服务器自己生成
this.category.name = "";
this.category.icon = "";
this.category.productUnit = "";
this.category.sort = 0;
this.category.showStatus = 1;
console.log("待apennd数据", this.category);
console.log("---append---");
},
//添加三级分类
addCategory() {
console.log("---addCategory---")
console.log(this.category)
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "新增成功!"
});
//关闭窗口
this.dialogVisible = false;
//刷新菜單
this.getMenus();
//设置展开的是当前删除节点的父catId
this.expandedKey = [this.category.parentCid];
});
console.log("---addCategory---")
},
}
}
3.1.6、编辑单个分类(后端+前端实现)
编辑分类:
1、点击编辑回显分类信息,这个信息一定要从服务器中进行查询,否则可能会出现多个管理员去操作时显示的数据还是之前树型结构的。
2、对于真正的编辑请求中携带的数据仅仅只是要进行修改的属性,其他不修改的一律不用携带。
拖拽数据效果展示:
el-tree的可拖拽节点:添加draggle属性
- 对于是否能够拖到指定的层级位置,是要进行判定的。在当前的系统需求中来说是无法将某个分类拖动到第三级的标签中的。
对于拖拽的过程中是否允许:allowDrop(draggingNode, dropNode, type)方法,返回布尔类型,分别是拖动中的节点,目标节点以及拖动类型(type
参数有三种情况:‘prev’、‘inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后)
拖拽成功可绑定触发事件:node-drop
开启拖拽功能,只有开启才能够进行拖动。
正在更新中…