谷粒商城建立环境

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

sudo mkdir -p /etc/docker 
// 阿里云 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 
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine 
// 阿里云的源地址 $ 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 
sudo yum install docker-ce docker-ce-cli containerd.io 
sudo systemctl start docker 
sudo systemctl enable docker 
docker pull mysql:5.7 
 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 容器端口映射到主机的端口 
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 
docker restart mysql 
sudo docker update mysql --restart=always //docker中开机自启 
docker pull redis 
mkdir -p /mydata/redis/conf touch /mydata/redis/conf/redis.conf 
vim /mydata/redis/conf/redis.conf 

具体内容

appendonly yes 

表示数据持久化,不会重启就丢。

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 
docker exec -it redis redis-cli 
docker restart redis 
sudo docker update redis --restart=always 

安装包在阿里云盘。(IT技术学习 – gulimall-soft)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUKMjEre-1684465170291)(images\谷粒商城项目笔记\image-20220612162507048.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W66i1qPz-1684465170291)(images\谷粒商城项目笔记\image-20220612162543747.png)]

6、统一开发环境

1、查看云端目前支持安装的jdk版本

[root@localhost ~] 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 ~] 

3、验证

[root@localhost ~] openjdk version "1.8.0_151" 

4、安装openjdk-devel(jps)

sudo yum install java-1.8.0-openjdk-devel.x86_64 
  • 下载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  
    • 编辑settings.xml文件

      cd conf  vi 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)]

  • 配置阿里云镜像

    <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)]

  • 下载地址:https://code.visualstudio.com/Download (阿里网盘也有备份)

    • 安装插件

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yO0aarx7-1684465170293)(images/谷粒商城项目笔记/image-20220502162914480.png)]

  • 安装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)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vvp3o3Y-1684465170295)(images/谷粒商城项目笔记/image-20220502164939532.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUpKJpTs-1684465170295)(images/谷粒商城项目笔记/image-20220502165236550.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tz2nZmd0-1684465170295)(images/谷粒商城项目笔记/image-20220502175101221.png)]

  • 下载安装包(阿里云盘:IT技术学习 – gulimall-soft)
  • 安装后账号登陆
    • 账号:1920459132@qq.com
    • 密码:19950420liu

7、nacos搭建

地址:https://github.com/alibaba/nacos/releases/tag/1.1.3 (阿里网盘有备份)

双击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/  

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]

注意:不能用默认的 elasticsearch-plugin.install xxx.zip 进行自动安装

安装后拷贝到 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] 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] [root@545c3c3a9ff9 elasticsearch] LICENSE.txt NOTICE.txt README.textile bin config data jdk lib logs modules plugins [root@545c3c3a9ff9 elasticsearch] [root@545c3c3a9ff9 bin] 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] ik 
  • 使用前记得重启elasticsearch

    docker restart elasticsearch 

分词器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruXZu4lA-1684465170297)(images/谷粒商城项目笔记/image-20201026092255250.png)]

