1、初始化数据库
-
安装数据库连接软件sqlyog
-
在阿里网盘下载(IT技术学习 - gulimall-soft)
-
下载安装
-
证书秘钥
名称:any 证书秘钥:dd987f34-f358-4894-bd0f-21f3f04be9c1
-
-
创建数据库(服务器中的数据库,每个服务分别对应一个数据库)
gulimall_oms //订单系统 gulimall_pms //商品系统 gulimall_sms //sell营销系统 gulimall_ums //用户系统 gulimall_wms //库存系统 gulimall_admin //后台管理系统
-
分别运行下面gitee中的sql语句
https://gitee.com/dongHangDongHang/gulimall/tree/master/sql
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gXhpB743-1684465170277)(images/谷粒商城项目笔记/image-20220502174252737.png)]
-
打开gulimall_pms库,运行sql文件夹里面的pms_catelog.sql。(这个表是商品分类表,也就是三级分类)
-
打开gulimall_pms库,运行sql文件夹里面的gulimall_pms_data.sql。
-
打开gulimall_admin库,mysql.sql文件,位置:https://gitee.com/dongHangDongHang/gulimall/blob/master/renren-fast/db/mysql.sql
-
打开gulimall_admin库,运行sql文件夹里面的sys_menus.sql。
-
初始化后台管理系统数据库(这里放在腾讯云)
- 看这里:https://gitee.com/dongHangDongHang/renren.git
2、配置nacos
下载地址:https://github.com/alibaba/nacos/releases/tag/1.1.3
(使用1.1.3 win版本)(阿里云盘:IT技术学习 - gulimall-soft)
-
初始化数据库
-
创建数据库 nacos_config
-
执行数据库文件(位置:nacos/conf/nacos-mysql.sql)
-
-
修改application.properties文件
-
位置:nacos/conf/application.properties
-
在该文件末尾添加
#数据源平台换成mysql spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://124.222.248.51:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=19950420
-
-
重启nacos
-
把数据库配置连接到腾讯云,数据和库都已存在,直接连上就可使用。
-
为各个服务创建命名空间
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1QFISpkr-1684465170278)(images/谷粒商城项目笔记/image-20220614163129531.png)]
3、虚拟机vmware
-
安装vmware (阿里云盘:IT技术学习 - gulimall-soft)
- 网址:https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html
- 下载地址:https://www.vmware.com/go/getworkstation-win
- vmware16pro许可证密钥最新
- ZF3R0-FHED2-M80TY-8QYGC-NPKYF
- YF390-0HF8P-M81RQ-2DXQE-M2UT6
- ZF71R-DMX85-08DQY-8YMNC-PPHV8
-
本机搭建Linux (centos)
-
点击 创建新的虚拟机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dj2f3JNM-1684465170279)(images\谷粒商城项目笔记\image-20220611115637392.png)]
-
点击 自定义 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6T8K7AK-1684465170279)(images\谷粒商城项目笔记\image-20220611115742982.png)]
-
点击 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5zHCuR6-1684465170279)(images\谷粒商城项目笔记\image-20220611115808666.png)]
-
点击 稍后安装操作系统 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6bLgZ73K-1684465170280)(images\谷粒商城项目笔记\image-20220611115842508.png)]
-
点击 Linux CentOS 7 64位 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQiLmxpP-1684465170280)(images\谷粒商城项目笔记\image-20220611115954635.png)]
-
修改虚拟机名称slave 修改位置 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjinCf0r-1684465170280)(images\谷粒商城项目笔记\image-20220611120309673.png)]
-
处理器配置设置 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eRVpsLTu-1684465170280)(images\谷粒商城项目笔记\image-20220611120413713.png)]
-
内存3072 下一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J23mBqW9-1684465170281)(images/谷粒商城项目笔记/image-20220616121512418.png)]
-
网络类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZLHPbr1-1684465170281)(images\谷粒商城项目笔记\image-20220611120525441.png)]
-
虚拟机向导
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBEktHW5-1684465170281)(images\谷粒商城项目笔记\image-20220611120545407.png)]
-
磁盘类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZtrxyH6-1684465170281)(images\谷粒商城项目笔记\image-20220611120601681.png)]
-
选择磁盘
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IEHvSyCs-1684465170281)(images\谷粒商城项目笔记\image-20220611120626001.png)]
-
指定磁盘容量50G
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dS5GeUsw-1684465170282)(images\谷粒商城项目笔记\image-20220611120712407.png)]
-
指定磁盘文件(直接下一步)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QnPfj0Yt-1684465170282)(images\谷粒商城项目笔记\image-20220611120801042.png)]
-
点击完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uYwPci1f-1684465170283)(images\谷粒商城项目笔记\image-20220611120823360.png)]
-
点击 编辑虚拟机设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KkQAz2sE-1684465170283)(images\谷粒商城项目笔记\image-20220611120909225.png)]
-
点击 CD/DVD 使用ISO映像文件 浏览
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDtalc6C-1684465170283)(images\谷粒商城项目笔记\image-20220611120951394.png)]
-
选择镜像文件所在位置(下载地址:http://ftp.sjtu.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso)(阿里云盘)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0yKs5wL-1684465170284)(images\谷粒商城项目笔记\image-20220611121126684.png)]
-
点击 确定
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPdE3XDG-1684465170284)(images\谷粒商城项目笔记\image-20220611121357267.png)]
-
点击 开启此虚拟机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7oB1YSym-1684465170284)(images\谷粒商城项目笔记\image-20220611121437787.png)]
-
鼠标点进去 点回车
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szrH2cjI-1684465170285)(images\谷粒商城项目笔记\image-20220611121521066.png)]
-
继续点 回车
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NaiLuRWS-1684465170285)(images\谷粒商城项目笔记\image-20220611121557554.png)]
-
点击 ESC (停止检测)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I394w7Wq-1684465170285)(images\谷粒商城项目笔记\image-20220611121729297.png)]
-
选择中文简体 继续
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8EWc8qSF-1684465170286)(images\谷粒商城项目笔记\image-20220611121844324.png)]
-
稍等片刻,待出现如下时,点击软件选择
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ethlOkK-1684465170286)(images\谷粒商城项目笔记\image-20220611122027105.png)]
-
选择 基础设施服务器 完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVdNZBZZ-1684465170286)(images\谷粒商城项目笔记\image-20220611122116685.png)]
-
稍等片刻,待出现如下时,点击 安装位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8tgeASB-1684465170287)(images\谷粒商城项目笔记\image-20220611122224033.png)]
-
点击 完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ugwgnnUx-1684465170287)(images\谷粒商城项目笔记\image-20220611122253483.png)]
-
点击 网络和主机名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qQPfUMZ-1684465170287)(images\谷粒商城项目笔记\image-20220611122405337.png)]
-
点击 配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gE9teINH-1684465170288)(images\谷粒商城项目笔记\image-20220611122459488.png)]
-
点击 常规 选中-可用时自动链接到这个网络
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-09ebzzPo-1684465170288)(images\谷粒商城项目笔记\image-20220611122628170.png)]
-
查看网段
-
点击 编辑 虚拟网络编辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0zLa08d-1684465170288)(images\谷粒商城项目笔记\image-20220611131232733.png)]
-
复制好这个网段 192.168.91.0
-
-
按如下配置,完了点保存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wzvywQP9-1684465170288)(images\谷粒商城项目笔记\image-20220611131755465.png)]
-
设置主机名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKme2d0p-1684465170289)(images\谷粒商城项目笔记\image-20220611132013112.png)]
-
点击 开始安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPMoryZI-1684465170289)(images\谷粒商城项目笔记\image-20220611132047986.png)]
-
设置 root密码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6XSMV93-1684465170289)(images\谷粒商城项目笔记\image-20220611132121957.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwwIQLrA-1684465170289)(images\谷粒商城项目笔记\image-20220611132153995.png)]
-
稍等片刻,安装完成!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utzM1Q78-1684465170290)(images\谷粒商城项目笔记\image-20220611133051796.png)]
-
视频地址:https://www.bilibili.com/video/BV1Qv41167ck?p=6
-
centos镜像下载地址:http://ftp.sjtu.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso
-
4、linux连接工具安装
-
位置:阿里云盘(IT技术学习 - gulimall-soft - Xmanager-7)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U4ZuLSv5-1684465170290)(images\谷粒商城项目笔记\image-20220611115047980.png)]
-
解压安装包运行安装程序,点击下一步,选择安装位置,建议安D盘(英文路径),记住自己的安装位置,后面要用到
-
解压插件包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GPCnjKp-1684465170290)(images\谷粒商城项目笔记\image-20220611115249660.png)]
解压后如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqyrrEHp-1684465170291)(images\谷粒商城项目笔记\image-20220611115319253.png)]
-
全选复制解压后的文件,到安装包安装的路径,例如我安装的路径 D:\ruanjian\Xmanager-7,粘贴->替换目标中的文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPRdxSDN-1684465170291)(images\谷粒商城项目笔记\image-20220611115336870.png)]
-
破解完毕!!!
-
原版地址:https://www.yuque.com/yinghuashuxia-cohok/ahov4c/eipegl
5、虚拟机安装docker
1、创建/etc/docker文件夹
sudo mkdir -p /etc/docker
2、配置镜像加速
// 阿里云 https://cr.console.aliyun.com/cn-shanghai/instances/mirrors
// 从上面地址里的 镜像工具 中找到 镜像加速器
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://lcfrsqb4.mirror.aliyuncs.com"]
}
EOF
//腾讯云
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
}
EOF
// 设置完后重启下daemon
sudo systemctl daemon-reload
//重启docker
sudo systemctl restart docker
3、卸载旧版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
4、设置Docker仓库源地址
// 阿里云的源地址
$ sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
// 清华大学源地址
$ sudo yum-config-manager \
--add-repo \
https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
5、安装 Docker
sudo yum install docker-ce docker-ce-cli containerd.io
6、启动Docker
sudo systemctl start docker
7、设置为开机自启
sudo systemctl enable docker
8、docker安装mysql
1、拉取mysql镜像
docker pull mysql:5.7
2、运行mysql
# --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
####
-v 将对应文件挂载到主机
-e 初始化对应
-p 容器端口映射到主机的端口
3、创建&修改配置文件
vi /mydata/mysql/conf/my.cnf
里面的具体内容
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
4、重启mysql
docker restart mysql
5、设置自动启动
sudo docker update mysql --restart=always //docker中开机自启
7、安装Redis
1、下载镜像文件
docker pull redis
2、创建本地映射文件
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
3、修改映射配置文件内容
vim /mydata/redis/conf/redis.conf
具体内容
appendonly yes
表示数据持久化,不会重启就丢。
4、启动 同时 映射到对应文件夹
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
5、使用 redis 镜像执行 redis-cli 命令连接
docker exec -it redis redis-cli
6、重启Redis
docker restart redis
7、设置为开机自启
sudo docker update redis --restart=always
8、安装redis-desktop-manager
安装包在阿里云盘。(IT技术学习 - gulimall-soft)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUKMjEre-1684465170291)(images\谷粒商城项目笔记\image-20220612162507048.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W66i1qPz-1684465170291)(images\谷粒商城项目笔记\image-20220612162543747.png)]
6、统一开发环境
1、jdk至少是1.8
1、查看云端目前支持安装的jdk版本
[root@localhost ~]# yum search java|grep jdk
ldapjdk-javadoc.noarch : Javadoc for ldapjdk
java-1.6.0-openjdk.x86_64 : OpenJDK Runtime Environment
java-1.6.0-openjdk-demo.x86_64 : OpenJDK Demos
2、安装
[root@localhost ~]# yum install -y java-1.8.0-openjdk
3、验证
[root@localhost ~]# java -version
openjdk version "1.8.0_151"
4、安装openjdk-devel(jps)
sudo yum install java-1.8.0-openjdk-devel.x86_64
2、安装配置linux maven
-
下载maven
-
下载地址:https://maven.apache.org/download.cgi (或者直接从阿里网盘取)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-errjlnLK-1684465170292)(images/谷粒商城项目笔记/image-20220502155305378.png)]
-
-
放到/usr/local/目录下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F00Gc7cH-1684465170292)(images/谷粒商城项目笔记/image-20220502155403659.png)]
-
解压
tar -zxvf apache-maven-3.8.5-bin.tar.gz
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPuTbtet-1684465170292)(images/谷粒商城项目笔记/image-20220502155506298.png)]
-
配置maven仓库
-
进入cd apache-maven-3.6.3目录
cd apache-maven-3.8.5 #进入apache-maven-3.8.5目录
-
创建ck目录
mkdir ck #创建ck目录
-
编辑settings.xml文件
cd conf # 进入conf目录 vi settings.xm # settings.xm文件
-
找到localRepository下面加上如下
<localRepository>/usr/local/apache-maven-3.8.5/ck</localRepository>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNHY424j-1684465170292)(images/谷粒商城项目笔记/image-20220502160004889.png)]
-
找到mirror 加上阿里的仓库配置
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tt8wSoiF-1684465170293)(images/谷粒商城项目笔记/image-20220502160037809.png)]
-
编辑:vi /etc/profile 文件,翻到最后加上如下
export MAVEN_HOME=/usr/local/apache-maven-3.8.5 export PATH=$PATH:$MAVEN_HOME/bin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jX4SgQDL-1684465170293)(images/谷粒商城项目笔记/image-20220502160217274.png)]
-
重新加载一下,使新增配置生效
source /etc/profile
-
-
安装完成,测试一下
mvn -v
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifQ3kM5t-1684465170293)(images/谷粒商城项目笔记/image-20220502160403364.png)]
3、配置win10 maven
-
配置阿里云镜像
<mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>
-
配置 jdk 1.8 编译项目
<profiles> <profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile> </profiles>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-08xYAsSq-1684465170293)(images/谷粒商城项目笔记/image-20220502161154177.png)]
4、安装vscode
-
下载地址:https://code.visualstudio.com/Download (阿里网盘也有备份)
-
安装插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yO0aarx7-1684465170293)(images/谷粒商城项目笔记/image-20220502162914480.png)]
-
5、配置git信息
-
安装git (https://git-scm.com/)(阿里网盘有备份:IT技术学习 - gulimall-soft)
-
配置用户名
git config --global user.name "liuhandong" #这个不需要和注册时一样
-
配置邮箱
git config --global user.email "1920459132@qq.com" # 注册账号使用的邮箱
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jGMtivyZ-1684465170294)(images/谷粒商城项目笔记/image-20220502163225891.png)]
-
配置 ssh 免密登录
ssh-keygen -t rsa -C "1920459132@qq.com"
-
连点三次回车
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nJTfwgBc-1684465170294)(images/谷粒商城项目笔记/image-20220502164158401.png)]
-
查看密钥
cat ~/.ssh/id_rsa.pub
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rI17sOYy-1684465170294)(images/谷粒商城项目笔记/image-20220502164238936.png)]
-
复制密钥
-
进入gitee的安全设置,把刚才复制的密钥粘贴到 公钥 框中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omxOitOE-1684465170294)(images/谷粒商城项目笔记/image-20220502164358929.png)]
-
测试该密钥
ssh -T git@gitee.com
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUXIuWOq-1684465170294)(images/谷粒商城项目笔记/image-20220502164714488.png)]
6、初始化gitee
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vvp3o3Y-1684465170295)(images/谷粒商城项目笔记/image-20220502164939532.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUpKJpTs-1684465170295)(images/谷粒商城项目笔记/image-20220502165236550.png)]
7、填充几大核心服务骨架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tz2nZmd0-1684465170295)(images/谷粒商城项目笔记/image-20220502175101221.png)]
8、安装postman
- 下载安装包(阿里云盘:IT技术学习 - gulimall-soft)
- 安装后账号登陆
- 账号:1920459132@qq.com
- 密码:19950420liu
7、nacos搭建
1、下载nacos
地址:https://github.com/alibaba/nacos/releases/tag/1.1.3 (阿里网盘有备份)
2、启动nacos
双击nacos/bin目录下的startup.cmd
8、运行gateway服务
9、运行renren-fast服务
10、运行renren-fast-vue服务
-
安装nodejs 10.16.3
-
下载地址:https://nodejs.org/download/release/v10.16.3/node-v10.16.3-x64.msi
-
安装完成后检查一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFfUEcIV-1684465170295)(images\谷粒商城项目笔记\image-20220611174029302.png)]
-
-
设置npm的镜像仓库位置
npm config set registry http://registry.npm.taobao.org/ # 设置node仓库。提高下载速度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaMDS70t-1684465170295)(images\谷粒商城项目笔记\image-20220611174325843.png)]
-
下载组件
npm install
-
此时如果出现了异常,可以尝试安装不同版本的nodejs试试(这里使用14.13.0版本的好像比较好使)
-
运行程序
npm run dev
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fSzBEN6S-1684465170296)(images\谷粒商城项目笔记\image-20220612094318245.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRQY5kgG-1684465170296)(images\谷粒商城项目笔记\image-20220612094330215.png)]
-
如果出现了如下异常
Module build failed: Error: Missing binding D:\Idea_WorkSpace\gulimall\renren-fast-vue\node_modules\node-sass\vendor\win32-x64-83\binding.node Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 14.x
-
就执行如下代码
npm i node-sass
-
然后再执行
npm run dev
-
-
登录进去(账号密码:admin/admin)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rGSlxjul-1684465170296)(images\谷粒商城项目笔记\image-20220612094432674.png)]
这里使用vscode
11、运行thirdparty服务
12、运行product服务
13、运行ware服务
14、安装ElasticSearch
-
下载镜像文件
docker pull elasticsearch:7.4.2 docker pull kibana:7.4.2
-
本地创建两个文件
mkdir -p /mydata/elasticsearch/config //配置文件信息挂载到这个文件夹下 mkdir -p /mydata/elasticsearch/data //
-
修改配置文件
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
-
给文件夹添加权限
chmod -R 777 /mydata/elasticsearch/
-
运行镜像
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \ -e "discovery.type=single-node" \ -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \ -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \ -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ -d elasticsearch:7.4.2
-
设置开机自启
docker update elasticsearch --restart=always
-
启动起来后查看该容器的日志
docker logs elasticsearch 或者 docker logs [容器id] 或者 docker logs [容器id前三位]
-
测试
-
地址:124.222.248.51:9200
-
出现如下界面表示安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KH8SrewP-1684465170296)(images/谷粒商城项目笔记/image-20220510134053961.png)]
-
-
特别注意:
-e ES_JAVA_OPTS="-Xms64m -Xmx256m" \ 测试环境下,设置 ES 的初始内存和最大内存,否则导致过大启动不了 ES
15、启动Kibana
-
运行镜像
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.91.100:9200 -p 5601:5601 \ -d kibana:7.4.2 // 注意如果用的是云服务器,并且es和kibana在一台机器,则使用如下命令找到ip地址 docker inspect elasticsearch | grep IPAddress // 并把ip地址放在host后面 // 一般是172.17.0.3 // http://192.168.91.100:9200 一定改为自己虚拟机的地址
-
启动完成后等待一会儿,或许几秒,或许几分钟
-
设置开机自启
docker update kibana --restart=always
-
测试
-
测试地址:124.222.248.51:5601
-
出现如下则成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tiv1FfZj-1684465170296)(images/谷粒商城项目笔记/image-20220510141748528.png)]
-
1、安装 ik 分词器
注意:不能用默认的 elasticsearch-plugin.install xxx.zip 进行自动安装
https://github.com/medcl/elasticsearch-analysis-ik/releases 下载与 es对应的版本
安装后拷贝到 plugins 目录下
-
下载安装包:(阿里云盘有备份:IT技术学习 - gulimall-s)
- 下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
-
在/mydata/elasticsearch/plugins/目录下新建文件夹ik
cd /mydata/elasticsearch/plugins/ mkdir ik
-
将下好的压缩包拷贝到ik文件夹里
-
解压缩
unzip elasticsearch-analysis-ik-7.4.2.zip
-
删除zip文件
rm -rf elasticsearch-analysis-ik-7.4.2.zip
-
进入elasticsearch中查看安装情况
[root@master ik]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c23a3a8c65b3 kibana:7.4.2 "/usr/local/bin/dumb…" 23 minutes ago Up 23 minutes 0.0.0.0:5601->5601/tcp, :::5601->5601/tcp kibana 545c3c3a9ff9 elasticsearch:7.4.2 "/usr/local/bin/dock…" 24 minutes ago Up 14 minutes 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 0.0.0.0:9300->9300/tcp, :::9300->9300/tcp elasticsearch [root@master ik]# docker exec -it 545 /bin/bash [root@545c3c3a9ff9 elasticsearch]# ls LICENSE.txt NOTICE.txt README.textile bin config data jdk lib logs modules plugins [root@545c3c3a9ff9 elasticsearch]# cd bin [root@545c3c3a9ff9 bin]# ls elasticsearch elasticsearch-cli elasticsearch-enve elasticsearch-node elasticsearch-setup-passwords elasticsearch-sql-cli-7.4.2.jar x-pack-env elasticsearch-certgen elasticsearch-croneval elasticsearch-keystore elasticsearch-plugin elasticsearch-shard elasticsearch-syskeygen x-pack-security-env elasticsearch-certutil elasticsearch-env elasticsearch-migrate elasticsearch-saml-metadata elasticsearch-sql-cli elasticsearch-users x-pack-watcher-env [root@545c3c3a9ff9 bin]# elasticsearch-plugin list ik
2、测试
-
使用前记得重启elasticsearch
docker restart elasticsearch
分词器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruXZu4lA-1684465170297)(images/谷粒商城项目笔记/image-20201026092255250.png)]
docker run -p 80:80 --name nginx -d nginx:1.10
3、自定义词库
-
Docker 安装 Nginx
-
创建要挂载的配置目录
mkdir -p /mydata/nginx/conf
-
启动临时nginx容器
docker run -p 80:80 --name nginx -d nginx:1.10
-
拷贝出 Nginx 容器的配置
# 将nginx容器中的nginx目录复制到本机的/mydata/nginx/conf目录 docker container cp nginx:/etc/nginx /mydata/nginx/conf # 复制的是nginx目录,将该目录的所有文件移动到 conf 目录 mv /mydata/nginx/conf/nginx/* /mydata/nginx/conf/ # 删除多余的 /mydata/nginx/conf/nginx目录 rm -rf /mydata/nginx/conf/nginx
-
删除临时nginx容器
# 停止运行 nginx 容器 docker stop nginx # 删除 nginx 容器 docker rm nginx
-
启动 nginx 容器
docker run -p 80:80 --name nginx \ -v /mydata/nginx/html:/usr/share/nginx/html \ -v /mydata/nginx/logs:/var/log/nginx \ -v /mydata/nginx/conf/:/etc/nginx \ -d nginx:1.10
-
设置 nginx 随 Docker 启动
docker update nginx --restart=always
-
测试 nginx
-
echo '<h1><a target="_blank" href="https://github.com/zsy0216/guli-mall">谷粒商城源码</a></h1>' \ >/mydata/nginx/html/index.html
-
打开:http://192.168.163.131/ 可以看到下面内容说明安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80uiqJPM-1684465170297)(images/谷粒商城项目笔记/image-20220510180925529.png)]
-
-
-
nginx 中自定义分词文件
mkdir /mydata/nginx/html/es echo "蔡徐坤" > /mydata/nginx/html/es/fenci.txt
nginx 默认请求地址为
ip:port/es/fenci.txt
;本机为:192.168.163.131/es/fenci.txt
如果想要增加新的词语,只需要在该文件追加新的行并保存新的词语即可。
-
给 es 配置自定义词库
# 1. 打开并编辑 ik 插件配置文件 vim /mydata/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
修改为以下内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict"></entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords"></entry> <!--用户可以在这里配置远程扩展字典 --> <!-- <entry key="remote_ext_dict">words_location</entry> --> <entry key="remote_ext_dict">http://192.168.91.100/es/fenci.txt</entry> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties>
-
重启 elasticsearch 容器
docker restart elasticsearch
-
测试自定义词库
GET my_index/_analyze { "analyzer": "ik_max_word", "text":"蔡徐坤" }
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQ5Xxkz6-1684465170297)(images/谷粒商城项目笔记/image-20220510181243145.png)]
16、Nginx-搭建域名访问环境
-
修改 Windows hosts 文件
-
位置:C:\Windows\System32\drivers\etc
-
后面追加
# guli mall # 注意这个ip地址是nginx所在服务器主机地址 192.168.91.100 gulimall.com
-
-
Nginx 配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nFDiNAma-1684465170297)(images/谷粒商城项目笔记/image-20220511151810799.png)]
-
分析Nginx配置文件
-
位置:cat /mydata/nginx/conf/nginx.conf
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; }
-
可以看到,在 http 块中最后有 include /etc/nginx/conf.d/*.conf; 这句配置说明在 conf.d 目录下所有 .conf 后缀的文件内容都会作为 nginx 配置文件 http 块中的配置。这是为了防止主配置文件太复杂,也可以对不同的配置进行分类。
下面我们参考 conf.d 目录下的配置,来配置 gulimall 的 server 块配置
-
-
配置gulimall.conf
-
复制出来
cd /mydata/nginx/conf/conf.d cp default.conf gulimall.conf
-
查看Windows ip
-
打开cmd 输入 ipconfig
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNAlQVFt-1684465170297)(images/谷粒商城项目笔记/image-20220511152556498.png)]
-
这里的 192.168.1.7 和 192.168.56.1 也是 Windows 的本机地址
所以我们配置当访问 nginx /请求时代理到 192.168.56.1:10000 商品服务首页
-
-
配置代理
vim gulimall.conf server { listen 80; server_name gulimall.com; #charset koi8-r; #access_log /var/log/nginx/log/host.access.log main; location / { proxy_pass http://192.168.91.1:10000; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
-
图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7qSW7pe-1684465170298)(images/谷粒商城项目笔记/image-20220511153010910.png)]
-
-
反向代理:nginx 代理网关由网关进行转发
-
修改 nginx.conf
vim /mydata/nginx/conf/nginx.conf
-
修改 http 块,配置上游服务器为网关地址
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; upstream gulimall { server 192.168.56.1:88; } include /etc/nginx/conf.d/*.conf; }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lwvj1U80-1684465170298)(images/谷粒商城项目笔记/image-20220511153802784.png)]
-
修改 gulimall.conf
-
配置代理地址为上面配置的上游服务器名
server { listen 80; server_name gulimall.com; #charset koi8-r; #access_log /var/log/nginx/log/host.access.log main; location / { proxy_set_header Host $host; proxy_pass http://gulimall; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNlHtelG-1684465170298)(images/谷粒商城项目笔记/image-20220511153706015.png)]
-
-
-
效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODeGdzKt-1684465170298)(images/谷粒商城项目笔记/image-20220511153338029.png)]
-
访问跳转分析
- 当前通过域名的方式,请求 gulimall.com ;
- 根据 hosts 文件的配置,请求 gulimall.com 域名时会请求虚拟机 ip
- 当请求到 192.168.163.131:80 时,会被 nginx 转发到我们配置的 192.168.163.1:10000 路径,该路径为运行商品服务的 windows 主机 ip 地址,至此达到通过域名访问商品服务的目的。
-
后续网关配置
-
之后为了统一管理我们的各种服务,我们将通过配置网关作为 nginx 转发的目标。最后通过配置网关根据不同的域名来判断跳转对应的服务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0t6YQ2t-1684465170299)(images/谷粒商城项目笔记/image-20220511153516996.png)]
-
性能与压力测试
jconsole 与 jvisualvm
jdk 的两个小工具 jconsole、jvisualvm(升级版本的 jconsole)。通过命令行启动、可监控本地和远程应用、远程应用需要配置
1、jvisualvm 能干什么
监控内存泄漏、跟踪垃圾回收、执行时内存、cpu分析、线程分析…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hamrQOOf-1684465170299)(images/谷粒商城项目笔记/image-20201029120502383.png)]
运行:正在运行的线程
休眠:sleep
等待:wait
驻留:线程池里面的空闲线程
监视:组赛的线程、正在等待锁
2、安装插件方便查看 gc
cmd 启动 jvisualvm
工具->插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEYb0sVQ-1684465170299)(images/谷粒商城项目笔记/image-20201029121108492.png)]
如果503 错误解决
打开网址: https://visualvm.github.io/pluginscenters.html
cmd 查看自己的jdk版本,找到对应的
docker stats 查看相关命令
JMeter
1、安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzyx5m7m-1684465170299)(images/谷粒商城项目笔记/image-20220504155752345.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1itYZZCj-1684465170300)(images/谷粒商城项目笔记/image-20220504155833695.png)]
2、启动
解压后打开bin目录
点击jmeter.bat
就开启了jmeter
JMeter 压测示例
1、添加线程组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eU0DDJwW-1684465170300)(images/谷粒商城项目笔记/image-20201029084634498.png)]
2、添加 HTTP 请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7p8CxwX0-1684465170300)(images/谷粒商城项目笔记/image-20201029085843220.png)]
3、添加监听器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R6ktEFPg-1684465170300)(images/谷粒商城项目笔记/image-20201029085942442.png)]
4、启动压测&查看
汇总图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhFtORrI-1684465170300)(images/谷粒商城项目笔记/image-20201029092357910.png)]
察看结果树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5GsUyjs-1684465170301)(images/谷粒商城项目笔记/image-20201029092436633.png)]
汇总报告
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kNJnO6SX-1684465170301)(images/谷粒商城项目笔记/image-20201029092454376.png)]
聚合报告
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NnSoUth9-1684465170301)(images/谷粒商城项目笔记/image-20201029092542876.png)]
JMeter Address Already in use 错误解决
windows本身提供的端口访问机制的问题。 Windows提供给TCP/IP 链接的端口为1024-5000,并且要四分钟来循环回收他们。就导致 我们在短时间内跑大量的请求时将端口占满了。
1.cmd中,用regedit命令打开注册表
2.在HKEY_ LOCAL MACHINE\SYSTEMCurrentControlSet\Services Tcpip\Parameters下,
1.右击parameters,添加一个新的DWORD,名字为MaxUserPort 2.然后双击 MaxUserPort,输入数值数据为65534,基数选择十进制(如果是分布式运行的话,控制机器和负载机器都需要这样操作哦)
3.修改配置完毕之后记得重启机器才会生效
TCPTimedWaitDelay:30
性能优化 - Nginx 动静分离
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1S5qweF-1684465170302)(images/谷粒商城项目笔记/image-20220511173747297.png)]
- 首先,把商品服务中静态文件夹 index 放到 nginx 下 /mydata/nginx/html/static目录;
- 给模板中所有静态资源的请求路径前都加上 /static;
- 修改 Nginx 配置文件 /mydata/nginx/conf/conf.d/gulimall.conf
# /static/ 下所有的请求都转给 nginx
location /static/ {
root /user/share/nginx/html;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zZlT4DH-1684465170302)(images/谷粒商城项目笔记/image-20220511173837394.png)]
缓存和分布式锁
缓存
整合 redis 作为缓存
1、引入依赖
SpringBoot 整合 redis,查看SpringBoot提供的 starts
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t0KGmYV4-1684465170302)(images/谷粒商城项目笔记/image-20201031154148722.png)]
pom.xml
<!--引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--不加载自身的 lettuce-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
堆外内存溢出异常:
这里可能会产生堆外内存溢出异常:OutOfDirectMemoryError。
下面进行分析:
- SpringBoot 2.0 以后默认使用 lettuce 作为操作 redis 的客户端,它使用 netty 进行网络通信;
- lettuce 的 bug 导致 netty 堆外内存溢出;
- netty 如果没有指定堆外内存,默认使用 -Xmx 参数指定的内存;
- 可以通过 -Dio.netty.maxDirectMemory 进行设置;
解决方案:不能只使用 -Dio.netty.maxDirectMemory 去调大堆外内存,这样只会延缓异常出现的时间。
- 升级 lettuce 客户端,或使用 jedis 客户端
2、配置
application.yaml
Spring:
redis:
host: 192.168.56.10
port: 6379
RedisAutoConfig.java
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVBLpUpI-1684465170303)(images/谷粒商城项目笔记/image-20201031154710108.png)]
3、测试
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testStringRedisTemplate() {
stringRedisTemplate.opsForValue().set("hello","world_" + UUID.randomUUID().toString());
String hello = stringRedisTemplate.opsForValue().get("hello");
System.out.println("之前保存的数据是:" + hello);
}
4、优化三级分类数据获取
/**
* TODO 产生堆外内存溢出 OutOfDirectMemoryError
* 1、SpringBoot2.0以后默认使用 Lettuce作为操作redis的客户端,它使用 netty进行网络通信
* 2、lettuce 的bug导致netty堆外内存溢出,-Xmx300m netty 如果没有指定堆内存移除,默认使用 -Xmx300m
* 可以通过-Dio.netty.maxDirectMemory 进行设置
* 解决方案 不能使用 -Dio.netty.maxDirectMemory调大内存
* 1、升级 lettuce客户端,2、 切换使用jedis
* redisTemplate:
* lettuce、jedis 操作redis的底层客户端,Spring再次封装
* @return
*/
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson() {
// 给缓存中放 json 字符串、拿出的是 json 字符串,还要逆转为能用的对象类型【序列化和反序列化】
// 1、加入缓存逻辑,缓存中放的数据是 json 字符串
// JSON 跨语言,跨平台兼容
String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
if (StringUtils.isEmpty(catelogJSON)) {
// 2、缓存没有,从数据库中查询
Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDb();
// 3、查询到数据,将数据转成 JSON 后放入缓存中
String s = JSON.toJSONString(catelogJsonFromDb);
redisTemplate.opsForValue().set("catelogJSON",s);
return catelogJsonFromDb;
}
// 转换为我们指定的对象
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
return result;
}
分布式锁
分布式锁基本原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovDzAtsf-1684465170303)(images/谷粒商城项目笔记/image-20201031122557660.png)]
**理解:**就先当1000个人去占一个厕所,厕所只能有一个人占到这个坑,占到这个坑其他人就只能在外面等待,等待一段时间后可以再次来占坑,业务执行后,释放锁,那么其他人就可以来占这个坑
分布式锁演进 - 阶段一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLe9Er5d-1684465170303)(images/谷粒商城项目笔记/image-20201031123441336.png)]
代码:
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "0");
if (lock) {
// 加锁成功..执行业务
Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDB();
redisTemplate.delete("lock"); // 删除锁
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠100ms重试
return getCatelogJsonFromDbWithRedisLock();
}
分布式锁演进 - 阶段二
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnp9mv5F-1684465170303)(images/谷粒商城项目笔记/image-20201031123640746.png)]
代码:
Boolean lock = redisTemplate.opsForValue().setIfAbsent()
if (lock) {
// 加锁成功..执行业务
// 设置过期时间
redisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDB();
redisTemplate.delete("lock"); // 删除锁
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠100ms重试
return getCatelogJsonFromDbWithRedisLock();
}
分布式锁演进 - 阶段三
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jEHRTgj0-1684465170304)(images/谷粒商城项目笔记/image-20201031124210112.png)]
代码:
// 设置值同时设置过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS);
if (lock) {
// 加锁成功..执行业务
// 设置过期时间,必须和加锁是同步的,原子的
redisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDB();
redisTemplate.delete("lock"); // 删除锁
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠100ms重试
return getCatelogJsonFromDbWithRedisLock();
}
分布式锁演进 - 阶段四
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y3wqUiVL-1684465170304)(images/谷粒商城项目笔记/image-20201031124615670.png)]
图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7eddCke-1684465170304)(images/谷粒商城项目笔记/image-20201031130547173.png)]
代码:
String uuid = UUID.randomUUID().toString();
// 设置值同时设置过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if (lock) {
// 加锁成功..执行业务
// 设置过期时间,必须和加锁是同步的,原子的
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDB();
// String lockValue = redisTemplate.opsForValue().get("lock");
// if (lockValue.equals(uuid)) {
// // 删除我自己的锁
// redisTemplate.delete("lock"); // 删除锁
// }
// 通过使用lua脚本进行原子性删除
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//删除锁
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠100ms重试
return getCatelogJsonFromDbWithRedisLock();
}
分布式锁演进 - 阶段五 最终模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6CJyUYje-1684465170305)(images/谷粒商城项目笔记/image-20201031130201609.png)]
代码:
String uuid = UUID.randomUUID().toString();
// 设置值同时设置过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功");
// 加锁成功..执行业务
// 设置过期时间,必须和加锁是同步的,原子的
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String,List<Catelog2Vo>> dataFromDb;
// String lockValue = redisTemplate.opsForValue().get("lock");
// if (lockValue.equals(uuid)) {
// // 删除我自己的锁
// redisTemplate.delete("lock"); // 删除锁
// }
try {
dataFromDb = getDataFromDB();
} finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//删除锁
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDb;
} else {
// 加锁失败,重试 synchronized()
// 休眠200ms重试
System.out.println("获取分布式锁失败,等待重试");
try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
return getCatelogJsonFromDbWithRedisLock();
}
问题:
- 分布式加锁解锁都是这两套代码,可以封装成工具类
- 分布式锁有更专业的框架
分布式锁 - Redisson
1、整合
1、引入依赖
<!--以后使用 redisson 作为分布锁,分布式对象等功能-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2、配置 redisson
@Configuration
public class MyRedissonConfig {
/**
* 所有对 Redisson 的使用都是通过 RedissonClient
*
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
// 1、创建配置
Config config = new Config();
// Redis url should start with redis:// or rediss://
config.useSingleServer().setAddress("redis://192.168.163.131:6379");
// 2、根据 Config 创建出 RedissonClient 实例
return Redisson.create(config);
}
}
3、使用
// 1. 获取一把锁
Rlock lock = redisson.getLock("my-lock");
// 2. 加锁, 阻塞式等待
lock.lock();
try {
System.out.println("加锁成功,执行业务...");
} catch (Exception e) {
} finally {
// 3. 解锁 假设解锁代码没有运行,Redisson 会出现死锁吗?(不会)
lock.unlock();
}
2、Redisson - Lock 锁测试 & Redisson - Lock 看门狗原理 - Redisson 如何解决死锁
@RequestMapping("/hello")
@ResponseBody
public String hello(){
// 1、获取一把锁,只要锁得名字一样,就是同一把锁
RLock lock = redission.getLock("my-lock");
// 2、加锁
lock.lock(); // 阻塞式等待,默认加的锁都是30s时间
// 1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期后被删掉
// 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s以后自动删除
lock.lock(10, TimeUnit.SECONDS); //10s 后自动删除
//问题 lock.lock(10, TimeUnit.SECONDS) 在锁时间到了后,不会自动续期
// 1、如果我们传递了锁的超时时间,就发送给 redis 执行脚本,进行占锁,默认超时就是我们指定的时间
// 2、如果我们为指定锁的超时时间,就是用 30 * 1000 LockWatchchdogTimeout看门狗的默认时间、
// 只要占锁成功,就会启动一个定时任务,【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s就自动续期
// internalLockLeaseTime【看门狗时间】 /3,10s
//最佳实践
// 1、lock.lock(10, TimeUnit.SECONDS);省掉了整个续期操作,手动解锁
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
Thread.sleep(3000);
} catch (Exception e) {
} finally {
// 解锁 将设解锁代码没有运行,reidsson会不会出现死锁
System.out.println("释放锁...." + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
进入到 Redisson
Lock 源码
1、进入 Lock
的实现 发现 他调用的也是 lock
方法参数 时间为 -1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwFVGHF3-1684465170305)(images/谷粒商城项目笔记/image-20201101051659465.png)]
2、再次进入 lock
方法
发现他调用了 tryAcquire
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IaK1kjMm-1684465170305)(images/谷粒商城项目笔记/image-20201101051925487.png)]
3、进入 tryAcquire
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbO7S9g4-1684465170306)(images/谷粒商城项目笔记/image-20201101052008724.png)]
4、里头调用了 tryAcquireAsync
这里判断 laseTime != -1 就与刚刚的第一步传入的值有关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhrWynmY-1684465170306)(images/谷粒商城项目笔记/image-20201101052037959.png)]
5、进入到 tryLockInnerAsync
方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paMmjAYh-1684465170306)(images/谷粒商城项目笔记/image-20201101052158592.png)]
6、internalLockLeaseTime
这个变量是锁的默认时间
这个变量在构造的时候就赋初始值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlX9oKKP-1684465170306)(images/谷粒商城项目笔记/image-20201101052346059.png)]
7、最后查看 lockWatchdogTimeout
变量
也就是30秒的时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kXslD3c-1684465170307)(images/谷粒商城项目笔记/image-20201101052428198.png)]
3、Reidsson - 读写锁
二话不说,上代码!!!
/**
* 保证一定能读取到最新数据,修改期间,写锁是一个排他锁(互斥锁,独享锁)读锁是一个共享锁
* 写锁没释放读锁就必须等待
* 读 + 读 相当于无锁,并发读,只会在 reids中记录好,所有当前的读锁,他们都会同时加锁成功
* 写 + 读 等待写锁释放
* 写 + 写 阻塞方式
* 读 + 写 有读锁,写也需要等待
* 只要有写的存在,都必须等待
* @return String
*/
@RequestMapping("/write")
@ResponseBody
public String writeValue() {
RReadWriteLock lock = redission.getReadWriteLock("rw_lock");
String s = "";
RLock rLock = lock.writeLock();
try {
// 1、改数据加写锁,读数据加读锁
rLock.lock();
System.out.println("写锁加锁成功..." + Thread.currentThread().getId());
s = UUID.randomUUID().toString();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
redisTemplate.opsForValue().set("writeValue",s);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
System.out.println("写锁释放..." + Thread.currentThread().getId());
}
return s;
}
@RequestMapping("/read")
@ResponseBody
public String readValue() {
RReadWriteLock lock = redission.getReadWriteLock("rw_lock");
RLock rLock = lock.readLock();
String s = "";
rLock.lock();
try {
System.out.println("读锁加锁成功..." + Thread.currentThread().getId());
s = (String) redisTemplate.opsForValue().get("writeValue");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
System.out.println("读锁释放..." + Thread.currentThread().getId());
}
return s;
}
来看下官网的解释
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHQnHYzV-1684465170307)(images/谷粒商城项目笔记/image-20201101053042268.png)]
4、Redisson - 闭锁测试
官网!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aAUyS8UJ-1684465170307)(images/谷粒商城项目笔记/image-20201101053053554.png)]
上代码
/**
* 放假锁门
* 1班没人了
* 5个班级走完,我们可以锁们了
* @return
*/
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redission.getCountDownLatch("door");
door.trySetCount(5);
door.await();//等待闭锁都完成
return "放假了....";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redission.getCountDownLatch("door");
door.countDown();// 计数器减一
return id + "班的人走完了.....";
}
和 JUC 的 CountDownLatch 一致
await()等待闭锁完成
countDown() 把计数器减掉后 await就会放行
5、Redisson - 信号量测试
官网!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0GKWXHBu-1684465170308)(images/谷粒商城项目笔记/image-20201101053450708.png)]
/**
* 车库停车
* 3车位
* @return
*/
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redission.getSemaphore("park");
boolean b = park.tryAcquire();//获取一个信号,获取一个值,占用一个车位
return "ok=" + b;
}
@GetMapping("/go")
@ResponseBody
public String go() {
RSemaphore park = redission.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}
类似 JUC 中的 Semaphore
缓存数据一致性
缓存数据一致性 - 双写模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmGgD9uD-1684465170308)(images/谷粒商城项目笔记/image-20201101053613373.png)]
两个线程写 最终只有一个线程写成功,后写成功的会把之前写的数据给覆盖,这就会造成脏数据
缓存数据一致性 - 失效模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYytclKl-1684465170308)(images/谷粒商城项目笔记/image-20201101053834126.png)]
三个连接
一号连接 写数据库 然后删缓存
二号连接 写数据库时网络连接慢,还没有写入成功
三号链接 直接读取数据,读到的是一号连接写入的数据,此时 二号链接写入数据成功并删除了缓存,三号开始更新缓存发现更新的是二号的缓存
缓存数据一致性解决方案
无论是双写模式还是失效模式,都会到这缓存不一致的问题,即多个实力同时更新会出事,怎么办?
- 1、如果是用户纯度数据(订单数据、用户数据),这并发几率很小,几乎不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
- 2、如果是菜单,商品介绍等基础数据,也可以去使用 canal 订阅,binlog 的方式
- 3、缓存数据 + 过期时间也足够解决大部分业务对缓存的要求
- 4、通过加锁保证并发读写,写写的时候按照顺序排好队,读读无所谓,所以适合读写锁,(业务不关心脏数据,允许临时脏数据可忽略)
总结:
- 我们能放入缓存的数据本来就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前的最新值即可
- 我们不应该过度设计,增加系统的复杂性
- 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点
最后符上 三级分类数据 加上分布式锁
检索服务
添加模板页面
<!-- 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
将资料中的前端页面放到 search 服务模块下的 resource/templates 下;
配置请求跳转
配置 Nginx 转发
配置 Windows hosts 文件:
192.168.56.100 search.gulimall.com
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-usUArK6U-1684465170308)(images/谷粒商城项目笔记/image-20220512132128091.png)]
找到 Nginx 的配置文件,编辑 gulimall.conf,将所有 *.gulimall.com 的请求都经由 Nginx 转发给网关;
server {
listen 80;
server_name gulimall.com *.gulimall.com;
...
}
然后重启 Nginx
docker restart nginx
配置网关服务转发到 search 服务
- id: mall_search_route
uri: lb://mall-search
predicates:
- Host=search.gulimall.com
配置页面跳转
配置 /list.html 请求转发到 list 模板
/**
* 自动将页面提交过来的所有请求参数封装成我们指定的对象
*
* @param param
* @return
*/
@GetMapping(value = "/list.html")
public String listPage(SearchParam param, Model model, HttpServletRequest request) {
return "list";
}
检索功能实现
异步和线程池
初始化线程的 4 种方式
1、继承 Thread
2、实现 Runnable
3、实现 Callable 接口 + FutureTask(可以拿到返回结果,可以处理异常)
4、线程池
方式一和方式二 主进程无法获取线程的运算结果,不适合当前场景
方式三:主进程可以获取当前线程的运算结果,但是不利于控制服务器种的线程资源,可以导致服务器资源耗尽
方式四:通过如下两种方式初始化线程池
Executors.newFixedThreadPool(3);
//或者
new ThreadPollExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler);
通过线程池性能稳定,也可以获取执行结果,并捕获异常,但是,在业务复杂情况下,一个异步调用可能会依赖另一个异步调用的执行结果
线程池的 7 大参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-meV6n9cA-1684465170309)(images/谷粒商城项目笔记/image-20201105154808826.png)]
运行流程:
1、线程池创建,准备好 core
数量 的核心线程,准备接受任务
2、新的任务进来,用 core
准备好的空闲线程执行
core
满了,就将再进来的任务放入阻塞队列中,空闲的 core 就会自己去阻塞队列获取任务执行- 阻塞队列也满了,就直接开新线程去执行,最大只能开到
max
指定的数量 max
都执行好了,Max-core
数量空闲的线程会在keepAliveTime
指定的时间后自动销毁,终保持到core
大小- 如果线程数开到了
max
数量,还有新的任务进来,就会使用 reject 指定的拒绝策略进行处理
3、所有的线程创建都是由指定的 factory
创建的
面试;
一个线程池 core 7、max 20 ,queue 50 100 并发进来怎么分配的 ?
先有 7 个能直接得到运行,接下来 50 个进入队列排队,再多开 13 个继续执行,线程70个被安排上了,剩下30个默认拒绝策略
常见的 4 种线程池
-
newCacheThreadPool
- 创建一个可缓存的线程池,如果线程池长度超过需要,可灵活回收空闲线程,若无可回收,则新建线程
-
newFixedThreadPool
- 创建一个指定长度的线程池,可控制线程最大并发数,超出的线程会再队列中等待
-
newScheduleThreadPool
- 创建一个定长线程池,支持定时及周期性任务执行
-
newSingleThreadExecutor
- 创建一个单线程化的线程池,她只会用唯一的工作线程来执行任务,保证所有任务
开发中为什么使用线程池
- 降低资源的消耗
- 通过重复利用已创建好的线程降低线程的创建和销毁带来的损耗
- 提高响应速度
- 因为线程池中的线程没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
- 提高线程的客观理性
- 线程池会根据当前系统的特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
CompletableFuture 异步编排
业务场景:
查询商品详情页逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRAlCBHg-1684465170309)(images/谷粒商城项目笔记/image-20201105163535757.png)]
假如商品详情页的每个查询,需要如下标注时间才能完成
那么,用户需要5.5s后才能看到商品相详情页的内容,很显然是不能接受的
如果有多个线程同时完成这 6 步操作,也许只需要 1.5s 即可完成响应
创建异步对象
CompletableFuture 提供了四个静态方法来创建一个异步操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMlFB1rW-1684465170309)(images/谷粒商城项目笔记/image-20201105185420349.png)]
1、runXxx 都是没有返回结果的,supplyXxxx都是可以获取返回结果的
2、可以传入自定义的线程池,否则就是用默认的线程池
3、根据方法的返回类型来判断是否该方法是否有返回类型
代码实现:
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main....start.....");
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}, executor);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor);
Integer integer = future.get();
System.out.println("main....stop....." + integer);
}
计算完成时回调方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nJzyhh2F-1684465170309)(images/谷粒商城项目笔记/image-20201105185821263.png)]
whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况
whenComplete 和 whenCompleteAsync 的区别
whenComplete :是执行当前任务的线程继续执行 whencomplete 的任务
whenCompleteAsync: 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行
方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executor).whenComplete((res,exception) ->{
// 虽然能得到异常信息,但是没法修改返回的数据
System.out.println("异步任务成功完成了...结果是:" +res + "异常是:" + exception);
}).exceptionally(throwable -> {
// 可以感知到异常,同时返回默认值
return 10;
});
handle 方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8iou2DmH-1684465170310)(images/谷粒商城项目笔记/image-20201105194503175.png)]
和 complete 一样,可以对结果做最后的处理(可处理异常),可改变返回值
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor).handle((res,thr) ->{
if (res != null ) {
return res * 2;
}
if (thr != null) {
return 0;
}
return 0;
});
线程串行方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ORlAUyES-1684465170310)(images/谷粒商城项目笔记/image-20201105195632819.png)]
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任物的返回值
thenAccept方法:消费处理结果,接受任务处理结果,并消费处理,无返回结果
thenRun 方法:只要上面任务执行完成,就开始执行 thenRun ,只是处理完任务后,执行 thenRun的后续操作
带有 Async 默认是异步执行的,同之前,
以上都要前置任务完成
/**
* 线程串行化,
* 1、thenRun:不能获取到上一步的执行结果,无返回值
* .thenRunAsync(() ->{
* System.out.println("任务2启动了....");
* },executor);
* 2、能接受上一步结果,但是无返回值 thenAcceptAsync
* 3、thenApplyAsync 能收受上一步结果,有返回值
*
*/
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor).thenApplyAsync(res -> {
System.out.println("任务2启动了..." + res);
return "Hello " + res;
}, executor);
String s = future.get();
System.out.println("main....stop....." + s);
两任务组合 - 都要完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hXwbN7wY-1684465170310)(images/谷粒商城项目笔记/image-20210102044028142.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wpwl0ZYo-1684465170311)(images/谷粒商城项目笔记/image-20210102044044914.png)]
两个任务必须都完成,触发该任务
thenCombine: 组合两个 future,获取两个 future的返回结果,并返回当前任务的返回值
thenAccpetBoth: 组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值
runAfterBoth:组合 两个 future,不需要获取 future 的结果,只需要两个 future处理完成任务后,处理该任务,
/**
* 两个都完成
*/
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1当前线程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任务1结束:" + i);
return i;
}, executor);
CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务2当前线程:" + Thread.currentThread().getId());
System.out.println("任务2结束:");
return "Hello";
}, executor);
// f1 和 f2 执行完成后在执行这个
// future01.runAfterBothAsync(future02,() -> {
// System.out.println("任务3开始");
// },executor);
// 返回f1 和 f2 的运行结果
// future01.thenAcceptBothAsync(future02,(f1,f2) -> {
// System.out.println("任务3开始....之前的结果:" + f1 + "==>" + f2);
// },executor);
// f1 和 f2 单独定义返回结果
CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> {
return f1 + ":" + f2 + "-> Haha";
}, executor);
System.out.println("main....end....." + future.get());
两任务组合 - 一个完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwtSFYaS-1684465170311)(images/谷粒商城项目笔记/image-20201106101904880.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hL3Enq8P-1684465170311)(images/谷粒商城项目笔记/image-20201106101918013.png)]
当两个任务中,任意一个future 任务完成时,执行任务
applyToEither;两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值
acceptEither: 两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值
runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值
/**
* 两个任务,只要有一个完成,我们就执行任务
* runAfterEnitherAsync:不感知结果,自己没有返回值
* acceptEitherAsync:感知结果,自己没有返回值
* applyToEitherAsync:感知结果,自己有返回值
*/
// future01.runAfterEitherAsync(future02,() ->{
// System.out.println("任务3开始...之前的结果:");
// },executor);
// future01.acceptEitherAsync(future02,(res) -> {
// System.out.println("任务3开始...之前的结果:" + res);
// },executor);
CompletableFuture<String> future = future01.applyToEitherAsync(future02, res -> {
System.out.println("任务3开始...之前的结果:" + res);
return res.toString() + "->哈哈";
}, executor);
多任务组合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XcncLk7Q-1684465170311)(images/谷粒商城项目笔记/image-20201106104031315.png)]
allOf:等待所有任务完成
anyOf:只要有一个任务完成
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的图片信息");
return "hello.jpg";
});
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的属性");
return "黑色+256G";
});
CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("查询商品介绍");
return "华为";
});
// 等待全部执行完
// CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
// allOf.get();
// 只需要有一个执行完
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
anyOf.get();
System.out.println("main....end....." + anyOf.get());
商品业务 & 认证服务
环境搭建
- 为登录和注册创建一个服务
- 讲提供的前端放到
templates
目录下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78Y5tcMQ-1684465170312)(images/谷粒商城项目笔记/image-20201110084252039.png)]
前端验证码倒计时
定义id 使用 Jquery
触发点击事件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iewpfld2-1684465170312)(images/谷粒商城项目笔记/image-20201110084521166.png)]
Jquery
$(function () {
/**
* 验证码发送
*/
$("#sendCode").click(function () {
//判断是否有该样式
if ($(this).hasClass("disabled")) {
// 正在倒计时
} else {
// 发送验证码
$.get("/sms/sendCode?phone=" + $("#phoneNum").val(), function (data) {
if (data.code != 0) {
alert(data.msg)
}
})
timeoutChangeStyle();
}
})
})
// 60秒
var num = 60;
function timeoutChangeStyle() {
// 先添加样式,防止重复点击
$("#sendCode").attr("class", "disabled")
// 到达0秒后 重置时间,去除样式
if (num == 0) {
$("#sendCode").text("发送验证码")
num = 60;
// 时间到达后清除样式
$("#sendCode").attr("class", "");
} else {
var str = num + "s 后再次发送"
$("#sendCode").text(str);
setTimeout("timeoutChangeStyle()", 1000);
}
num--;
}
对应效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64US0jHA-1684465170312)(images/谷粒商城项目笔记/image-20201110084733372.png)]
整合短信验证码
1、短信验证我们选择的是阿里云的短信服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqNvGYjt-1684465170313)(images/谷粒商城项目笔记/image-20201110084936446.png)]
2、选择对应短信服务进行开通
在云市场就能看到购买的服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dcqJQnJE-1684465170313)(images/谷粒商城项目笔记/image-20201110085141506.png)]
3、验证短信功能是否能发送
在购买短信的页面,能进行调试短信
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AeYwN6S-1684465170313)(images/谷粒商城项目笔记/image-20201110085315288.png)]
输入对应手机号,appCode 具体功能不做演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aeajrzAw-1684465170313)(images/谷粒商城项目笔记/image-20201110085348103.png)]
4、使用 Java 测试短信是否能进行发送
往下拉找到对应 Java 代码
注意:
服务商提供的接口地址,请求参数都不同,请参考服务商提供的测试代码
@Test
public void contextLoads() {
String host = "http://dingxin.market.alicloudapi.com";
String path = "/dx/sendSms";
String method = "POST";
String appcode = "你自己的AppCode";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("mobile", "159xxxx9999");
querys.put("param", "code:1234");
querys.put("tpl_id", "TP1711063");
Map<String, String> bodys = new HashMap<String, String>();
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
需要导入对应工具类,参照注释就行
验证码防刷校验
用户要是一直提交验证码
- 前台:限制一分钟后提交
- 后台:存入redis 如果有就返回
/**
* 发送短信验证码
* @param phone 手机号
* @return
*/
@GetMapping("/sms/sendCode")
@ResponseBody
public R sendCode(@RequestParam("phone") String phone) {
// TODO 1、接口防刷
// 先从redis中拿取
String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
if(!StringUtils.isEmpty(redisCode)) {
// 拆分
long l = Long.parseLong(redisCode.split("_")[1]);
// 当前系统事件减去之前验证码存入的事件 小于60000毫秒=60秒
if (System.currentTimeMillis() -l < 60000) {
// 60秒内不能再发
R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(),BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());
}
}
// 2、验证码的再次效验
// 数据存入 =》redis key-phone value - code sms:code:131xxxxx - >45678
String code = UUID.randomUUID().toString().substring(0,5).toUpperCase();
// 拼接验证码
String substring = code+"_" + System.currentTimeMillis();
// redis缓存验证码 防止同一个phone在60秒内发出多次验证吗
redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,substring,10, TimeUnit.MINUTES);
// 调用第三方服务发送验证码
thirdPartFeignService.sendCode(phone,code);
return R.ok();
}
一步一坑注册页环境
1、编写 vo 接收页面提交
- 使用到了 JSR303校验
/**
* 注册数据封装Vo
* @author gcq
* @Create 2020-11-09
*/
@Data
public class UserRegistVo {
@NotEmpty(message = "用户名必须提交")
@Length(min = 6,max = 18,message = "用户名必须是6-18位字符")
private String userName;
@NotEmpty(message = "密码必须填写")
@Length(min = 6,max = 18,message = "密码必须是6-18位字符")
private String password;
@NotEmpty(message = "手机号码必须提交")
@Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机格式不正确")
private String phone;
@NotEmpty(message = "验证码必须填写")
private String code;
}
2、页面提交数据与Vo一致
设置 name
属性与 Vo
一致,方便将传递过来的数据转换成 JSON
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LH5vlcpn-1684465170315)(images/谷粒商城项目笔记/image-20201110100732631.png)]
3、数据校验
/**
* //TODO 重定向携带数据,利用session原理,将数据放在session中,
* 只要跳转到下一个页面取出这个数据,session中的数据就会删掉
* //TODO分布式下 session 的问题
* RedirectAttributes redirectAttributes 重定向携带数据
* redirectAttributes.addFlashAttribute("errors", errors); 只能取一次
* @param vo 数据传输对象
* @param result 用于验证参数
* @param redirectAttributes 数据重定向
* @return
*/
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result,
RedirectAttributes redirectAttributes) {
// 校验是否通过
if (result.hasErrors()) {
// 拿到错误信息转换成Map
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
//用一次的属性
redirectAttributes.addFlashAttribute("errors",errors);
// 校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
// 将传递过来的验证码 与 存redis中的验证码进行比较
String code = vo.getCode();
String s = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
if (!StringUtils.isEmpty(s)) {
// 验证码和redis中的一致
if(code.equals(s.split("_")[0])) {
// 删除验证码:令牌机制
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
// 调用远程服务,真正注册
R r = memberFeignService.regist(vo);
if (r.getCode() == 0) {
// 远程调用注册服务成功
return "redirect:http://auth.gulimall.com/login.html";
} else {
Map<String, String> errors = new HashMap<>();
errors.put("msg",r.getData(new TypeReference<String>(){}));
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
Map<String, String> errors = new HashMap<>();
errors.put("code", "验证码错误");
redirectAttributes.addFlashAttribute("code", "验证码错误");
// 校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
Map<String, String> errors = new HashMap<>();
errors.put("code", "验证码错误");
redirectAttributes.addFlashAttribute("code", "验证码错误");
// 校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
}
4、前端页面接收错误信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUa66aXY-1684465170315)(images/谷粒商城项目笔记/image-20201110101306173.png)]
5、异常机制 & 用户注册
- 用户注册单独抽出了一个服务
Controller
/**
* 注册
* @param registVo
* @return
*/
@PostMapping("/regist")
public R regist(@RequestBody MemberRegistVo registVo) {
try {
memberService.regist(registVo);
} catch (PhoneExsitException e) {
// 返回对应的异常信息
return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());
} catch (UserNameExistException e) {
return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(),BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());
}
return R.ok();
}
@Override
public void regist(MemberRegistVo registVo) {
MemberDao memberDao = this.baseMapper;
MemberEntity entity = new MemberEntity();
// 设置默认等级
MemberLevelEntity memberLevelEntity = memberLevelDao.getDefaultLevel();
entity.setLevelId(memberLevelEntity.getId());
// 检查手机号和用户名是否唯一
checkPhoneUnique(registVo.getPhone());
checkUserNameUnique(registVo.getUserName());
entity.setMobile(registVo.getPhone());
entity.setUsername(registVo.getUserName());
//密码要加密存储
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode(registVo.getPassword());
entity.setPassword(encode);
memberDao.insert(entity);
}
@Override
public void checkPhoneUnique(String phone) throws PhoneExsitException {
MemberDao memberDao = this.baseMapper;
Integer mobile = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (mobile > 0) {
throw new PhoneExsitException();
}
}
@Override
public void checkUserNameUnique(String username) throws UserNameExistException {
MemberDao memberDao = this.baseMapper;
Integer count = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
if (count > 0) {
throw new PhoneExsitException();
}
}
此处引入一个问题
- 密码是直接存入数据库吗? 这样子会导致数据的不安全,
- 引出了使用 MD5进行加密,但是MD5加密后,别人任然可以暴力破解
- 可以使用加盐的方式,将密码加密后,得到一串随机字符,
- 随机字符和密码和进行验证相同结果返回true否则false
至此注册相关结束~
账号密码登录
1、定义 Vo 接收数据提交
/**
* @author gcq
* @Create 2020-11-10
*/
@Data
public class UserLoginVo {
private String loginacct;
private String password;
}
同时需要保证前端页面提交字段与 Vo 类中一致
2、在 Member 服务中编写接口
@Override
public MemberEntity login(MemberLoginVo vo) {
String loginacct = vo.getLoginacct();
String password = vo.getPassword();
// 1、去数据库查询 select * from ums_member where username=? or mobile =?
MemberDao memberDao = this.baseMapper;
MemberEntity memberEntity = memberDao.selectOne(new QueryWrapper<MemberEntity>()
.eq("username", loginacct).or().
eq("mobile", loginacct));
if (memberDao == null) {
// 登录失败
return null;
} else {
// 获取数据库的密码
String passwordDB = memberEntity.getPassword();
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 和用户密码进行校验
boolean matches = passwordEncoder.matches(password, passwordDB);
if(matches) {
// 密码验证成功 返回对象
return memberEntity;
} else {
return null;
}
}
}
分布式 Session不共享不同步问题
我们在auth.gulimall.com中保存session,但是网址跳转到 gulimall.com中,取不出auth.gulimall.com中保存的session,这就造成了微服务下的session不同步问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A985hIGv-1684465170316)(images/谷粒商城项目笔记/image-20201111103637615.png)]
1、Session同步解决方案-分布式下session共享问题
同一个服务复制多个,但是session还是只能在一个服务上保存,浏览器也是只能读取到一个服务的session
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uay6KVtX-1684465170316)(images/谷粒商城项目笔记/image-20201111104758917.png)]
2、Session共享问题解决-session复制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQrufnE0-1684465170316)(images/谷粒商城项目笔记/image-20201111104851977.png)]
3、Session共享问题解决-客户端存储
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVWVmwO4-1684465170317)(images/谷粒商城项目笔记/image-20201111104913888.png)]
4、Session共享问题解决-hash一致性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VExJn2fk-1684465170317)(images/谷粒商城项目笔记/image-20201111105039741.png)]
5、Session共享问题解决-统一存储
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4gjDUMu-1684465170317)(images/谷粒商城项目笔记/image-20201111105135178.png)]
SpringSession整合
1、官网文档 阅读
- 进入到 Spring Framework
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIlfzRWl-1684465170317)(images/谷粒商城项目笔记/image-20201111144109273.png)]
2、选择Spring Session文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMsibOLh-1684465170317)(images/谷粒商城项目笔记/image-20201111144350506.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ip53V5XF-1684465170318)(images/谷粒商城项目笔记/image-20201111144438592.png)]
3、开始使用Spring Session
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Owk6IRbW-1684465170318)(images/谷粒商城项目笔记/image-20201111144639786.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGr90n3v-1684465170318)(images/谷粒商城项目笔记/image-20201111144718176.png)]
整合SpringBoot
1、添加Pom.xml依赖
https://docs.spring.io/spring-session/docs/2.5.0/reference/html5/#samples
auth 服务、product 服务、 search 服务 pom文件
<!-- 整合 spring session 实现 session 共享-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2、application.yml 配置
spring:
session:
store-type: redis
**主启动类增加注解:@EnableRedisHttpSession **
3、reids配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SimYg4hL-1684465170318)(images/谷粒商城项目笔记/image-20201111150056671.png)]
4、启动类加上如下注解
@EnableRedisHttpSession // 整合spring session
自定义 SpringSession 完成 Session 子域共享
CookieSerializer
api文档参考:https://docs.spring.io/spring-session/docs/2.4.1/reference/html5/index.html#api-cookieserializer
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RMbTcpO-1684465170318)(images/谷粒商城项目笔记/image-20210101124234037.png)]
指定redis序列化
文档地址:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GzdrRUTm-1684465170319)(images/谷粒商城项目笔记/image-20210101124513827.png)]
redis中json序列化
官网文档地址:https://docs.spring.io/spring-session/docs/2.4.1/reference/html5/index.html#samples
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQesFXfm-1684465170319)(images/谷粒商城项目笔记/image-20210101125216426.png)]
提供的实例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nz2IORFc-1684465170319)(images/谷粒商城项目笔记/image-20210101125303807.png)]
/**
* SpringSession整合子域
* 以及redis数据存储为json
* @author gcq
* @Create 2020-11-11
*/
@Configuration
public class GulimallSessionConfig {
/**
* 设置cookie信息
* @return
*/
@Bean
public CookieSerializer CookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
// 设置一个域名的名字
cookieSerializer.setDomainName("gulimall.com");
// cookie的路径
cookieSerializer.setCookieName("GULIMALLSESSION");
return cookieSerializer;
}
/**
* 设置json转换
* @return
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
// 使用jackson提供的转换器
return new GenericJackson2JsonRedisSerializer();
}
}
SpringSession 原理
/**
* 核心原理
* 1、@EnableRedisHttpSession导入RedisHttpSessionConfiguration配置
* 1、给容器中添加了一个组件
* sessionRepository = 》》》【RedisOperationsSessionRepository】 redis 操作 session session的增删改查封装类
* 2、SessionRepositoryFilter==>:session存储过滤器,每个请求过来必须经过Filter
* 1、创建的时候,就自动从容器中获取到了SessionRepostiory
* 2、原始的request,response都被包装了 SessionRepositoryRequestWrapper、SessionRepositoryResponseWrapper
* 3、以后获取session.request.getSession()
* SessionRepositoryResponseWrapper
* 4、wrappedRequest.getSession() ==>SessionRepository
*
* 装饰者模式
* spring-redis的相关功能:
* 执行session相关操作后,redis里面存储的时间也会刷新
*/
核心源码是:
-
SessionRepositoryFilter
类下面的doFilterInternal
方法 -
及那个
request
、response
包装成SessionRepositoryRequestWrapper
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0tIXChY-1684465170319)(images/谷粒商城项目笔记/image-20201111195249024.png)]
授权认证
OAuth2.0
- **OAuth:**OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们的数据的内容
- **OAuth2.0:**对于用户相关的 OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保存用户数据的安全和隐私,第三方网站访问用户数据前都需要显示向用户授权
文档地址:
相关流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Th72zhsj-1684465170319)(images/谷粒商城项目笔记/image-20201110154532752.png)]
微博登录准备工作
1、进入微博开放平台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0m7ore6-1684465170320)(images/谷粒商城项目笔记/image-20201110154702360.png)]
2、登录微博,进入微连接,选择网站接入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4Qu4PwS-1684465170320)(images/谷粒商城项目笔记/image-20201110160834589.png)]
3、选择立即接入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKCYa0fq-1684465170320)(images/谷粒商城项目笔记/image-20201110161001013.png)]
4、创建自己的应用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FXcTqi2Z-1684465170320)(images/谷粒商城项目笔记/image-20201110161032203.png)]
5、我们可以在开发阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZI4BDY2-1684465170321)(images/谷粒商城项目笔记/image-20201110161152105.png)]
6、进入高级信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78P78L1U-1684465170321)(images/谷粒商城项目笔记/image-20201110161407018.png)]
7、添加测试账号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1O4gaeR9-1684465170321)(images/谷粒商城项目笔记/image-20201110161451881.png)]
8、进入文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdXFTu7P-1684465170322)(images/谷粒商城项目笔记/image-20201110161634486.png)]
微博登录代码实现
微博登录流程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwHRMdF7-1684465170322)(images/谷粒商城项目笔记/image-20201231084733753.png)]
注册流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwgXBoBB-1684465170322)(images/谷粒商城项目笔记/image-20201231084909415.png)]
账号密码登录流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wSrIe04-1684465170322)(images/谷粒商城项目笔记/image-20201231012134722.png)]
手机验证码发送流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOOWqEWp-1684465170323)(images/谷粒商城项目笔记/image-20201231012207446.png)]
查看微博开放平台文档
https://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBIbuebG-1684465170323)(images/谷粒商城项目笔记/image-20201111093019560.png)]
点击微博登录后,跳转到微博授权页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9ZhzAC7-1684465170323)(images/谷粒商城项目笔记/image-20201111093153199.png)]
用户授权后调用回调接口,并带上参数code换取AccessToken
/**
* 回调接口
* @param code
* @return
* @throws Exception
*/
@GetMapping("/oauth2.0/weibo/success")
public String weibo(@RequestParam("code") String code) throws Exception {
// 1、根据code换取accessToken
Map<String, String> map = new HashMap<>();
map.put("client_id", "1133714539");
map.put("client_secret", "f22eb330342e7f8797a7dbe173bd9424");
map.put("grant_type", "authorization_code");
map.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/weibo/success");
map.put("code", code);
HttpResponse response = HttpUtils.doPost("https://api.weibo.com",
"/oauth2/access_token",
"post",
new HashMap<>(),
map,
new HashMap<>());
// 状态码为200请求成功
if (response.getStatusLine().getStatusCode() == 200 ){
// 获取到了accessToken
String json = EntityUtils.toString(response.getEntity());
SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
R r = memberFeignService.OAuthlogin(socialUser);
if (r.getCode() == 0) {
MemberRespVo data = r.getData("data", new TypeReference<MemberRespVo>() {
});
log.info("登录成功:用户:{}",data.toString());
// 2、登录成功跳转到首页
return "redirect:http://gulimall.com";
} else {
// 注册失败
return "redirect:http://auth.gulimall.com/login.html";
}
} else {
// 请求失败
// 注册失败
return "redirect:http://auth.gulimall.com/login.html";
}
// 2、登录成功跳转到首页
return "redirect:http://gulimall.com";
}
拿到AccessToken 请求对应接口拿到信息
@Override
public MemberEntity login(SocialUser vo) {
// 登录和注册合并逻辑
String uid = vo.getUid();
MemberDao memberDao = this.baseMapper;
// 根据社交用户的uuid查询
MemberEntity memberEntity = memberDao.selectOne(new QueryWrapper<MemberEntity>()
.eq("social_uid", uid));
// 能查询到该用户
if (memberEntity != null ){
// 更新对应值
MemberEntity update = new MemberEntity();
update.setId(memberEntity.getId());
update.setAccessToken(vo.getAccess_token());
update.setExpiresIn(vo.getExpires_in());
memberDao.updateById(update);
memberEntity.setAccessToken(vo.getAccess_token());
memberEntity.setExpiresIn(vo.getExpires_in());
return memberEntity;
} else {
// 2、没有查询到当前社交用户对应的记录就需要注册一个
MemberEntity regist = new MemberEntity();
try {
Map<String,String> query = new HashMap<>();
// 设置请求参数
query.put("access_token",vo.getAccess_token());
query.put("uid",vo.getUid());
// 发送get请求获取社交用户信息
HttpResponse response = HttpUtils.doGet("https://api.weibo.com/",
"2/users/show.json",
"get",
new HashMap<>(),
query);
// 状态码为200 说明请求成功
if (response.getStatusLine().getStatusCode() == 200){
// 将返回结果转换成json
String json = EntityUtils.toString(response.getEntity());
// 利用fastjson将请求返回的json转换为对象
JSONObject jsonObject = JSON.parseObject(json);
// 拿到需要的值
String name = jsonObject.getString("name");
String gender = jsonObject.getString("gender");
//.. 拿到多个信息
regist.setNickname(name);
regist.setGender("m".equals(gender) ? 1 : 0);
}
} catch (Exception e) {
e.printStackTrace();
}
// 设置社交用户相关信息
regist.setSocialUid(vo.getUid());
regist.setAccessToken(vo.getAccess_token());
regist.setExpiresIn(vo.getExpires_in());
memberDao.insert(regist);
return regist;
}
}
SSO-单点登录
什么是SSO
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的 。
参考:https://gitee.com/xuxueli0323/xxl-sso
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUhDhAMj-1684465170323)(images/谷粒商城项目笔记/image-20220512161446123.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wt1A6fRR-1684465170324)(images/谷粒商城项目笔记/image-20220512161455587.png)]
单点登录框演示
XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有"轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。现已开放源代码,开箱即用。
首先对整个项目进行:mvn clean package -Dmaven.skip.test=true
xxl-sso-server:
- 8080/xxl-sso-server
- 编排:
- ssoserver.com 登陆验证服务器
- client1.com 客户端1
- client2.com 客户端2
先启动xxl-sso-server 然后启动client1
只要 client1
登录成功 client2
就不用进行登录直接登录成功
代码测试:
sso-client
/**
* @author gcq
* @Create 2020-11-12
*/
@Controller
public class HelloController {
@Value("${sso.server.url}")
private String ssoServerUrl;
/**
* 无需登录就可以访问
* @return
*/
@ResponseBody
@RequestMapping("/hello")
public String hello() {
return "hello";
}
/**
* 需要验证的连接
* @param model
* @param token 只要是ssoserver登陆成功回来就会带上
* @return
*/
@GetMapping("/employees")
public String employees(Model model, HttpSession session,
@RequestParam(value="token",required = false) String token) {
if (!StringUtils.isEmpty(token)) {
// 去ssoserver登录成功调回来就会带上
//TODO 1、去ssoserver获取当前token真正对应的用户信息
RestTemplate restTemplate = new RestTemplate();
// 使用restTemplate进行远程请求
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?token=" + token, String.class);
// 拿到数据
String body = forEntity.getBody();
// 设置到session中
session.setAttribute("loginUser",body);
}
Object loginUser = session.getAttribute("loginUser");
if (loginUser == null ){
// 没有登录重定向到登陆页面,并带上当前地址
return "redirect:" + ssoServerUrl + "?redirect_url=http://client1.com:8081/employees";
} else {
List<String> emps = new ArrayList<>();
emps.add("张三");
emps.add("李四");
model.addAttribute("emps",emps);
return "list";
}
}
}
sso-server
/**
* @author gcq
* @Create 2020-11-12
*/
@Controller
public class LoginController {
@Autowired
StringRedisTemplate redisTemplate;
/**
* 根据token从redis中查询用户信息
* @param token
* @return
*/
@ResponseBody
@GetMapping("/userInfo")
public String userInfo(@RequestParam("token") String token) {
String s = redisTemplate.opsForValue().get(token);
return s;
}
@GetMapping("login.html")
public String login(@RequestParam("redirect_url") String url, Model model,
@CookieValue(value = "sso_token",required = false)String sso_token) {
if (!StringUtils.isEmpty(sso_token)) {
//说明有人之前登录过,给浏览器留下了痕迹
return "redirect:" + url + "?token=" + sso_token;
}
// 添加url到model地址中,在前端页面进行取出
model.addAttribute("url",url);
return "login";
}
/**
* 登录
* @param username
* @param password
* @param url client端带过来的地址
* @return
*/
@PostMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("url") String url,
HttpServletResponse response){
// 账号密码不为空
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
// 登陆成功
// 把登录成功的用户存起来
String uuid = UUID.randomUUID().toString().replace("-","");
redisTemplate.opsForValue().set(uuid,username);
// 将uuid存入cookie
Cookie token = new Cookie("sso_token",uuid);
response.addCookie(token);
// 保存到cookie
return "redirect:" + url + "?token=" + uuid;
}
// 登录失败,展示登录页
return "login";
}
}
幂等性
什么是幂等性
接口幂等性就是用户对同一操作发起的一次请求和多次请求结果是一致的,不会因为多次点击而产生了副作用,比如支付场景,用户购买了商品,支付扣款成功,但是返回结果的时候出现了网络异常,此时钱已经扣了,用户再次点击按钮,此时就会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。。。这就没有保证接口幂等性
那些情况需要防止
用户多次点击按钮
用户页面回退再次提交
微服务互相调用,由于网络问题,导致请求失败,feign触发重试机制
其他业务情况
什么情况下需要幂等
以 SQL 为例,有些操作时天然幂等的
SELECT * FROM table WHERE id =? 无论执行多少次都不会改变状态是天然的幂等
UPDATE tab1 SET col1=1 WHERE col2=2 无论执行成功多少状态都是一致的,也是幂等操作
delete from user where userid=1 多次操作,结果一样,具备幂等性
insert into user(userid,name) values(1,’ a’ ) 如userid为唯一主键,即重复上面的业务,只会插入一条用户记录,具备幂等性
UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,不是幂等的。insert into user(userid,name) values(,a")如userid不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。
幂等解决方案
token 机制
1、服务端提供了发送 token
的接口,我们在分析业务的时候,哪些业务是存在幂等性问题的,就必须在执行业务前,先获取 token
,服务器会把 token
保存到 redis 中
2、然后调用业务接口请求时, 把 token
携带过去,一般放在请求头部
3、服务器判断 token
是否存在 redis
,存在表示第一次请求,然后删除 token
,继续执行业务
4、如果判断 token
不存在 redis
中,就表示重复操作,直接返回重复标记给 client
,这样就保证了业务代码,不被重复执行
危险性:
1、先删除 token 还是后删除 token:
- 先删除可能导致,业务确实没有执行,重试还得带上之前的 token, 由于防重设计导致,请求还是不能执行
- 后删除可能导致,业务处理成功,但是服务闪断,出现超时,没有删除掉token,别人继续重试,导致业务被执行两次
- 我们最后设计为先删除 token,如果业务调用失败,就重新获取 token 再次请求
2、Token 获取,比较 和删除 必须是原子性
- redis.get(token),token.equals、redis.del(token),如果说这两个操作都不是原子,可能导致,在高并发下,都 get 同样的数据,判断都成功,继续业务并发执行
- 可以在 redis 使用 lua 脚本完成这个操作
"if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"
各种锁机制
1、数据库悲观锁
select * from xxx where id = 1 for update;
for update 查询的时候锁定这条记录 别人需要等待
悲观锁使用的时候一般伴随事务一起使用,数据锁定时间可能会很长,需要根据实际情况选用,另外需要注意的是,id字段一定是主键或唯一索引,不然可能造成锁表的结果,处理起来会非常麻烦
2、数据库的乐观锁
这种方法适合在更新的场景中
update t_goods set count = count - 1,version = version + 1 where good_id = 2 and version = 1
根据 version 版本,也就是在操作数据库存前先获取当前商品的 version 版本号,然后操作的时候带上 version 版本号,我们梳理下,我们第一次操作库存时,得
到 version 为 1,调用库存服务 version = 2,但返回给订单服务出现了问题,订单服务又一次调用了库存服务,当订单服务传的 version 还是 1,再执行上面的
sql 语句 就不会执行,因为 version 已经变成 2 了,where 条件不成立,这样就保证了不管调用几次,只会真正处理一次,乐观锁主要使用于处理读多写少的问题
3、业务层分布锁
如果多个机器可能在同一时间处理相同的数据,比如多台机器定时任务拿到了相同的数据,我们就可以加分布式锁,锁定此数据,处理完成后后释放锁,获取锁必须先判断这个数据是否被处理过
各种唯一约束
1、数据库唯一约束
插入数据,应该按照唯一索引进行插入,比如订单号,相同订单就不可能有两条订单插入,我们在数据库层面防止重复
这个机制利用了数据库的主键唯一约束的特性,解决了 insert场 景时幂等问题,但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键
如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关
2、redis set 防重
很多数据需要处理,只能被处理一次,比如我们可以计算数据的 MD5 将其放入 redis 的
set,每次处理数据,先看这个 MD5 是否已经存在,存在就不处理
防重表
使用订单表 orderNo
做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中,这样就保证了重复请求时,因为去重表有唯一
约束,导致请求失败,避免了幂等性等问题,去重表和业务表应该在同一个库中,这样就保证了在同一个事务,即使业务操作失败,也会把去重表的数据回滚,这
个很好的保证了数据的一致性,
redis防重也算
全局请求唯一id
调用接口时,生成一个唯一的id,redis 将数据保存到集合中(去重),存在即处理过,可以使用 nginx 设置每一个请求一个唯一id
proxy_set_header X-Request-Id $Request_id