谷粒商城-个人笔记(基础篇一)

本文重点记录老师讲的话 和 一些配置流程,笔记中有的内容尽量少记录。

  • 官方笔记-基础篇:https://blog.csdn.net/hancoder/article/details/107612619

  • 官方笔记-高级篇:https://blog.csdn.net/hancoder/article/details/107612746

  • 官方笔记-集群篇:https://blog.csdn.net/hancoder/article/details/107612802

  • 官方笔记下载地址:https://download.csdn.net/download/hancoder/1273468

  • 接口文档:https://easydoc.xyz/doc/75716633/ZUqEdvA4/LnjzZHPj


目录

 一、项目简介

二、分布式基础概念

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

服务熔断&服务降级

API网关

微服务划分图

环境搭建

虚拟机安装docker

开发环境(简单介绍)

数据库

clone 人人开源

IDEA项目准备

SpringCloud Alibaba简介

Nacos

应用启动

验证

网关

三、前端

1、ES6

2、安装vue

3、vue声明式渲染

4、解构表达式.html

5、函数优化.html

6、对象优化.html

7、map和reduce.html

8、promise.html

四、Vue

1、安装:

2、测试

3、指令

3.1、v-text、v-html.html

3.2、v-bind.html

3.3、v-model.html

3.4、v-on.html

3.5、v-for.html

3.6、v-if和v-show.html

3.7、v-else和v-else-if.html

4、计算属性和侦听器

4.1、计算属性和侦听器.html

4.2、过滤器.html

4.3、组件化

4.4、生命周期钩子函数

5、vue模块化开发

5.1、全局安装webpack

5.2、全局安装vue脚手架

5.3、初始化vue项目

5.4、启动vue项目

5.5、注意

6、 element ui

五、功能模块实现

1、 商品系统

1.1、分类维护

1)、递归树形结构获取数据

2)、删除数据

3)、 菜单拖动

4)、拖动菜单时需要修改顺序和级别

1.2、品牌管理

1)、添加“显示状态按钮”

2)、添加上传

3)、上传其他方式

4)、JSR303校验

5)、新增品牌与分类关联关系

6)、获取品牌关联的分类

1.3 、平台属性(商品SPU和SKU管理)

1.3.1、属性分组

1)、获取分类属性分组

2)、获取属性分组详情

3)、查询分组关联属性和删除关联

4)、删除属性与分组的关联关系

5)、获取属性分组没有关联的其他属性

6)、添加属性与分组关联关系

1.3.2、 规格参数

1)、保存属性【规格参数,销售属性】

1.3.3、销售属性

1)、 获取分类销售属性

 



 一、项目简介


前后分离开发,分为内网部署和外网部署,外网是面向公众访问的,部署前端项目,可以有手机APP,电脑网页;内网部署的是后端集群,前端在页面上操作发送请求到后端,在这途中会经过Nginx集群,Nginx把请求转交给API网关(springcloud gateway)(网关可以根据当前请求动态地路由到指定的服务,看当前请求是想调用商品服务还是购物车服务还是检索),从路由过来如果请求很多,可以负载均衡地调用商品服务器中一台(商品服务复制了多份),当商品服务器出现问题也可以在网关层面对服务进行熔断或降级(使用阿里的sentinel组件),网关还有其他的功能如认证授权、限流(只放行部分到服务器)等。

到达服务器后进行处理(springboot为微服务),服务与服务可能会相互调用(使用feign组件),有些请求可能经过登录才能进行(基于OAuth2.0的认证中心。安全和权限使用springSecurity控制)

服务可能保存了一些数据或者需要使用缓存,我们使用redis集群(分片+哨兵集群)。持久化使用mysql,读写分离和分库分表。

服务和服务之间会使用消息队列(RabbitMQ),来完成异步解耦,分布式事务的一致性。有些服务可能需要全文检索,检索商品信息,使用ElaticSearch。

服务可能需要存取数据,使用阿里云的对象存储服务OSS。

项目上线后为了快速定位问题,使用ELK对日志进行处理,使用LogStash收集业务里的各种日志,把日志存储到ES中,用Kibana可视化页面从ES中检索出相关信息,帮助我们快速定位问题所在。

在分布式系统中,由于我们每个服务都可能部署在很多台机器,服务和服务可能相互调用,就得知道彼此都在哪里,所以需要将所有服务都注册到注册中心。服务从注册中心发现其他服务所在位置(使用阿里Nacos作为注册中心)。

每个服务的配置众多,为了实现改一处配置相同配置就同步更改,就需要配置中心,也使用阿里的Nacos,服务从配置中心中动态取配置。

服务追踪,追踪服务调用链哪里出现问题,使用springcloud提供的Sleuth、Zipkin、Metrics,把每个服务的信息交给开源的Prometheus进行聚合分析,再由Grafana进行可视化展示,提供Prometheus提供的AlterManager实时得到服务的告警信息,以短信/邮件的方式告知服务开发人员。

还提供了持续集成和持续部署。项目发布起来后,因为微服务众多,每一个都打包部署到服务器太麻烦,有了持续集成后开发人员可以将修改后的代码提交到github,运维人员可以通过自动化工具Jenkins Pipeline将github中获取的代码打包成docker镜像,最终是由k8s集成docker服务,将服务以docker容器的方式运行。


二、分布式基础概念


集群是个物理形态,分布式是个工作方式。

远程调用:在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的相互调用,我们称为远程调用

springcloud中使用HTTP+JSON的方式完成远程调用

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

A服务调用B服务,A服务并不知道B服务当前在哪几台服务器有,那些是正常的,那些服务已经下线。解决这个问题可以引入注册中心。

配置中心用来几种管理微服务的配置信息。

服务熔断&服务降级

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

rpc

情景:

订单服务 --> 商品服务 --> 库存服务

库存服务出现故障导致响应慢,导致商品服务需要等待,可能等到10s后库存服务才能响应。库存服务的不可用导致商品服务阻塞,商品服务等的期间,订单服务也处于阻塞。一个服务不可用导致整个服务链都阻塞。如果是高并发,第一个请求调用后阻塞10s得不到结果,第二个请求直接阻塞10s。更多的请求进来导致请求积压,全部阻塞,最终服务器的资源耗尽。导致雪崩