docker run -p 80:80 –name nginx -d nginx:1.10

  • Docker 安装 Nginx

    • 创建要挂载的配置目录

      mkdir -p /mydata/nginx/conf 
    • 启动临时nginx容器

      docker run -p 80:80 --name nginx -d nginx:1.10 
    • 拷贝出 Nginx 容器的配置

       docker container cp nginx:/etc/nginx /mydata/nginx/conf  mv /mydata/nginx/conf/nginx/* /mydata/nginx/conf/  rm -rf /mydata/nginx/conf/nginx 
    • 删除临时nginx容器

       docker stop 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 配置自定义词库

     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

    • 后面追加

       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;  keepalive_timeout 65;  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;   location / { proxy_pass http://192.168.91.1:10000; }    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;  keepalive_timeout 65;  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;   location / { proxy_set_header Host $host; proxy_pass http://gulimall; }    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)]

性能与压力测试

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 错误解决

cmd 查看自己的jdk版本,找到对应的

docker stats 查看相关命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzyx5m7m-1684465170299)(images/谷粒商城项目笔记/image-20220504155752345.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1itYZZCj-1684465170300)(images/谷粒商城项目笔记/image-20220504155833695.png)]

解压后打开bin目录

点击jmeter.bat

就开启了jmeter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eU0DDJwW-1684465170300)(images/谷粒商城项目笔记/image-20201029084634498.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7p8CxwX0-1684465170300)(images/谷粒商城项目笔记/image-20201029085843220.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R6ktEFPg-1684465170300)(images/谷粒商城项目笔记/image-20201029085942442.png)]

汇总图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1S5qweF-1684465170302)(images/谷粒商城项目笔记/image-20220511173747297.png)]

  • 首先,把商品服务中静态文件夹 index 放到 nginx 下 /mydata/nginx/html/static目录;
  • 给模板中所有静态资源的请求路径前都加上 /static;
  • 修改 Nginx 配置文件 /mydata/nginx/conf/conf.d/gulimall.conf
 location /static/ { root /user/share/nginx/html; } 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zZlT4DH-1684465170302)(images/谷粒商城项目笔记/image-20220511173837394.png)]

缓存和分布式锁

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 客户端

application.yaml

Spring: redis: host: 192.168.56.10 port: 6379 

RedisAutoConfig.java

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVBLpUpI-1684465170303)(images/谷粒商城项目笔记/image-20201031154710108.png)]

@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); } 
/** * 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 作为分布锁,分布式对象等功能--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency> 
@Configuration public class MyRedissonConfig {  @Bean(destroyMethod = "shutdown") public RedissonClient redisson() throws IOException {  Config config = new Config();  config.useSingleServer().setAddress("redis://192.168.163.131:6379");  return Redisson.create(config); } } 
 Rlock lock = redisson.getLock("my-lock");  lock.lock(); try { System.out.println("加锁成功,执行业务..."); } catch (Exception e) { } finally {  lock.unlock(); } 
@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)]

二话不说,上代码!!!

/** * 保证一定能读取到最新数据,修改期间,写锁是一个排他锁(互斥锁,独享锁)读锁是一个共享锁 * 写锁没释放读锁就必须等待 * 读 + 读 相当于无锁,并发读,只会在 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)]

官网!!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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就会放行

官网!!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 下;

配置 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 
- id: mall_search_route uri: lb://mall-search predicates: - Host=search.gulimall.com 

配置 /list.html 请求转发到 list 模板

 @GetMapping(value = "/list.html") public String listPage(SearchParam param, Model model, HttpServletRequest request) { return "list"; } 

异步和线程池

1、继承 Thread

2、实现 Runnable

3、实现 Callable 接口 + FutureTask(可以拿到返回结果,可以处理异常)

4、线程池

方式一和方式二 主进程无法获取线程的运算结果,不适合当前场景

方式三:主进程可以获取当前线程的运算结果,但是不利于控制服务器种的线程资源,可以导致服务器资源耗尽

方式四:通过如下两种方式初始化线程池

Executors.newFixedThreadPool(3); //或者 new ThreadPollExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler); 

通过线程池性能稳定,也可以获取执行结果,并捕获异常,但是,在业务复杂情况下,一个异步调用可能会依赖另一个异步调用的执行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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个默认拒绝策略

  • newCacheThreadPool 
    • 创建一个可缓存的线程池,如果线程池长度超过需要,可灵活回收空闲线程,若无可回收,则新建线程
  • newFixedThreadPool 
    • 创建一个指定长度的线程池,可控制线程最大并发数,超出的线程会再队列中等待
  • newScheduleThreadPool 
    • 创建一个定长线程池,支持定时及周期性任务执行
  • newSingleThreadExecutor 
    • 创建一个单线程化的线程池,她只会用唯一的工作线程来执行任务,保证所有任务
  • 降低资源的消耗
    • 通过重复利用已创建好的线程降低线程的创建和销毁带来的损耗
  • 提高响应速度
    • 因为线程池中的线程没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
  • 提高线程的客观理性
    • 线程池会根据当前系统的特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配

业务场景:

查询商品详情页逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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; }); 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 默认是异步执行的,同之前,

以上都要前置任务完成

  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);          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 的结果,处理任务,也没有返回值

       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<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)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqNvGYjt-1684465170313)(images/谷粒商城项目笔记/image-20201110084936446.png)]

在云市场就能看到购买的服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dcqJQnJE-1684465170313)(images/谷粒商城项目笔记/image-20201110085141506.png)]

在购买短信的页面,能进行调试短信

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AeYwN6S-1684465170313)(images/谷粒商城项目笔记/image-20201110085315288.png)]

输入对应手机号,appCode 具体功能不做演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aeajrzAw-1684465170313)(images/谷粒商城项目笔记/image-20201110085348103.png)]

往下拉找到对应 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>();  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 {  HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys); System.out.println(response.toString());   } catch (Exception e) { e.printStackTrace(); } } 

需要导入对应工具类,参照注释就行

用户要是一直提交验证码

  • 前台:限制一分钟后提交
  • 后台:存入redis 如果有就返回
 @GetMapping("/sms/sendCode") @ResponseBody public R sendCode(@RequestParam("phone") String phone) {   String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone); if(!StringUtils.isEmpty(redisCode)) {  long l = Long.parseLong(redisCode.split("_")[1]);  if (System.currentTimeMillis() -l < 60000) {  R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(),BizCodeEnume.SMS_CODE_EXCEPTION.getMsg()); } }   String code = UUID.randomUUID().toString().substring(0,5).toUpperCase();  String substring = code+"_" + System.currentTimeMillis();  redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,substring,10, TimeUnit.MINUTES);  thirdPartFeignService.sendCode(phone,code); return R.ok(); } 
  • 使用到了 JSR303校验
 @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; } 

设置 name 属性与 Vo 一致,方便将传递过来的数据转换成 JSON

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LH5vlcpn-1684465170315)(images/谷粒商城项目笔记/image-20201110100732631.png)]

 @PostMapping("/regist") public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes) {  if (result.hasErrors()) {  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"; }  String code = vo.getCode(); String s = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone()); if (!StringUtils.isEmpty(s)) {  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"; } } 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUa66aXY-1684465170315)(images/谷粒商城项目笔记/image-20201110101306173.png)]

  • 用户注册单独抽出了一个服务

Controller

 @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

至此注册相关结束~

 @Data public class UserLoginVo { private String loginacct; private String password; } 

同时需要保证前端页面提交字段与 Vo 类中一致

@Override public MemberEntity login(MemberLoginVo vo) { String loginacct = vo.getLoginacct(); String password = vo.getPassword();  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; } } } 

我们在auth.gulimall.com中保存session,但是网址跳转到 gulimall.com中,取不出auth.gulimall.com中保存的session,这就造成了微服务下的session不同步问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A985hIGv-1684465170316)(images/谷粒商城项目笔记/image-20201111103637615.png)]

同一个服务复制多个,但是session还是只能在一个服务上保存,浏览器也是只能读取到一个服务的session

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uay6KVtX-1684465170316)(images/谷粒商城项目笔记/image-20201111104758917.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQrufnE0-1684465170316)(images/谷粒商城项目笔记/image-20201111104851977.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVWVmwO4-1684465170317)(images/谷粒商城项目笔记/image-20201111104913888.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VExJn2fk-1684465170317)(images/谷粒商城项目笔记/image-20201111105039741.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4gjDUMu-1684465170317)(images/谷粒商城项目笔记/image-20201111105135178.png)]

  • 进入到 Spring Framework

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIlfzRWl-1684465170317)(images/谷粒商城项目笔记/image-20201111144109273.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMsibOLh-1684465170317)(images/谷粒商城项目笔记/image-20201111144350506.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ip53V5XF-1684465170318)(images/谷粒商城项目笔记/image-20201111144438592.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Owk6IRbW-1684465170318)(images/谷粒商城项目笔记/image-20201111144639786.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGr90n3v-1684465170318)(images/谷粒商城项目笔记/image-20201111144718176.png)]

https://docs.spring.io/spring-session/docs/2.5.0/reference/html5/#samples

auth 服务、product 服务、 search 服务 pom文件

 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> 
spring: session: store-type: redis 

**主启动类增加注解:@EnableRedisHttpSession **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SimYg4hL-1684465170318)(images/谷粒商城项目笔记/image-20201111150056671.png)]

@EnableRedisHttpSession // 整合spring session 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RMbTcpO-1684465170318)(images/谷粒商城项目笔记/image-20210101124234037.png)]

文档地址:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GzdrRUTm-1684465170319)(images/谷粒商城项目笔记/image-20210101124513827.png)]

redis中json序列化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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(); } } 
/** * 核心原理 * 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 方法

  • 及那个 requestresponse 包装成 SessionRepositoryRequestWrapper

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0tIXChY-1684465170319)(images/谷粒商城项目笔记/image-20201111195249024.png)]

授权认证

  • **OAuth:**OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们的数据的内容
  • **OAuth2.0:**对于用户相关的 OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保存用户数据的安全和隐私,第三方网站访问用户数据前都需要显示向用户授权

文档地址:

相关流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Th72zhsj-1684465170319)(images/谷粒商城项目笔记/image-20201110154532752.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0m7ore6-1684465170320)(images/谷粒商城项目笔记/image-20201110154702360.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4Qu4PwS-1684465170320)(images/谷粒商城项目笔记/image-20201110160834589.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKCYa0fq-1684465170320)(images/谷粒商城项目笔记/image-20201110161001013.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FXcTqi2Z-1684465170320)(images/谷粒商城项目笔记/image-20201110161032203.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZI4BDY2-1684465170321)(images/谷粒商城项目笔记/image-20201110161152105.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78P78L1U-1684465170321)(images/谷粒商城项目笔记/image-20201110161407018.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1O4gaeR9-1684465170321)(images/谷粒商城项目笔记/image-20201110161451881.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdXFTu7P-1684465170322)(images/谷粒商城项目笔记/image-20201110161634486.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwgXBoBB-1684465170322)(images/谷粒商城项目笔记/image-20201231084909415.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wSrIe04-1684465170322)(images/谷粒商城项目笔记/image-20201231012134722.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOOWqEWp-1684465170323)(images/谷粒商城项目笔记/image-20201231012207446.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBIbuebG-1684465170323)(images/谷粒商城项目笔记/image-20201111093019560.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9ZhzAC7-1684465170323)(images/谷粒商城项目笔记/image-20201111093153199.png)]

 @GetMapping("/oauth2.0/weibo/success") public String weibo(@RequestParam("code") String code) throws Exception {  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<>());  if (response.getStatusLine().getStatusCode() == 200 ){  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());  return "redirect:http://gulimall.com"; } else {  return "redirect:http://auth.gulimall.com/login.html"; } } else {   return "redirect:http://auth.gulimall.com/login.html"; }  return "redirect:http://gulimall.com"; } 
@Override public MemberEntity login(SocialUser vo) {  String uid = vo.getUid(); MemberDao memberDao = this.baseMapper;  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 {  MemberEntity regist = new MemberEntity(); try { Map<String,String> query = new HashMap<>();  query.put("access_token",vo.getAccess_token()); query.put("uid",vo.getUid());  HttpResponse response = HttpUtils.doGet("https://api.weibo.com/", "2/users/show.json", "get", new HashMap<>(), query);  if (response.getStatusLine().getStatusCode() == 200){  String json = EntityUtils.toString(response.getEntity());  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; } } 

单点登录(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

 @Controller public class HelloController { @Value("${sso.server.url}") private String ssoServerUrl;  @ResponseBody @RequestMapping("/hello") public String hello() { return "hello"; }  @GetMapping("/employees") public String employees(Model model, HttpSession session, @RequestParam(value="token",required = false) String token) { if (!StringUtils.isEmpty(token)) {   RestTemplate restTemplate = new RestTemplate();  ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?token=" + token, String.class);  String body = forEntity.getBody();  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

 @Controller public class LoginController { @Autowired StringRedisTemplate redisTemplate;  @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; }  model.addAttribute("url",url); return "login"; }  @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);  Cookie token = new Cookie("sso_token",uuid); response.addCookie(token);  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不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。

1、服务端提供了发送 token 的接口,我们在分析业务的时候,哪些业务是存在幂等性问题的,就必须在执行业务前,先获取 token,服务器会把 token 保存到 redis 中

2、然后调用业务接口请求时, 把 token 携带过去,一般放在请求头部

3、服务器判断 token 是否存在 redis,存在表示第一次请求,然后删除 token,继续执行业务

4、如果判断 token 不存在 redis 中,就表示重复操作,直接返回重复标记给 client,这样就保证了业务代码,不被重复执行

危险性:

"if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 

select * from xxx where id = 1 for update;

for update 查询的时候锁定这条记录 别人需要等待

悲观锁使用的时候一般伴随事务一起使用,数据锁定时间可能会很长,需要根据实际情况选用,另外需要注意的是,id字段一定是主键或唯一索引,不然可能造成锁表的结果,处理起来会非常麻烦

这种方法适合在更新的场景中

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 条件不成立,这样就保证了不管调用几次,只会真正处理一次,乐观锁主要使用于处理读多写少的问题

如果多个机器可能在同一时间处理相同的数据,比如多台机器定时任务拿到了相同的数据,我们就可以加分布式锁,锁定此数据,处理完成后后释放锁,获取锁必须先判断这个数据是否被处理过

这个机制利用了数据库的主键唯一约束的特性,解决了 insert场 景时幂等问题,但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键

如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关

很多数据需要处理,只能被处理一次,比如我们可以计算数据的 MD5 将其放入 redis 的

set,每次处理数据,先看这个 MD5 是否已经存在,存在就不处理

使用订单表 orderNo 做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中,这样就保证了重复请求时,因为去重表有唯一

约束,导致请求失败,避免了幂等性等问题,去重表和业务表应该在同一个库中,这样就保证了在同一个事务,即使业务操作失败,也会把去重表的数据回滚,这

个很好的保证了数据的一致性,

redis防重也算

调用接口时,生成一个唯一的id,redis 将数据保存到集合中(去重),存在即处理过,可以使用 nginx 设置每一个请求一个唯一id

proxy_set_header X-Request-Id $Request_id

原文链接:https://blog.csdn.net/shushnu/article/details/130762428

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享