GUIGU-GULIMALL项目:
写在开头:
-
这份笔记仅仅记录了一些环境搭建以及基础篇中一些技术的使用,基本的CURD大部分没有记录,参考了很多网友的博客。若有冒犯,请联系我删除。参考文档见下:
-
本博客为自己随笔记录,写的并不好,详细步骤可以参考上面的博客。
1 Linux环境搭建:
1.1 使用visualBox安装centos/7
-
visualBox进行安装需要cpu开启虚拟化,在开机启动的时候设置主板,CPU configuration,然后点击Intel Vitualization Technology。重启电脑
-
普通安装linux虚拟机太麻烦,可以利用vagrant可以帮助我们快速地创建一个虚拟机。主要装了vitualbox,vagrant可以帮助我们快速创建出一个虚拟机。他有一个镜像仓库。
-
去https://www.vagrantup.com/ 下载vagrant安装,安装后重启系统。cmd中输入vagrant有版本代表成功了。
-
输入vagrant init centos/7,即可初始化一个centos7系统。(注意这个命令在哪个目录下执行的,他的Vagrantfile就生成在哪里)
1.2 vagrant up启动虚拟机环境。
-
启动后出现default folder:/cygdrive/c/User/… =>/vagrant。然后ctrl+c退出
-
前面的页面中有ssh账号信息。vagrant ssh 就会连上虚拟机。可以使用exit退出
-
下次使用也可以直接vagrant up直接启动,他下面有一个Vagrantfile,不过我们也可以配置环境变量。
-
启动后再次vagrant ssh连上即可
-
网络方式是网络地址转换NAT(端口转发),如果其他主机要访问虚拟机,必须由windows端口如3333断发给虚拟机端口如3306。这样每在linux里安一个软件都要进行端口映射,不方便,(也可以在virualBox里挨个设置)。我们想要给虚拟机一个固定的ip地址,windows和虚拟机可以互相ping通。
-
visualBox的网络模式可以参考:https://mp.weixin.qq.com/s?__biz=MzI5MDg4ODEzOA==&mid=2247488277&idx=1&sn=012c33bec2984a61850b30b1bb270812&scene=21#wechat_redirect
方式1:在虚拟机中配置静态ip。
方式2:更改Vagrantfile更改虚拟机ip,修改其中的 config.vm.network "private_network",ip:"192.168.56.10",这个ip需要 在windows的ipconfig中查到vitualbox的虚拟网卡ip,然后更改下最后一个数字就行(不能是1,1是我们的主机)。配置完后 vagrant reload重启虚拟机。在虚拟机中ip addr就可以查看到地址了。互相ping也能ping通。
关掉防火墙,VirualBox中第一个网卡设置NAT,第二个网卡设置仅主机
-
如果ping不了baidu
-
cd /etc/sysconfig/network-scripts
-
ls 一般有ifcfg-eth0 1
-
ip addr 看哪个网格是192.168.56网段,然后vim他
-
vim ifcfg-eth1 加入
-
GATEWAY=192.168.56.1 DNS1=114.114.114.114 DNS2=8.8.8.8
service network restart
-
默认只允许ssh登录方式,为了后来操作方便,文件上传等,我们可以配置允许账号密码登录
vim /etc/ssh/sshd_config
修改
PasswordAuthentication yes
重启
service sshd restart
账号root
密码vagrant
1.3 配置源
##### 备份原yum源
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
##### 使用新yum源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
##### 生成缓存
yum makecache
1.4 虚拟机安装docker
https://docs.docker.com/engine/install/centos/
#卸载系统之前的docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
sudo yum install -y yum-utils
# 配置镜像
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
# 设置开机自启动
sudo systemctl enable docker
docker -v
sudo docker images
# 配置镜像加速
https://cr.console.aliyun.com/cn-qingdao/instances/mirrors
1.5 根据页面命令执行完命令
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://chqac97z.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
1.6 安装mysql-docker
-
用docker安装上mysql,去docker仓库里搜索mysql
sudo docker pull mysql:5.7 ##### --name指定容器名字 -v目录挂载 -p指定端口映射 -e设置mysql参数 -d后台运行 sudo 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 su root 密码为vagrant,这样就可以不写sudo了 [root@localhost vagrant]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6a685a33103f mysql:5.7 "docker-entrypoint.s…" 32 seconds ago Up 30 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mysql
-
设置mysql:
##### 进入已启动的容器
docker exec -it mysql bin/bash
##### 退出进入的容器
exit;
因为有目录映射,所以我们可以直接在镜像外执行
vi /mydata/mysql/conf/my.conf
[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
保存(注意评论区该配置不对,不是collection而是collation)
docker restart mysql
1.7安装Redis
-
如果直接挂载的话docker会以为挂载的是一个目录,所以我们先创建一个文件然后再挂载,在虚拟机中。
##### 在虚拟机中
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
docker pull redis
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
# 直接进去redis客户端。
docker exec -it redis redis-cli
-
默认是不持久化的。在配置文件中输入appendonly yes,就可以aof持久化了。修改完docker restart redis,docker -it redis redis-cli
vim /mydata/redis/conf/redis.conf
##### 插入下面内容
appendonly yes
保存
docker restart redis
-
设置redis容器在docker启动的时候启动
docker update redis --restart=always
2. IDEA、VScode开发环境:
2.1 maven
在settings中配置阿里云镜像,配置jdk1.8。这个基本都配置过,不贴了
IDEA安装插件lombok,mybatisX。IDEA设置里配置好maven
2.2 vsCode设置
下载vsCode用于前端管理系统。在vsCode里安装插件。
Auto Close Tag
Auto Rename Tag
Chinese
ESlint
HTML CSS Support
HTML Snippets
JavaScript ES6
Live Server
open in brower
Vetur
2.3 安装git(或者github):
-
下载git客户端,右键桌面Git GUI/bash Here。去bash,
# 配置用户名
git config --global user.name "username" //(名字,随意写)
# 配置邮箱
git config --global user.email "[email protected]" // 注册账号时使用的邮箱
# 配置ssh免密登录
ssh-keygen -t rsa -C "[email protected]"
三次回车后生成了密钥:公钥私钥
cat ~/.ssh/id_rsa.pub
也可以查看密钥
浏览器登录码云后,个人头像上点设置--ssh公钥---随便填个标题---复制
xx
# 测试
ssh -T [email protected]
测试成功,就可以无密给码云推送仓库了
3 项目搭建:
3.1 码云创建仓库:
-
在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven(就会忽略掉maven一些个人无需上传的配置文件),许可证选Apache-2.0,开发模型选生成/开发模型,开发时在dev分支,发布时在master分支,创建。
-
在IDEA中New–Project from version control–git–复制刚才项目的地址
-
IDEA然后New Module–Spring Initializer–com.atguigu.gulimall , Artifact填 gulimall-product。Next—选择web(web开发),springcloud routing里选中openFeign(rpc调用)。
-
依次创建出以下服务模块
商品服务product 存储服务ware 订单服务order 优惠券服务coupon 用户服务member
-
共同点:
-
导入web和openFeign
-
group:com.atguigu.gulimall
-
Artifact:gulimall-XXX
-
每一个服务,包名com.atguigu.gulimall.XXX{product/order/ware/coupon/member}
-
模块名:gulimall-XXX
-
-
然后右下角显示了springboot的service选项,选择他
-
从某个项目粘贴个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> <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>
-
修改总项目的
.gitignore
,把小项目里的垃圾文件在提交的时候忽略掉,比如HELP.md-
**/mvnw **/mvnw.cmd **/.mvn **/target/ .idea **/.gitignore
-
在version control/local Changes,点击刷新看Unversioned Files,可以看到变化。
-
全选最后剩下21个文件,选择右键、Add to VCS。
-
在IDEA中安装插件:gitee,重启IDEA。
-
在D额fault changelist右键点击commit,去掉右面的勾选Perform code analysis、CHECK TODO,然后点击COMMIT,有个下拉列表,点击commit and push才会提交到云端。此时就可以在浏览器中看到了。commit只是保存更新到本地.push才是提交到gitee.
-
3.2 数据库:
3.2.1 数据库地址
-
https://gitee.com/HanFerm/gulimall/tree/master/sql文件
-
pms库里面少了pms_category_brand_relation表
3.2.2 docker配置数据库:
-
sudo docker ps sudo docker ps -a # 这两个命令的差别就是后者会显示 【已创建但没有启动的容器】 # 我们接下来设置我们要用的容器每次都是自动启动 sudo docker update redis --restart=always sudo docker update mysql --restart=always # 如果不配置上面的内容的话,我们也可以选择手动启动 sudo docker start mysql sudo docker start redis # 如果要进入已启动的容器 sudo docker exec -it mysql /bin/bash # /bin/bash就是进入一般的命令行,如果改成redis就是进入了redis
-
用navicat或者sqlyog或者其他可视化数据库操作软件建表:
-
gulimall-oms gulimall-pms gulimall-sms gulimall-ums gulimall-wms
-
然后打开对应的sql在对应的数据库中执行。依次执行。(注意sql文件里没有建库语句)
-
3.3 人人开源:
3.3.1 人人前后端:
-
在码云上搜索人人开源,我们使用renren-fast(后端)、renren-fast-vue(前端)项目。
-
git下载项目:
-
git clone https://gitee.com/renrenio/renren-fast.git git clone https://gitee.com/renrenio/renren-fast-vue.git
-
-
下载后删除文件夹中的.git文件,同时分别导入到idea和VScode中。
-
在IDEA项目里的pom.xml添加一个renrnen-fast
-
<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>
-
-
然后打开renren-fast/db/mysql.sql,复制全部,在sqlyog中创建库guli-admin,粘贴刚才的内容执行。然后修改项目里renren-fast中的application.yml,修改application-dev.yml中的数库库的url,通常把localhost修改为192.168.56.10即可。然后该对后面那个数据库
-
url: jdbc:mysql://192.168.56.10:3306/guli_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: root
-
然后运行该java项目下的RenrenApplication
浏览器输入http://localhost:8080/renren-fast/ 得到{“msg”:“invalid token”,“code”:401}就代表无误
-
-
前端启动vue项目,后端启动SpringBoot项目。略
3.3.2 人人逆向工程:
-
逆向工程搭建:
-
git clone https://gitee.com/renrenio/renren-generator.git
-
下载到桌面后,同样把里面的.git文件删除,然后移动到我们IDEA项目目录中,同样配置好根项目pom.xml(将renren-generator添加进去)
-
<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>
-
在maven中刷新一下,让项目名变粗体,稍等下面进度条完成。修改application.yml
-
url: jdbc:mysql://192.168.56.10:3306/gulimall-pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root
-
然后修改generator.properties:
-
# 主目录 mainPath=com.atguigu #包名 package=com.atguigu.gulimall #模块名 moduleName=product #作者 author=hh #email [email protected] #表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms # 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了 tablePrefix=pms_
-
!!! 在代码生成器的的resources目录下的template文件夹下的controller.java.vm中注释掉下面的代码:
-
//import org.apache.shiro.authz.annotation.RequiresPermissions; //@RequiresPermissions("${moduleName}:${pathName}:list") 第二行举个例,实际要注释掉每个方法上面的@RequiresPermissions 原因:此项目不用
-
运行逆向工程,依次生成product、ware、member、order、coupon板块。
-
-
抽取common板块:
-
逆向生成的所有的module中都需要引入例如mybatis,lombok等等的相同的依赖,所以我们可以新建一个common板块,用去配置所有的依赖和一些常用的其他的类。
-
新建common module:略
-
再common module中的pom文件中添加:
-
<!-- mybatisPLUS--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <!--简化实体类,用@Data代替getset方法--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <!-- httpcomponent包。发送http请求 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.13</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
-
创建完common后我们可以将每一个微服务中都引入common,作为common的子项目
-
<dependency> <groupId>com.atguigu.gulimall</groupId> <artifactId>gulimall-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
-
-
-
配置逆向生成的代码运行所需环境
-
复制renren-fast----utils包下的Query和
PageUtils
、R
、Constant
复制到common项目的java/com.atguigu.common.utils
下。 -
引入mybatis-plus和servlet-api
<!-- 数据库驱动 https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency> <!--tomcat里一般都带--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency>
-
删掉common里xss/xssfiler和XssHttpServletRequestWrapper.在product项目的resources目录下新建application.yml
-
spring: datasource: username: root password: root url: jdbc:mysql://192.168.56.10:3306/gulimall-pms driver-class-name: com.mysql.jdbc.Driver # MapperScan # sql映射文件位置 mybatis-plus: mapper-locations: classpath:/mapper/**/*.xml global-config: db-config: id-type: auto
-
在主类上加入MapperScan("你的dao包所在的位置")
-
测试
-
@SpringBootTest class gulimallProductApplicationTests { @Resource BrandService brandService; @Test void contextLoads() { BrandEntity brandEntity = new BrandEntity(); brandEntity.setDescript("手机"); brandEntity.setName("小米"); brandService.save(brandEntity); System.out.println("保存成功"); } }
-
数据库中如果新增了这条数据则测试成功,这个逆向生成的代码可以用了。
-
-
-
继续生成其余的module,构建gulimall整个项目。
-
分别设置端口号:
-
gulimall-coupon 7000 gulimall-geteway 88 gulimall-member 8000 gulimall-order 9000 gulimall-product 11000 gulimall-ware 12000 renren-fast 8083 renren-generator 80
-
4 SpringCloud Alibaba
4.1 简介:
-
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
-
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
-
伴随着springcloud中的Eureka、Hystrix等组件的停止维护,spring cloud alibaba 中的nacos、sentinel逐渐成为替代方案。
-
Nacos可以作为微服务架构中的注册中心(spring cloud的其他方案:eurekba、consul、zookeeper)以及配置中心(spring cloud config)来使用。
-
Sentinel可以作为服务保护替代方案,替代springcloud中的Hystrix,实现服务限流、降级、熔断等功能。
4.2本项目中的技术运用:
-
本项目采用nacos作为注册及配置中心,网关采用getway,采用Spring cloud提供的openFeign进行远程调用服务。
-
在common中引入依赖:
-
<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
4.3 Nacos:
4.3.1 简介:
4.3.2 配置nacos地址:
-
在product项目中的核心配置文件中配置nacos地址:
-
spring: datasource: username: root password: root url: jdbc:mysql://192.168.56.10:3306/mall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.jdbc.Driver cloud: nacos: discovery: server-addr: 127.0.0.1:8848
-
在common中引入依赖,方便其他服务使用:
-
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
在启动类上加上@EnableDiscoveryClient注解开启服务的注册及发现功能。
-
再在服务核心配置文件中配置当前服务中心名和当前模块的名字:
-
application: name: gulimall-coupon
-
后面依次给member、coupon配置nacos。
4.4 远程调用与注册中心:
4.4.1 使用远程调用:
-
如果会员服务想调用优惠卷服务查询所有的优惠券,那么应该在会员服务里引入openfeign依赖,那么他就有了远程调用其他服务的能力。:
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
4.4.2 测试远程调用:
-
在CouponController中写入以下内容:
-
@RequestMapping("coupon/coupon") public class CouponController { @Autowired private CouponService couponService; @RequestMapping("/member/list") public R membercoupons(){ //全系统的所有返回都返回R // 应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给他返回 CouponEntity couponEntity = new CouponEntity(); couponEntity.setCouponName("满100-10");//优惠券的名字 return R.ok().put("coupons",Arrays.asList(couponEntity)); }
-
在member的配置类上加注解
@EnableDiscoveryClient,
告诉member是一个远程调用客户端,member要调用东西的: -
/* * 想要远程调用的步骤: * 1 引入openfeign * 2 编写一个接口,接口告诉springcloud这个接口需要调用远程服务 * 2.1 在接口里声明@FeignClient("gulimall-coupon")他是一个远程调用客户端且要调用coupon服务 * 2.2 要调用coupon服务的/coupon/coupon/member/list方法 * 3 开启远程调用功能 @EnableFeignClients,要指定远程调用功能放的基础包 * */ @EnableFeignClients(basePackages="com.atguigu.gulimall.member.feign")//扫描接口方法注解 @EnableDiscoveryClient// 注册到nacos @SpringBootApplication public class gulimallMemberApplication { public static void main(String[] args) { SpringApplication.run(gulimallMemberApplication.class, args); } }
-
在MeemberController中写测试请求:
-
@RestController @RequestMapping("member/member") public class MemberController { @Autowired private MemberService memberService; @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")); }
-
请求你的url,测试有没有返回一个R对象。
4.5 nacos作为配置中心:
4.5.1 添加配置中心依赖:
-
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
-
在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别application.properties高.所以先使用nacos里配置的配置。
-
# 改名字,对应nacos里的配置文件名 spring.application.name=gulimall-coupon spring.cloud.nacos.config.server-addr=127.0.0.1:8848
4.5.2 nacos管理中心配置:
-
默认路径:localhost:8848
-
在配置列表中添加:gulimall-coupon.properties
-
coupon.user.name=Tx coupon.user.age=20
-
发布并重启coupon(生产中加入@RefreshScope即可),http://localhost:7000/coupon/coupon/test
-
在controller中加入注解@RefreshScope可以在不重启服务的情况下在nacos中配置应用到服务中。
4.5.3配置中心的进阶使用:
在nacos浏览器中还可以配置:
-
命名空间:用作配置隔离。(一般每个微服务一个命名空间)默认public。默认新增的配置都在public空间下
-
开发、测试、开发可以用命名空间分割。properties每个空间有一份。也可以为每个微服务配置一个命名空间,微服务互相隔离
-
在bootstrap.properties里配置(测试完去掉,学习不需要)
# 可以选择对应的命名空间 # 写上对应环境的命名空间ID spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
-
配置集:一组相关或不相关配置项的集合。
-
配置集ID:类似于配置文件名,即Data ID
-
配置分组:默认所有的配置集都属于
DEFAULT_GROUP
。 -
# 更改配置分组 spring.cloud.nacos.config.group=DEFAULT_GROUP
4.6 网关getway:
4.6.1 简介:
-
网关是请求流量的入口,常用功能包括路由转发,权限校验,限流控制等。springcloud
gateway
取代了zuul
网关。 -
参考手册:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/
4.6.2 核心:
-
Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates断言, and a collection of filters. A route is matched if the aggregate predicate is true.发一个请求给网关,网关要将请求路由到指定的服务。路由有id,目的地uri,断言的集合,匹配了断言就能到达指定位置
-
Predicate断言: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.就是java里的断言函数,匹配请求里的任何信息,包括请求头等。根据请求头路由哪个服务
-
Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.过滤器请求和响应都可以被修改。
-
客户端发请求给服务端。中间有网关。先交给映射器,如果能处理就交给handler处理,然后交给一系列filer,然后给指定的服务,再返回回来给客户端。
4.6.3 使用网关服务:
-
创建:略
-
添加common依赖:略
-
nacos中新建getway命名空间,并新建guilmall-gateway.yml.yml.
-
开启服务注册及发现:
-
服务发现:主类中添加@EnableDiscoveryClient注解
-
服务注册:配置nacos注册中心地址applicaion.properties
-
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.application.name=gulimall-gateway server.port=88
-
bootstrap.properties 填写nacos配置中心地址:
-
spring.application.name=gulimall-gateway spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.namespace=自己建的命名空间的地址
-
配置本服务在nacos中的服务名:
-
spring: application: name: gulimall-gateway
-
-
设置网关路由转发:
-
spring: cloud: gateway: routes: - id: product_route uri: lb://gulimall-product predicates: - Path=/api/product/** filters: - RewritePath=/api/(?<segment>/?.*),/$\{segment} - id: third-party-route uri: lb://mall-third-service predicates: - Path=/api/thirdparty/** filters: - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment} - id: admin_route uri: lb://renren-fast predicates: - Path=/api/** filters: - RewritePath=/api/(?<segment>/?.*),/renren-fast/$\{segment} - id: test_route uri: https://www.baidu.com predicates: - Query=url,baidu
-
测试:访问localhost:8080/hello?url=baidu
-
5 三级分类
-
评论区有带数据的sql文件
5.1 element-ui的使用
-
element-ui提供了tree树形组件,他的数据是以data属性显示的。而他的子菜单是由data里的children属性决定的。
-
树形组件的使用可以参考element-ui里面实例。
5.2 新建菜单:
-
在系统管理-》菜单管理中新建商品系统目录,路径:product/category
-
同样在这里新建分类维护菜单,分类维护是商品系统的子菜单。路径:product/category
5.3 关于登陆页面验证码不显示的问题:
5.3.1 问题分析:
-
打开后台:我们会发现,他要请求
localhost:8001/renren-fast/product/category/list/tree
这个url。 -
但实际上人人工程的端口号以及在之前修改过的8083,自然我们拿不到验证码的图片
5.3.2 解决方法:
-
搭建网关,通过网关路由到8083端口。
5.3.3 具体实现:
-
修改vue工程里的请求路径:static/config/index.js
-
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
-
将renren-fast注册到nacos中,再通过getway中的路径重写访问目的端口号。
-
renren-fast注册到nacos:
<dependency> <!-- 里面有nacos注册中心 --> <groupId>com.atguigu.gulimall</groupId> <artifactId>gulimall-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
-
renren-fast项目中添加:
-
spring: application: name: renren-fast # 意思是把renren-fast项目也注册到nacos中(后面不再强调了),这样网关才能转发给 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # nacos server: port: 88 # 别和gateway冲突了
-
fast启动类上加上注解
@EnableDiscoveryClient
-
-
getway中路径重写:
-
- id: admin_route uri: lb://renren-fast # 路由给renren-fast predicates: # 什么情况下路由给它 - Path=/api/** # 默认前端项目都带上api前缀,就是我们前面题的localhost:88/api filters: - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 把/api/* 改变成 /renren-fast/* # fast找
-
-
后出现跨域问题:见下:
5.4 跨域问题:
5.4.1 解决方案一:
5.4.2 解决方案二
-
服务端配置允许跨域
-
在网关中定义“
GulimallCorsConfiguration
”类,该类用来做过滤,允许所有的请求跨域。 -
package com.atguigu.gulimall.gateway.config; @Configuration // gateway 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项目,注释掉“io.renren.config.CorsConfig”类。
-
再次访问就可以显示验证码了。
5.5 继续实现三级分类:
5.5.1 网关转发product服务
-
在显示分类信息时,利用抓包工具可以看到请求的路径是不存在的,这是因为网关在进行转发时识别到了/api并将这次请求转发到了renren-fast模块,而我们应该是要请求product模块里面的内容,因此会显示404错误。
-
解决方法:在getway中继续配置详细的路径映射:
-
- id: product_route uri: lb://gulimall-product # 注册中心的服务 predicates: - Path=/api/product/** filters: - RewritePath=/api/(?<segment>/?.*),/$\{segment}
-
此处有个知识点:精确的路由一点更要放在模糊路由的前面,getway中匹配路由是按照你代码的顺序自上而下的,命中路由后就不会再继续寻找下去。
5.5.2category.vue(主要的代码)
-
批量删除
-
//批量删除 BatchDelete(){ let catIds=[]; let names=[] let checkedNodes=this.$refs.menutree.getCheckedNodes() console.log("被选中的元素",checkedNodes) //遍历拿到每一个结点的id,根据id进行删除 for(let i=0;i<checkedNodes.length;i++) { catIds.push(checkedNodes[i].catId) names.push(checkedNodes[i].name) } //向后台提交批量删除请求 this.$confirm(`确认批量删除当前选中的【${names}】节点?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.$http({ url: this.$http.adornUrl('/product/category/delete'), method: 'post', data: this.$http.adornData(catIds, false) }).then(({data}) => { this.$message({ type: 'success', message: '删除成功!' }) this.getMenus() //this.expandKey = [node.parent.data.catId] }) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) }
-
-
拖拽节点并更新节点的数据
-
handleDrop(draggingNode, dropNode, dropType, ev) { console.log("handleDrop: ", draggingNode, dropNode, dropType); //1 当前节点最新的父节点 let pCid = 0; let siblings = null; if (dropType == "before" || dropType == "after") { pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId; siblings = dropNode.parent.childNodes; } else { pCid = dropNode.data.catId; siblings = dropNode.childNodes; } this.pCid.push(pCid); //2 当前拖拽节点的最新顺序 for (let i = 0; i < siblings.length; i++) { if (siblings[i].data.catId == draggingNode.data.catId) { // 如果遍历的是当前正在拖拽的节点 let catLevel = draggingNode.level; if (siblings[i].level != draggingNode.level) { // 当前节点的层级发生变化 catLevel = siblings[i].level; // 修改他子节点的层级 this.updateChildNodeLevlel(siblings[i]); } this.updateNodes.push({ catId: siblings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel, }); } else { this.updateNodes.push({ catId: siblings[i].data.catId, sort: i }); } } //3 当前拖拽节点的最新层级 console.log("updateNodes", this.updateNodes); }, updateChildNodeLevlel(node) { if (node.childNodes.length > 0) { for (let i = 0; i < node.childNodes.length; i++) { var cNode = node.childNodes[i].data; this.updateNodes.push({ catId: cNode.catId, catLevel: node.childNodes[i].level, }); this.updateChildNodeLevlel(node.childNodes[i]); } } }, allowDrop(draggingNode,dropNode,type){ //被拖动节点的总层数 console.log("allowDrop",draggingNode,dropNode,type) this.countNodeLevel(draggingNode) //当前拖动的节点+父节点所在的深度不大于三就可以拖动 //console.log(this.maxLevel) //console.log(draggingNode.data.catLevel) let deep=Math.abs(this.maxLevel-draggingNode.level+1) console.log("深度:",deep) if(type=="inner") return (deep+dropNode.level)<=3 else return (deep+dropNode.parent.level)<=3 }, countNodeLevel(node) { //找到所有的子节点,求出最大深度 if(node.childNodes != null &&node.childNodes.length>0) { for(let i=0;i<node.childNodes.length;i++) { if(node.childNodes[i].catLevel>this.maxLevel) { this.maxLevel=node.childNodes[i].cLevel } this.countNodeLevel(node.childNodes[i]); } } }
-
6 品牌管理
6.1 逆向工程生成
-
使用逆向工程生成前后端代码:略
6.2 配置阿里云OSS对象存储:
6.2.1 开通阿里云服务:
-
略
6.2.2 引入依赖:
-
在product服务的pom文件中引入阿里云sdk:
-
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.0</version> <scope>test</scope> </dependency>
6.2.3 配置上传文件:
-
String endpoint = ""; // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 String accessKeyId = ""; String accessKeySecret = ""; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 创建PutObjectRequest对象。 // 填写Bucket名称、Object完整路径和本地文件的完整路径。Object完整路径中不能包含Bucket名称。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 PutObjectRequest putObjectRequest = new PutObjectRequest("你选择的bucket", "文件名", new File("文件路径")); // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。 // ObjectMetadata metadata = new ObjectMetadata(); // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString()); // metadata.setObjectAcl(CannedAccessControlList.Private); // putObjectRequest.setMetadata(metadata); // 上传文件。 ossClient.putObject(putObjectRequest); // 关闭OSSClient。 ossClient.shutdown();
6.2.4 使用子账户管理OSS对象存储:
-
步骤:略(跟着视频来挺简单的)
6.2.5 测试代码
-
@Test public void testUpload() { String endpoint = "xxx"; String accessKeyId = "xxx"; String accessKeySecret = "xxx"; OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); PutObjectRequest putObjectRequest = new PutObjectRequest("gulimall-kaisarh", "login.png", new File("C:\\Users\\Administrator\\Pictures\\login.png")); ossClient.putObject(putObjectRequest); ossClient.shutdown(); System.out.println("上传完成"); }
6.3 SpringCloud Alibaba-OSS实现对象存储:
6.3.1 引入SpringCloud Alibaba-OSS
-
由于很多服务都可能使用文件上传,因此直接在gulimall-common中引入:
-
--引入spring-alibaba-oss--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alicloud-oss</artifactId> </dependency>
-
配置阿里云oss账号信息:
-
spring: cloud: alicloud: oss: endpoint: oss-cn-shanghai.aliyuncs.com access-key: xxxxxx secret-key: xxxxxx
-
测试使用OssClient 上传
-
@Resources OSSClient ossClient; @Test public void testUpload() throws FileNotFoundException { InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\removeAll.png"); ossClient.putObject("gulimall-kaisarh", "removeAll.png", inputStream); ossClient.shutdown(); System.out.println("上传完成"); }
6.4 校验
6.4.1:表单校验自定义校验器
-
前端表单校验:自定义一个校验器,
-
firstLetter: [ { validator: (rule, value, callback) => { if (value == "") { callback(new Error("首字母必须填写")); } else if (!/^[a-zA-Z]$/.test(value)) { callback(new Error("首字母必须a-z或者A-Z之间")); } else { callback(); } }, trigger: "blur" } ]
-
callback()参数为空表示校验通过的情况。
6.4.2 JSR303规范及后端校验:
-
JSR303是一套JavaBean参数校验的标准,定义了很多常用的校验注解。可以直接将这些注解加在我们JavaBean的属性上面就可以在需要校验的时候进行校验了。
-
使用方法:
-
给 bean 添加校验注解 constraints 并定义自己的错误提示
-
controller 给参数添加 @Valid 注解
-
给校验的bean后面紧跟一个BindingResult, 就可以获取到校验的结果
-
-
分组校验:
-
先定义分组,再再校验注解上加参数groups={组名.class}.
-
-
例如:
-
/** * 品牌id */ //@Null(message = "修改时品牌id必须为空",groups = {UpdateGroup.class}) //@NotNull(message = "新增时品牌时不能指定id",groups = {AddGroup.class}) @TableId private Long brandId;
-
-
统一异常处理:
-
兼容并处理返回的异常:
-
@Slf4j @RestControllerAdvice public class GulimallExceptionControllerAdvice { @ExceptionHandler(value= MethodArgumentNotValidException.class) public R handleValidException(MethodArgumentNotValidException e) { log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass()); BindingResult bindingResult = e.getBindingResult(); Map<String, String> map = new HashMap<>(); bindingResult.getFieldErrors().forEach((fieldError) -> { String message = fieldError.getDefaultMessage(); String filed = fieldError.getField(); map.put(filed, message); }); return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMessage()).put("data", map); } //能够兼容所有的异常 @ExceptionHandler(value = Throwable.class) public R handleException(Throwable throwable) { return R.error(BizCodeEnume.UNKNOWN_EXCEPTION.getCode(),BizCodeEnume.UNKNOWN_EXCEPTION.getMessage()); } }
-
6.5 SPU及SKU:
6.5.1 简介:
-
SPU:Standard Product Unit(标准化产品单元)
-
是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
-
如iphoneX是SPU
-
-
SKU:Stock Keeping Unit(库存量单位)
-
即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品同意编号的简称,每种产品均对应有唯一的SKU号。
-
如iphoneX 64G 黑色 是SKU
-
-
SPU决定规格参数。SKU决定销售属性。
6.6 基本属性及销售属性:
6.6.1 简介:
-
基本属性:同一个SPU拥有的特性叫基本属性。如机身长度,这个是手机共用的属性。而每款手机的属性值不同。商品介绍、规格与 包装都属于基本属性,是SPU的特性。规格参数属于基本属性。
-
销售属性:决定SKU的属性,如颜色、
6.7 前端抽取组件、父子组件的交互:
6.7.1 抽取组件:
-
所谓抽取组件就是将一个常用的组件封装成一个vue组件,需要使用时在其他的页面中直接调用这个组件即可。
-
例如:category.vue:
-
<template> <div> <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input> <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick" :filter-node-method="filterNode" :highlight-current = "true" ></el-tree> </div> </template> <script> 略 </script>
-
在其他组件中使用这个组件就先在script中引入,再components中声明,后再使用。
6.7.2 父子组件的交互:
-
在子组件中调用this.$emit("父组件中的方法名",参数);
-
在父组件在声明子组件传递的方法,并调用自己的方法。
-
例如:
-
子组件:
-
<template> <div> <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input> <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick" :filter-node-method="filterNode" :highlight-current = "true" ></el-tree> </div> </template> //实现 nodeclick(data, node, component) { console.log("子组件category的节点被点击", data, node, component); //向父组件发送事件; this.$emit("tree-node-click", data, node, component); }
-
-
父组件:
-
<el-col :span="6"> <category @tree-node-click="treenodeclick"></category> </el-col> //感知树节点被点击 treenodeclick(data, node, component) { if (node.level == 3) { this.catId = data.catId; this.getDataList(); //重新查询 } }
-
6.7.3 关于pubsub的问题:
-
安装:先试试npm如果不行再使用cnpm
-
有些地方不需要使用this.pubsub来调用,可以改成pubsub直接调用(‘publish‘ of undefined异常解决办法)。
6.8 各种对象:
-
PO持久对象:Po就是对数据库中某个表中的一条记录,多个记录可以是用Po集合,po不包含对数据库的操作
-
DO领域对象:从现实世界中抽取出来的邮箱或无形的业务实体。
-
TO数据传输对象:不同应用程序之间传输的对象。
-
DTO数传输对象:泛指用于展示层与业务之间的传输对象
-
VO值对象:用于业务层之间的数据传递,但是应该是抽象出的业务对象,可与表对应也可以不用,根据业务取药。 视图对象,接收页面传来的数据,也可封装为完成业务逻辑,界面所需要的数据。
-
BO业务对象:把业务逻辑封装为一个对象。
-
POJO剪短无规则java对象:传统意义的java对象。
-
DAO数据访问对象:持久化层提供接口,用于访问数据库。
6.9 枚举类型的使用:
6.9.1 自定义枚举类型:
-
创建枚举类型类,
-
public class ProductConstant { public enum AttrEnum { //可以理解为实现声明了两种类型 ATTR_TYPE_BASE(1, "基本属性"), ATTR_TYPE_SALE(0, "销售属性"); private int code; private String msg; AttrEnum(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } } }
-
-
使用:
-
类名。枚举类型名。事先声明的类型。方法
-
6.10 vo的使用:
6.10.1 简介:
-
在web项目中,vo一般多用于封装一个包含了不同的类中的属性的情况,或者处理对象包含了一个类及类外其他的属性的情况。
-
import lombok.Data; @Data public class AttrVo { private Long attrId; private String attrName; private Integer searchType; private String icon; private String valueSelect; private Integer attrType; private Long enable; private Long catelogId; private Integer showDesc; private Long attrGroupId; }
-
在这个vo中,showDesc,searchType等等都是Attr类中所不具有的属性,封装在一个对象中便于对数据的处理。
-
-
可以利用BeanUtils对对象的属性值进行快速拷贝,前提是数据源与目标两者的属性名相同。vo中用这个很方便(vo中的大多数属性都是从实体类中拷贝过来的)。
-
BeanUtils.copyProperties(源,目标)
-
6.11远程调用其他的服务:
-
在需要调用其他服务的服务里新建feign包:包下新建一个接口。
-
@FeignClient("gulimall-coupon") public interface CouponFeignService { @PostMapping("coupon/spubounds/save") R saveSpuBounds(SpuBoundTo spuBoundTo); @PostMapping("coupon/skufullreduction/saveinfo") R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo); }
-
注解@FeignClient指明要调用的服务是哪个。
-
注意:
-
请求路径一定要写全(从controller那一层开始写)
-
只用声明调用某个服务里的哪个方法就行。
-
-
-
在需要调用其他微服务的启动类上添加注解
-
@EnableFeignClients("com.atguigu.gulimall.product.feign")
-
-
在被调用的服务启动类上开启服务发现功能
-
@EnableDiscoveryClient
-
-
关于FeignClient的两种写法:
-
1.让所有的请求经过网关 @FeignClient("gulimall-getway") @RequestMapping("/api/product/skuinfo/info/{skuId}") 2.不经过网关直接访问product的服务 @FeignClient("gulimall-product") @RequestMapping("/product/skuinfo/info/{skuId}")
-
7 仓储服务:
-
防止事务回滚:
-
try{}catch():如果失败则抛出异常,自己解决
-
高级篇:消息队列实现一致性事务
-
-
@Override public void addStock(Long skuId, Long wareId, Integer skuNum) { //1、判断如果还没有这个库存记录是新增 List<WareSkuEntity> entities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId)); if (entities == null || entities.size() == 0) { WareSkuEntity skuEntity = new WareSkuEntity(); skuEntity.setSkuId(skuId); skuEntity.setStock(skuNum); skuEntity.setWareId(wareId); skuEntity.setStockLocked(0); //TODO 远程查询sku的名字,如果失败,整个事务无需回滚 //1、自己catch异常 //TODO 还可以用什么办法让异常出现以后不回滚?高级 try { R info = productFeignService.info(skuId); Map<String, Object> data = (Map<String, Object>) info.get("skuInfo"); if (info.getCode() == 0) { skuEntity.setSkuName((String) data.get("skuName")); } } catch (Exception e) { } wareSkuDao.insert(skuEntity); } else { wareSkuDao.addStock(skuId, wareId, skuNum); } }