解决方案:

1 服务熔断

指定超时时间,库存服务3s没有响应就超时,如果经常失败,比如10s内100个请求都失败了。开启断路保护机制,下一次请求进来不调用库存服务了,因为上一次100%错误都出现了,我们直接在此中断,商品服务直接返回,返回一些默认数据或者null,而不调用库存服务了,这样就不会导致请求积压。

  • 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据

2 服务降级

  • 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。降级:某些服务不处理,或者处理简单【抛异常、返回NULL、调用Mock数据、调用Faaback处理逻辑】

API网关

客户端发送请求到服务器路途中,设置一个网关,请求都先到达网关,网关对请求进行统一认证(合法非法)和处理等操作。他是安检。

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

微服务划分图

反映了需要创建的微服务以及相关技术。

前后分离开发。前端项目分为admin-vue(工作人员使用的后台管理系统)、shop-vue(面向公众访问的web网站)、app(公众)、小程序(公众)

  • 商品服务:商品的增删改查、商品的上下架、商品详情
  • 支付服务
  • 优惠服务
  • 用户服务:用户的个人中心、收货地址
  • 仓储服务:商品的库存
  • 秒杀服务:
  • 订单服务:订单增删改查
  • 检索服务:商品的检索ES
  • 中央认证服务:登录、注册、单点登录、社交登录
  • 购物车服务:
  • 后台管理系统:添加优惠信息等

环境搭建

下载CentOS7的镜像

下载地址:https://www.centos.org/download/

谷粒商城-个人笔记(基础篇一)_第1张图片

谷粒商城-个人笔记(基础篇一)_第2张图片

virtual box 安装Centos7请参考https://www.cnblogs.com/xyinjie/p/9437049.html

centos7安装好之后之后,记得关闭防火墙

systemctl stop firewalld

我们想要给虚拟机一个固定的ip地址,windows和虚拟机可以互相ping通。方式1是在虚拟机中配置静态ip。也可以更改Vagrantfile更改虚拟机ip,修改其中的config.vm.network "private_network",ip:"192.168.56.10",这个ip需要在windows的ipconfig中查到vitualbox的网卡ip,然后更改下最后一个数字就行(不能是1)。配置完后重启虚拟机。在虚拟机中ip addr就可以查看到地址了。互相ping也能ping通。

虚拟机安装docker

https://docs.docker.com/engine/install/centos/

#安装前先更新yum,不然本机无法连接虚拟机的mysql、redis等
sudo yum update
#卸载系统之前的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

# 配置镜像
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/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

配置阿里云加速镜像

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

mysql 

用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
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

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

vi /mydata/redis/conf/redis.conf
# 插入下面内容
appendonly yes
保存

docker restart redis

开发环境(简单介绍)

maven

在maven的config.xml中配置阿里云镜像,配置jdk1.8。给IDEA安装插件lombok,mybatisX。

vsCode

下载vsCode用于前端管理系统。在vsCode里安装插件。

  • Auto Close Tag
  • Auto Rename Tag
  • Chinese
  • ESlint
  • HTML CSS Support
  • HTML Snippets
  • JavaScript ES6
  • Live Server
  • open in brower
  • Vetur

安装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公钥、随便填个标题,然后赋值
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6MWhGXSKdRxr1mGPZysDrcwABMTrxc8Va2IWZyIMMRHH9Qn/wy3PN2I9144UUqg65W0CDE/thxbOdn78MygFFsIG4j0wdT9sdjmSfzQikLHFsJ02yr58V6J2zwXcW9AhIlaGr+XIlGKDUy5mXb4OF+6UMXM6HKF7rY9FYh9wL6bun9f1jV4Ydlxftb/xtV8oQXXNJbI6OoqkogPKBYcNdWzMbjJdmbq2bSQugGaPVnHEqAD74Qgkw1G7SIDTXnY55gBlFPVzjLWUu74OWFCx4pFHH6LRZOCLlMaJ9haTwT2DB/sFzOG/Js+cEExx/arJ2rvvdmTMwlv/T+6xhrMS3 [email protected]

# 测试
ssh -T [email protected]
测试成功

码云(本人用的是gitHub作为代码托管)

在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven,许可证选Apache-2.0,开发模型选生成/开发模型,开发时在dev分支,发布时在master分支,创建。

在IDEA中New–Project from version control–git–复制刚才项目的地址,如https://gitee.com/hanferm/gulimall.git

然后New Module–Spring Initializer–com.atguigu.gulimall , Artifact填 gulimall-product。Next—选择web,springcloud routin里选中openFeign。

依次创建出以下服务

  • 商品服务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粘贴到项目目录,修改他



	4.0.0

	com.atguigu.gulimall
	gulimall
	0.0.1-SNAPSHOT
	gulimall
	聚合服务

	pom

	
		gulimall-coupon
		gulimall-member
		gulimall-order
		gulimall-product
		gulimall-ware

	




在maven窗口刷新,并点击+号,找到刚才的pom.xml添加进来,发现多了个root。这样比如运行root的clean命令,其他项目也一起clean了。

修改总项目的.gitignore,把小项目里的垃圾文件在提交的时候忽略掉,比如HTLP.md。。。

在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才会提交到云端。此时就可以在浏览器中看到了。

数据库

因为已经有人贡献sql文件了,所以我们不理会下面引用部分的内容了

安装powerDesigner软件。http://forspeed.onlinedown.net/down/powerdesigner1029.zip

其他软件:
https://www.lanzous.com/b015ag33e

密码:2wre

所有的数据库数据再复杂也不建立外键,因为在电商系统里,数据量大,做外键关联很耗性能。

name是给我们看的,code才是数据库里真正的信息。

选择primary和identity作为主键。然后点preview就可以看到生成这张表的语句。

点击菜单栏database–generate database—点击确定

注意重启虚拟机和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

然后接着去sqlyog直接我们的操作,在左侧root上右键建立数据库:字符集选utf8mb4,他能兼容utf8且能解决一些乱码的问题。分别建立了下面数据库

gulimall-oms
gulimall-pms
gulimall-sms
gulimall-ums
gulimall-wms

然后打开对应的sql在对应的数据库中执行。依次执行。(注意sql文件里没有建库语句)

clone 人人开源

https://gitee.com/renrenio

1587609877028

克隆到本地:

git clone https://gitee.com/renrenio/renren-fast-vue.git

git clone https://gitee.com/renrenio/renren-fast.git

将拷贝下来的“renren-fast”删除“.git”后,拷贝到“gulimall”工程根目录下,然后将它作为gulimall的一个module

创建“gulimall_admin”的数据库,然后执行“renren-fast/db/mysql.sql”中的SQl脚本

修改“application-dev.yml”文件,默认为dev环境,修改连接mysql的url和用户名密码

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.43.125:3306/gulimall_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
            username: root
            password: root

启动“gulimall_admin”,然后访问“http://localhost:8080/renren-fast/”

1587616296253

安装node:http://nodejs.cn/download/ 选择windows下载。下载完安装。

NPM是随同NodeJS一起安装的包管理工具,如JavaScript-NPM,java-Maven。

命令行输入node -v 检查配置好了,配置npm的镜像仓库地址,再执

node -v
npm config set registry http://registry.npm.taobao.org/

PS D:\tmp\renren-fast-vue> npm config set registry http://registry.npm.taobao.org/
PS D:\tmp\renren-fast-vue> npm install
npm WARN [email protected] requires a peer of ajv@>=4.10.0 but none is installed. You must install peer dependencies yourself.
npm WARN [email protected] requires a peer of node-sass@^4.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

up to date in 17.227s
PS D:\tmp\renren-fast-vue> 
PS D:\tmp\renren-fast-vue> npm run dev

> [email protected] dev D:\tmp\renren-fast-vue
> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js

 10% building modules 5/10 modules 5 active ...-0!D:\tmp\renren-fast-vue\src\main.js(node:19864) Warning: Accessing non-existent property 'cat' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:19864) Warning: Accessing non-existent property 'cd' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'chmod' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'cp' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'dirs' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'pushd' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'popd' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'echo' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'tempdir' of module exports inside circular dependency
(node:19864) Warning: Accessing non-existent property 'pwd' of module exports inside circular dependency

然后取VScode的终端中输入 npm install,会报错,然后进行如下操作:

常见问题1:首先确保安装了python3.0以上版本,并配置全局变量

谷粒商城-个人笔记(基础篇一)_第3张图片

常见问题2:“Module build failed: Error: Cannot find module 'node-sass”

运行过程中,出现“Module build failed: Error: Cannot find module 'node-sass’报错问题”,解决方法

首先把项目文件夹下的package.json里面的node-sass4.9.0改成4.9.2(不改可能也没关系,不过我改了,防止踩坑)
然后项目文件夹下打开cmd命令窗口(和Visual Studio Code的终端命令是一样的)
执行:
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
执行成功看看有没有报错,如果没报错执行下面命令
npm install ,
没报错就是安装成功,然后使用npm run dev 就ok了
注:这么做得原理就是先单独从淘宝镜像吧nod-sass下载下来,然后再进行编译,因为这句命令好像是不成功的,(npm config set registry http://registry.npm.taobao.org/),默认从github下载,导致报错的
如果之前安装失败的。先清理 缓存
清理缓存:npm rebuild node-sass
                  npm uninstall node-sass

常见问题3:cnpm - 解决 " cnpm : 无法加载文件 C:\Users\93457\AppData\Roaming\npm\cnpm.ps1,因为在此系统上禁止运行脚本。有关详细信息 。。。 "

https://www.cnblogs.com/500m/p/11634969.html

常见问题4、npm install报错:[email protected] install: node install.js

如果执行过npm install,先删除 node_modules 文件夹,不然运行的时候可能会报错
执行下面的命令
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
再执行 npm install 即可正常下载

所有问题的根源都在“node_modules”,npm install之前,应该将这个文件夹删除,然后再进行安装和运行。

再次运行npm run dev恢复正常:

1587637858665

浏览器输入localhost:8001 就可以看到内容了,登录admin admin

IDEA项目准备

逆向工程搭建

git clone https://gitee.com/renrenio/renren-generator.git

下载到桌面后,同样把里面的.git文件删除,然后移动到我们IDEA项目目录中,同样配置好pom.xml


		gulimall-coupon
		gulimall-member
		gulimall-order
		gulimall-product
		gulimall-ware
		renren-fast
		renren-generator
	

在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(这里乱码的百度IDEA设置properties编码)

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=product
#作者
author=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=pms_

运行RenrenApplication。如果启动不成功,修改application中是port为801。访问http://localhost:801/

在网页上下方点击每页显示50个(pms库中的表),以让全部都显示,然后点击全部,点击生成代码。下载了压缩包

解压压缩包,把main放到gulimall-product的同级目录下。

然后在项目上右击(在项目上右击很重要)new modules— maven—然后在name上输入gulimall-common。

在pom.xml中也自动添加了gulimall-common

在common项目的pom.xml中添加



    com.baomidou
    mybatis-plus-boot-starter
    3.3.2



    org.projectlombok
    lombok
    1.18.8



    org.apache.httpcomponents
    httpcore
    4.4.13


    commons-lang
    commons-lang
    2.6

我们把每个微服务里公共的类和依赖放到common里。

tips: shift+F6修改项目名

然后在product项目中的pom.xml中加入下面内容


    com.atguigu.gulimall
    gulimall-common
    0.0.1-SNAPSHOT

复制

  • renren-fast----utils包下的Query和PageUtils、R、Constant复制到common项目的java/com.atguigu.common.utils下

  • 把@RequiresPermissions这些注解掉,因为是shiro的

  • 复制renren-fast中的xss包粘贴到common的com.atguigu.common目录下。

  • 还复制了exception文件夹,对应的位置关系自己观察一下就行

  • 注释掉product项目下类中的//import org.apache.shiro.authz.annotation.RequiresPermissions;,他是shiro的东西

  • 注释renren-generator\src\main\resources\template/Controller中所有的@RequiresPermissions。## import org.apache.shiro.authz.annotation.RequiresPermissions;

总之什么报错就去fast里面找。重启逆向工程。重新在页面上得到压缩包。重新解压出来,不过只把里面的controller复制粘贴到product项目对应的目录就行。

测试

测试与整合商品服务里的mybatisplus

https://mp.baomidou.com/guide/quick-start.html#配置

在common的pom.xml中导入



    mysql
    mysql-connector-java
    8.0.17



    javax.servlet
    servlet-api
    2.5
    provided

删掉common里xss/xssfiler和XssHttpServletRequestWrapper

在product项目的resources目录下新建application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.43.125: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

classpath 和 classpath* 区别:
classpath:只会到你的class路径中查找找文件;
classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找

classpath*的使用:当项目中有多个classpath路径,并同时加载多个classpath路径下(此种情况多数不会遇到)的文件,*就发挥了作用,如果不加*,则表示仅仅加载第一个classpath路径。

然而执行后能通过,但是数据库中文显示乱码,所以我模仿逆向工程,把上面的配置url改为

url: jdbc:mysql://192.168.56.10:3306/gulimall-pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

正常了。

然后在主启动类上加上注解@MapperScan()

@MapperScan("com.atguigu.gulimall.product.dao")
@SpringBootApplication
public class gulimallProductApplication {


    public static void main(String[] args) {

        SpringApplication.run(gulimallProductApplication.class, args);
    }

}

然后去测试,先通过下面方法给数据库添加内容

@SpringBootTest
class gulimallProductApplicationTests {
    @Autowired
    BrandService brandService;

    @Test
    void contextLoads() {
        BrandEntity brandEntity = new BrandEntity();
        brandEntity.setDescript("哈哈1哈");
        brandEntity.setName("华为");
        brandService.save(brandEntity);
        System.out.println("保存成功");
    }
}

在数据库中就能看到新增数据了

@SpringBootTest
class gulimallProductApplicationTests {
    @Autowired
    BrandService brandService;

    @Test
    void contextLoads() {
        BrandEntity brandEntity = new BrandEntity();
        brandEntity.setBrandId(1L);
        brandEntity.setDescript("修改");
        brandService.updateById(brandEntity);
    }
}

coupon

重新打开generator逆向工程,修改generator.properties

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=coupon
#作者
autho=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=sms_

修改yml数据库信息

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.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

server:
  port: 7000

端口号后面会设置,这里提前设置好了

启动生成RenrenApplication.java,运行后去浏览器80端口查看,同样让他一页全显示后选择全部后生成。生成后解压复制到coupon项目对应目录下。

让coupon也依赖于common,修改pom.xml


    com.atguigu.gulimall
    gulimall-common
    0.0.1-SNAPSHOT

resources下src包先删除

添加application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.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


运行gulimallCouponApplication.java

http://localhost:8080/coupon/coupon/list

{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}

member

重新使用代码生成器生成ums

模仿上面修改下面两个配置

代码生成器里:

    url: jdbc:mysql://192.168.56.10:3306/gulimall-ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

 

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=member
#作者
author=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=ums_

重启RenrenApplication.java,然后同样去浏览器获取压缩包解压到对应member项目目录

member也导入依赖


    com.atguigu.gulimall
    gulimall-common
    0.0.1-SNAPSHOT

同样新建application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.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

server:
  port: 8000

order端口是9000,product是10000,ware是11000。

以后比如order系统要复制多份,他的端口计算9001、9002。。。

重启web后,http://localhost:8000/member/growthchangehistory/list

{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}

order

修改代码生成器

    url: jdbc:mysql://192.168.56.10:3306/gulimall-oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

#代码生成器,配置信息

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=order
#作者
author=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=oms_

运行RenrenApplication.java重新生成后去下载解压放置。

application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.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
      
server:
  port: 9000

POMxml


    com.atguigu.gulimall
    gulimall-common
    0.0.1-SNAPSHOT

启动gulimallOrderApplication.java

http://localhost:9000/order/order/list

{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}

ware

修改代码生成器

    url: jdbc:mysql://192.168.56.10:3306/gulimall-wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

#代码生成器,配置信息

# 主目录
mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
#模块名
moduleName=ware
#作者
author=hh
#email
[email protected]
#表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
# 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
tablePrefix=wms_

运行RenrenApplication.java重新生成后去下载解压放置。

application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.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
      
server:
  port: 11000

POMxml


    com.atguigu.gulimall
    gulimall-common
    0.0.1-SNAPSHOT

启动gulimallWareApplication.java

http://localhost:11000/ware/wareinfo/list

{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}

SpringCloud Alibaba简介

阿里18年开发的微服务一站式解决方案。https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

  • 注册中心:
  • 配置中心:
  • 网关:

netflix把feign闭源了,spring cloud开了个open feign

在common的pom.xml中加入


    
        
            com.alibaba.cloud
            spring-cloud-alibaba-dependencies
            2.2.0.RELEASE
            pom
            import
        
    

上面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理

Nacos

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

作为我们的注册中心和配置中心。

注册中心文档:https://github.com/alibaba/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example

其他文档在该项目上层即可找到,下面读一读官网给的介绍就会用了。流程:下载–解压–/bin/startup.cmd—在某个项目里properties里写spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848(yaml同理)。

如何接入:

在启动示例进行演示之前,我们先了解一下 Spring Cloud 应用如何接入 Nacos Discovery。 注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。

  1. 首先,修改 pom.xml 文件,引入 Nacos Discovery Starter。

    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-nacos-discovery
    
    

    放到common里

  2. 在应用的 /src/main/resources/application.properties 配置文件中配置 Nacos Server 地址(或在yaml中按格式配置)

    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    

我们要配置nacos服务器的地址,也就是注册中心地址,但是我们还没有nacos服务器,所以我们先在下面按照"启动nacos server"创建nacos服务器

  1. 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能

     @SpringBootApplication
     @EnableDiscoveryClient
     public class ProviderApplication {
    
     	public static void main(String[] args) {
     		SpringApplication.run(Application.class, args);
     	}
    
     	@RestController
     	class EchoController {
     		@GetMapping(value = "/echo/{string}")
     		public String echo(@PathVariable String string) {
     				return string;
     		}
     	}
     }
    

启动 Nacos Server:

  1. 首先需要获取 Nacos Server,支持直接下载和源码构建两种方式。
    1. 直接下载:Nacos Server 下载页。解压zip,双击bin里的startup.cmd就启动了。
    2. 源码构建:进入 Nacos Github 项目页面,将代码 git clone 到本地自行编译打包,参考此文档。推荐使用源码构建方式以获取最新版本
  2. 启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。
    1. Linux/Unix/Mac 操作系统,执行命令 sh startup.sh -m standalone
    2. Windows 操作系统,执行命令 cmd startup.cmd

应用启动

  1. 增加配置,在 nacos-discovery-provider-example 项目的 /src/main/resources/application.properties 中添加基本配置信息

     spring.application.name=service-provider
     server.port=18082
    
  2. 启动应用,支持 IDE 直接启动和编译打包后启动。

    1. IDE直接启动:找到 nacos-discovery-provider-example 项目的主类 ProviderApplication,执行 main 方法启动应用。
    2. 打包编译后启动:在 nacos-discovery-provider-example 项目中执行 mvn clean package 将工程编译打包,然后执行 java -jar nacos-discovery-provider-example.jar启动应用。

验证

查询服务

在浏览器输入此地址 http://127.0.0.1:8848/nacos/v1/ns/catalog/instances?serviceName=service-provider&clusterName=DEFAULT&pageSize=10&pageNo=1&namespaceId=,并点击跳转,可以看到服务节点已经成功注册到 Nacos Server。

在coupon的gulimallCouponApplication.java加上@EnableDiscoveryClient,导入包,然后重开项目。

http://127.0.0.1:8848/nacos/ 账号面nacos

最后application.yml内容,配置了服务中心名和当前模块名字

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall-sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-coupon


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 7000

然后依次给member、配置上面的yaml,改下name就行。再给每个项目配置类上加上注解@EnableDiscoveryClient

测试member和coupon的远程调用

想要获取当前会员领取到的所有优惠券。先去注册中心找优惠券服务,注册中心调一台优惠券服务器给会员,会员服务器发送请求给这台优惠券服务器,然后对方响应。

Feign与注册中心

spring cloud feign

声明式远程调用

feign是一个声明式的HTTP客户端,他的目的就是让远程调用更加简单。给远程服务发的是HTTP请求。

会员服务想要远程调用优惠券服务,只需要给会员服务里引入openfeign依赖,他就有了远程调用其他服务的能力。


    org.springframework.cloud
    spring-cloud-starter-openfeign

我们之前在member的pom.xml已经引用过了(微服务)。

在coupon中修改如下的内容

@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要调用东西的

package com.atguigu.gulimall.member;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/*
* 想要远程调用的步骤:
* 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
@SpringBootApplication
public class gulimallMemberApplication {

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

那么要调用什么东西呢?就是我们刚才写的优惠券的功能,复制函数部分,在member的com.atguigu.gulimall.member.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;

@FeignClient("gulimall-coupon") //告诉spring cloud这个接口是一个远程客户端,要调用coupon服务,再去调用coupon服务/coupon/coupon/member/list对应的方法
public interface CouponFeignService {
    @RequestMapping("/coupon/coupon/member/list")//注意写全优惠券类上还有映射//注意我们这个地方不熟控制层,所以这个请求映射请求的不是我们服务器上的东西,而是nacos注册中心的
    public R membercoupons();//得到一个R对象
}

然后我们在member的控制层写一个测试请求

@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"));
    }

重新启动服务

http://localhost:8000/member/member/coupons

{"msg":"success","code":0,"coupons":[{"id":null,"couponType":null,"couponImg":null,"couponName":"满100-10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}],"member":{"id":null,"levelId":null,"username":null,"password":null,"nickname":"会员昵称张三","mobile":null,"email":null,"header":null,"gender":null,"birth":null,"city":null,"job":null,"sign":null,"sourceType":null,"integration":null,"growth":null,"status":null,"createTime":null}}

上面内容很重要,我们停留5分钟体会一下。

coupon里的R.ok()是什么

public class R extends HashMap {//R继承了HashMap
    // ok是个静态方法,new了一个R对象,并且
    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);//调用了super.put(key, value);,即hashmap的put
        return r;
    }
}

coupon里的控制层就是new了个couponEntity然后放到hashmap(R)里而已。

配置中心

我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。

官方教程:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

引入配置中心依赖,放到common中


     com.alibaba.cloud
     spring-cloud-starter-alibaba-nacos-config
 

在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别application.properties高

# 改名字,对应nacos里的配置文件名
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

原来的方式:

@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,他是环境里的变量
    private String name;
    @Value("${coupon.user.age}")
    private Integer age;
    @RequestMapping("/test")
    public R test(){

        return R.ok().put("name",name).put("age",age);
    }

浏览器去nacos里的配置列表,点击+号,data ID:gulimall-coupon.properties,配置

# gulimall-coupon.properties
coupon.user.name="配置中心"      
coupon.user.age=12

然后点击发布。重启coupon,http://localhost:7000/coupon/coupon/test

{"msg":"success","code":0,"name":"配置中心","age":12}

但是修改肿么办?实际生产中不能重启应用。在coupon的控制层上加@RefreshScope

@RefreshScope
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,他是环境里的变量
    private String name;
    @Value("${coupon.user.age}")
    private Integer age;
    @RequestMapping("/test")
    public R test(){

        return R.ok().put("name",name).put("age",age);
    }

重启后,在nacos浏览器里修改配置,修改就可以观察到能动态修改了

nacos的配置内容优先于项目本地的配置内容。

配置中心进阶

在nacos浏览器中还可以配置:

  • 命名空间:用作配置隔离。(一般每个微服务一个命名空间)

    • 默认public。默认新增的配置都在public空间下

    • 开发、测试、开发可以用命名空间分割。properties每个空间有一份。

    • 在bootstrap.properties里配置(测试完去掉,学习不需要)

      # 可以选择对应的命名空间 # 写上对应环境的命名空间ID
      spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
      
    • 也可以为每个微服务配置一个命名空间,微服务互相隔离

  • 配置集:一组相关或不相关配置项的集合。

  • 配置集ID:类似于配置文件名,即Data ID

  • 配置分组:默认所有的配置集都属于DEFAULT_GROUP。双十一,618,双十二

    # 更改配置分组
    spring.cloud.nacos.config.group=DEFAULT_GROUP
    

最终方案:每个微服务创建自己的命名空间,然后使用配置分组区分环境(dev/test/prod)

加载多配置集

我们要把原来application.yml里的内容都分文件抽离出去。我们在nacos里创建好后,在coupons里指定要导入的配置即可。

bootstrap.properties

spring.application.name=gulimall-coupon

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# 可以选择对应的命名空间 # 写上对应环境的命名空间ID
spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
# 更改配置分组
spring.cloud.nacos.config.group=dev

#新版本不建议用下面的了
#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

spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yml
spring.cloud.nacos.config.extension-configs[1].group=dev
spring.cloud.nacos.config.extension-configs[1].refresh=true

spring.cloud.nacos.config.extension-configs[2].data-id=other.yml
spring.cloud.nacos.config.extension-configs[2].group=dev
spring.cloud.nacos.config.extension-configs[2].refresh=true

输出内容有

2020-06-25 00:04:13.677  WARN 17936 --- [           main] c.a.c.n.c.NacosPropertySourceBuilder     : Ignore the empty nacos configuration and get it based on dataId[gulimall-coupon] & group[dev]

2020-06-25 00:04:13.681  INFO 17936 --- [           main] b.c.PropertySourceBootstrapConfiguration :
Located property source: [
BootstrapPropertySource {name='bootstrapProperties-gulimall-coupon.properties,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-gulimall-coupon,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-other.yml,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-mybatis.yml,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-datasource.yml,dev'}]

网关

发送请求需要知道商品服务的地址,如果商品服务器有123服务器,1号掉线后,还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上线还是下线。

请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。

所以我们使用spring cloud的gateway组件做网关功能。

网关是请求浏览的入口,常用功能包括路由转发,权限校验,限流控制等。springcloud gateway取到了zuul网关。

https://spring.io/projects/spring-cloud-gateway

参考手册:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/

三大核心概念

  • 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,然后给指定的服务,再返回回来给客户端。

有很多断言。

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Cookie=mycookie,mycookievalue

代表数组。只有断言成功了,才路由到指定的地址。

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - name: Cookie
          args:
            name: mycookie
            regexp: mycookievalue

创建,使用initilizer,Group:com.atguigu.gulimall,Artifact: gulimall-gateway,package:com.atguigu.gulimall.gateway。 搜索gateway选中。

pom.xml里加上common依赖, 修改jdk版本,

开启注册服务发现@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 填写配置中心地址

spring.application.name=gulimall-gateway

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=bfa85f10-1a9a-460c-a7dc-efa961b45cc1

nacos里创建命名空间gateway,然后在命名空间里创建文件guilmall-gateway.yml

spring:
    application:
        name: gulimall-gateway

在项目里创建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

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

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

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

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

        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?.*),/renren-fast/$\{segment}



  ## 前端项目,/api
## http://localhost:88/api/captcha.jpg   http://localhost:8080/renren-fast/captcha.jpg
## http://localhost:88/api/product/category/list/tree http://localhost:10000/product/category/list/tree

测试 localhost:8080/hello?url=baidu

 


三、前端


前后端对比

1、ES6

ECMAScript6.0(以下简称ES6,ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本),是JavaScript语言的下一代标准,2015年6月正式发布,从ES6开始的版本号采用年号,如ES2015,就是ES6。ES2016就是ES7。

ECMAScript是规范,JS的规范的具体实现。

打开VSCode—打开文件夹—新建es6文件夹—新建文件1、let.html—shift+!+Enter生成模板。填入下面内容后,右键open with live server

2、安装vue

# 最新稳定版
$ npm install vue

3、vue声明式渲染




    
    
    
    Document


    

    



  • var在{}之外也起作用
  • let在{}之外不起作用
  • var多次声明同一变量不会报错,let多次声明会报错,只能声明一次。
  • var 会变量提升(打印和定义可以顺序反)。let 不存在变量提升(顺序不能反)
  • let的const声明之后不允许改变

4、解构表达式.html




    
    
    
    Document



    


  • 支持let arr = [1,2,3]; let [a,b,c] = arr;这种语法
  • 支持对象解析:const { name: abc, age, language } = person; 冒号代表改名
  • 字符串函数
  • 支持一个字符串为多行
  • 占位符功能 ${}

5、函数优化.html





    
    
    
    Document



    


  • 原来想要函数默认值得这么写b = b || 1; 现在可以直接写了function add2(a, b = 1) {
  • 函数不定参数function fun(...values) {
  • 支持箭头函数(lambda表达式),还支持使用{}结构传入对象的成员

6、对象优化.html




    
    
    
    Document


    



  • 可以获取map的键值对等Object.keys()、values、entries
  • Object.assgn(target,source1,source2) 合并
  • const person2 = { age, name }//声明对象简写
  • …代表取出该对象所有属性拷贝到当前对象。let someone = { …p1 }

7、map和reduce.html




    
    
    
    Document


    
    


  • map处理,arr = arr.map(item=> item*2);
  • reduce。arr.reduce((原来的值,处理后的值即return的值)=>{

8、promise.html

优化异步操作




    
    
    
    Document
    


    



corse_score_10.json 得分

{
    "id": 100,
    "score": 90
}

user.json 用户

{
    "id": 1,
    "name": "zhangsan",
    "password": "123456"
}

user_corse_1.json 课程

{
    "id": 10,
    "name": "chinese"
}

以前嵌套ajax的时候很繁琐。

  • 把Ajax封装到Promise中,赋值给let p
  • 在Ajax中成功使用resolve(data),失败使用reject(err)
  • p.then().catch()

模块化

模块化就是把代码进行拆分,方便重复利用。类似于java中的导包,而JS换了个概念,是导模块。

模块功能主要有两个命令构成 export 和import

  • export用于规定模块的对外接口
  • import用于导入其他模块提供的功能

user.js

var name = "jack"
var age = 21
function add(a,b){
    return a + b;
}

export {name,age,add}

hello.js

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

export default {
    sum(a, b) {
        return a + b;
    }
}
// export {util}

//`export`不仅可以导出对象,一切JS变量都可以导出。比如:基本类型变量、函数、数组、对象。

main.js

import abc from "./hello.js"
import {name,add} from "./user.js"

abc.sum(1,2);
console.log(name);
add(1,3);

 

 


四、Vue


因为这是前端的内容,代码多比较占用页面,所以另写到CSDN上。

https://blog.csdn.net/hancoder/article/details/107007605

MVVM思想

  • M:module 包括数据和一些基本操作
  • V:view 视图,页面渲染结果
  • VM:View-module,模型与视图间的双向操作(无需开发人员干涉)

视图和数据通过VM绑定起来,模型里有变化会自动地通过Directives填写到视图中,视图表单中添加了内容也会自动地通过DOM Listeners保存到模型中。

教程:https://cn.vuejs.org/v2/guide/

1、安装:

  • 直接下载并用 

    在VSCode中安装vue 2 snippets插件,在谷歌浏览器中安装

    3、指令

    3.1、v-text、v-html.html

    
    
    
        
        
        
        Document
    
    
       
        
    { {msg}} { {1+1}} { {hello()}}

    插值表达式

    花括号:只能写在标签体力(<>这里)

    插值闪烁

    3.2、v-bind.html

    
    
    
        
        
        
        Document
    
    
    
        
        
    gogogo 你好

    3.3、v-model.html

    双向绑定

    
    
    
        
        
        
        Document
    
    
    
        
        
    精通的语言: java
    PHP
    Python
    选中了 { {language.join(",")}}

    3.4、v-on.html

    
    
    
    
        
        
        
        Document
    
    
    
        

    有{ {num}}个赞

    大div
    小div
    去百度

    提示:

    3.5、v-for.html

    
    
    
    
        
        
        
        Document
    
    
    
    
        
    • 当前索引:{ {index}} ==> { {user.name}} ==> { {user.gender}} ==>{ {user.age}}
      对象信息: { {k}}=={ {v}}=={ {i}};

    3.6、v-if和v-show.html

    
    
    
    
        
        
        
        Document
    
    
    
        
        

    if=看到我....

    show=看到我

    3.7、v-else和v-else-if.html

    
    
    
    
        
        
        
        Document
    
    
    
        
    { {random}}

    看到我啦?! >= 0.75

    看到我啦?! >= 0.5

    看到我啦?! >= 0.2

    看到我啦?! < 0.2

    4、计算属性和侦听器

    4.1、计算属性和侦听器.html

    
    
    
    
        
        
        
        Document
    
    
    
        
    • 西游记; 价格:{ {xyjPrice}},数量:
    • 水浒传; 价格:{ {shzPrice}},数量:
    • 总价:{ {totalPrice}}
    • { {msg}}

    4.2、过滤器.html

    
    
    
    
        
        
        
        Document
    
    
    
        
        
    • { {user.id}} ==> { {user.name}} ==> { {user.gender == 1?"男":"女"}} ==> { {user.gender | genderFilter}} ==> { {user.gender | gFilter}}

    4.3、组件化

    
    
    
    
        
        
        
        Document
    
    
    
    
        

    4.4、生命周期钩子函数

    
    
    
    
        
        
        
        Document
    
    
    
        
    { {num}}

    { {name}},有{ {num}}个人点赞

    5、vue模块化开发

    5.1、全局安装webpack

    npm install webpack -g

    5.2、全局安装vue脚手架

    npm install -g @vue/cli-init

    5.3、初始化vue项目

    vue init webpack appName    :vue脚手架使用webpack,初始化一个appname项目 

    5.4、启动vue项目

    项目的package.json 只能够有scripts,代表我们能执行的命令

    npm start = npm run dev :启动项目

    npm run build: 将项目打包

    5.5、注意

    遇到的问题:

    'vue' 不是内部或外部命令,也不是可运行的程序 或批处理文件

    解决办法:

    安装:vue

    npm install -g vue
    npm install -g  @vue/cli 

    然后就出现标题的问题了
    解决方案如下:

    1.npm config list 查看一下npm 的配置信息,下图

    谷粒商城-个人笔记(基础篇一)_第4张图片

    然后找到这个红色线里面的路径,看看有没有vue.md的文件:

    然后把这个路径添加的系统环境变量的path里面,如下图:

    谷粒商城-个人笔记(基础篇一)_第5张图片

    最后在来检查一波

    vue --version 
    --3.8.2
    

    6、 element ui

    官网: https://element.eleme.cn/#/zh-CN/component/installation

    安装

    npm i element-ui -S
    

    在 main.js 中写入以下内容:

    import ElementUI  from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css';
    
    Vue.use(ElementUI);

     


    五、功能模块实现


    接口文档:https://easydoc.xyz/doc/75716633/ZUqEdvA4/LnjzZHPj

    1、 商品系统

    1.1、分类维护

    1)、递归树形结构获取数据

    谷粒商城-个人笔记(基础篇一)_第6张图片

    分类维护接口文档:获取所有分类及子分类

    GET:   product/category/list/tree

    接口描述

    获取所有分类以及子分类,并返回json树形结构

    响应参数

    参数名 参数类型 描述
    code int 0-成功,其他业务码-失败
    msg string 提示消息
    data array 返回的所有菜单

    响应示例

    {
        "code": 0,
        "msg": "success",
        "data": [{
            "catId": 1,
            "name": "图书、音像、电子书刊",
            "parentCid": 0,
            "catLevel": 1,
            "showStatus": 1,
            "sort": 0,
            "icon": null,
            "productUnit": null,
            "productCount": 0,
            "children": []
        }]
    }

    在注册中心中“product”命名空间中,创建“gulimall-product.yml”配置文件:

    将“application.yml”内容拷贝到该配置文件

    #端口
    server:
      port: 10000
    
    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://192.168.43.125:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8
    #    url: jdbc:mysql://192.168.31.40:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8
        driver-class-name: com.mysql.jdbc.Driver
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
        alicloud:
          access-key: LTAI4FzhuX1rFQt2yWDmgwsn
          secret-key: z3ZFoNHoCaG1V1qmQ32Dm4OTLNP5fA
          oss:
            endpoint: oss-cn-shanghai.aliyuncs.com
    
    mybatis-plus:
      mapper-locations: classpath:/mapper/**/*.xml
      global-config:   #配置每个实体类的主键自增长
        db-config:
          id-type: auto
          logic-delete-value: 1
          logic-not-delete-value: 0
    logging:
      level:
        com.atguigu.gulimall: debug

     在本地创建“bootstrap.properties”文件,指明配置中心的位置和使用到的配置文件:

    spring.application.name=gulimall-product
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.namespace=2128d268-4782-4ec8-88ee-ac5254696ea8
    spring.cloud.nacos.config.extension-configs[0].data-id=gulimall-product.yml
    spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
    spring.cloud.nacos.config.extension-configs[0].refresh=true

    然后启动gulimall-product,查看到该服务已经出现在了nacos的注册中心中了

    修改“com.atguigu.gulimall.product.entity.CategoryEntity”类,代码如下:

    @Data
    @TableName("pms_category")
    public class CategoryEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * 分类id
    	 */
    	@TableId
    	private Long catId;
    	/**
    	 * 分类名称
    	 */
    	private String name;
    	/**
    	 * 父分类id
    	 */
    	private Long parentCid;
    	/**
    	 * 层级
    	 */
    	private Integer catLevel;
    	/**
    	 * 是否显示[0-不显示,1显示]
    	 */
    	@TableLogic(value = "1",delval = "0")
    	private Integer showStatus;
    	/**
    	 * 排序
    	 */
    	private Integer sort;
    	/**
    	 * 图标地址
    	 */
    	private String icon;
    	/**
    	 * 计量单位
    	 */
    	private String productUnit;
    	/**
    	 * 商品数量
    	 */
    	private Integer productCount;
    	/**
    	 * 子分类
    	 */
    	@JsonInclude(JsonInclude.Include.NON_EMPTY) //属性为NULL 不序列化
    	@TableField(exist = false) //表示当前属性不是数据库的字段,但在项目中必须使用,这样在新增等使用bean的时候,mybatis-plus就会忽略这个,不会报错
    	private List children;
    }
    

    修改“com.atguigu.gulimall.product.controller.CategoryController”类,添加如下代码:

    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){
        List list = categoryService.listWithTree();
    
        return R.ok().put("data", list);
    }

    修改‘com.atguigu.gulimall.product.service.CategoryService’类,代码如下 

    List listWithTree();

    如何区别是哪种分类级别?

    答:可以通过分类的parent_cid来进行判断,如果是一级分类,其值为0.

     修改‘com.atguigu.gulimall.product.service.impl.CategoryServiceImpl’类,代码如下 

    @Override
        public List listWithTree() {
            //1、查出所有分类
            List entities = baseMapper.selectList(null);
            //2、组装成父子的树形结构
            List level1Menus = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
            ).map((menu)->{
                menu.setChildren(getChildrens(menu,entities));
                return menu;
            }).sorted((menu1,menu2)->{
                return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 :menu2.getSort());
            }).collect(Collectors.toList());
            //2.1、找到所有的一级分类
            return level1Menus;
        }
    
    
        //递归查找所有菜单的子菜单
        private List getChildrens(CategoryEntity root,List all){
            List children = all.stream().filter(categoryEntity -> {
                return categoryEntity.getParentCid() == root.getCatId();
            }).map(categoryEntity -> {
                //1、找到子菜单
                categoryEntity.setChildren(getChildrens(categoryEntity, all));
                return categoryEntity;
            }).sorted((menu1, menu2) -> {
                //2、菜单的排序
                return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 :menu2.getSort());
            }).collect(Collectors.toList());
    
    
            return children;
        }
    

    测试:http://localhost:10000/product/category/list/tree

    下面是得到的部分JSON数据 

     [
      {
        "catId": 1,
        "name": "图书、音像、电子书刊",
        "parentCid": 0,
        "catLevel": 1,
        "showStatus": 1,
        "sort": 0,
        "icon": null,
        "productUnit": null,
        "productCount": 0,
        "childCategoryEntity": [
          {
            "catId": 22,
            "name": "电子书刊",
            "parentCid": 1,
            "catLevel": 2,
            "showStatus": 1,
            "sort": 0,
            "icon": null,
            "productUnit": null,
            "productCount": 0,
            "childCategoryEntity": [
              {
                "catId": 165,
                "name": "电子书",
                "parentCid": 22,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              },
              {
                "catId": 166,
                "name": "网络原创",
                "parentCid": 22,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              },
              {
                "catId": 167,
                "name": "数字杂志",
                "parentCid": 22,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              },
              {
                "catId": 168,
                "name": "多媒体图书",
                "parentCid": 22,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              }
            ]
          },
          {
            "catId": 23,
            "name": "音像",
            "parentCid": 1,
            "catLevel": 2,
            "showStatus": 1,
            "sort": 0,
            "icon": null,
            "productUnit": null,
            "productCount": 0,
            "childCategoryEntity": [
              {
                "catId": 169,
                "name": "音乐",
                "parentCid": 23,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              },
              {
                "catId": 170,
                "name": "影视",
                "parentCid": 23,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              },
              {
                "catId": 171,
                "name": "教育音像",
                "parentCid": 23,
                "catLevel": 3,
                "showStatus": 1,
                "sort": 0,
                "icon": null,
                "productUnit": null,
                "productCount": 0,
                "childCategoryEntity": []
              }
            ]
          },
          {

    启动后端项目renren-fast

    启动前端项目renren-fast-vue:

    npm run dev

    访问: http://localhost:8001/#/login

    创建一级菜单:

    谷粒商城-个人笔记(基础篇一)_第7张图片

    创建完成后,在后台的管理系统中会创建一条记录:

    然后创建子菜单:

    谷粒商城-个人笔记(基础篇一)_第8张图片

    创建renren-fast-vue\src\views\modules\product目录,之所以是这样来创建,是因为product/category,对应于product-category

    在该目录下,新建“category.vue”文件: