谷粒商城业务逻辑(一)

谷粒商城项目业务逻辑

基础篇

后台管理用人人开源三件套

后台管理vue只配置总的请求路由,从网关调用,其它服务只需要注册到网关就可以,

renren逆行生成的vue中给label标签赋值,在数据表格中要配置自己的数据样式对应表格,以及部分crud要自内容管理哪里赋予权限

修改vue代码界面的返回code状态码

所有模块注册到注册中心

http://localhost:88/api/study/student/list?t=1634975888504&page=1&limit=10&key=
分页查询

http://localhost:88/api/study/student/list?t=1634975888504&page=1&limit=10&key=1435

按照条件分页查询

cloud:  
	nacos:    
		discovery:      
			server-addr: 127.0.0.1:8848
			
application:
	name: kkmall-coupon
@EnableDiscoveryClient

自己本地调用

@RestController@RequestMapping("coupon/seckillsession")
public class SeckillSessionController {    
@Autowired    
private SeckillSessionService seckillSessionService;    
@GetMapping("/lates3DaySession")    
public R getLate3DaySession() {        
List<SeckillSessionEntity>=seckillSessionEntities=seckillSessionService.getLate3DaySession();        
return R.ok().setData(seckillSessionEntities);
}

其它模块调用

@FeignClient("kkmall-coupon")
public interface CouponFeignService {
    @GetMapping("/coupon/seckillsession/lates3DaySession")
    R getLate3DaySession();
}

使用逆向工程生成代码配合你自己的阿里云图片上传时

from表单的 item 要改为自己的 自定义上传图片

 <el-form-item label="海报">
       <el-upload
                :show-file-list="false"
                :http-request="fnUploadRequest"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeAvatarUpload">
            <img v-if="imageUrlh" :src="imageUrlh" class="avatar" alt="">
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
       </el-upload>
    </el-form-item>

并且return 双向绑定的数据以及data里面要操作的数据要改为自己自定义的

dataForm: {
          ticketid: 0,
          movename: '',
          moveprice: '',
        },
        imageUrlh: '',

'header': this.imageUrlh

点击按钮将外部vue弹出框组件,当作一个标签

1、引入

import Buy from './moveticket-buy'
components: {
      Buy
    },

2、使用

 <Buy v-if="buyVisible" ref="buy" @refreshDataList="getDataList"></Buy>

v-if=“buyVisible”

标识显示,false不显示

按钮

 <el-button type="text" size="small" @click="buyHandle(scope.row.ticketid)">购买</el-button>

方法

  // 购买
      buyHandle (id) {
        this.buyVisible = true
        this.$nextTick(() => {
          this.$refs.buy.init(id)
        })
      },

原生拦截器

动态代理,

拦截所有,登陆放行,登录成功,有放入到session中,将用户id作为key,

拦截器 得到请求地址,session得到用户,用户得到权限,

地址与权限,预处理返回true,否则为false

后台管理的鉴定权限、认证

SysMenuEntity 侧边栏url

SysRoleEntity 系统角色

SysRoleMenuEntity 系统角色与导航栏的关系

SysUserRoleEntity 用户与角色之间的关系

首先,每个用户的账号对应了他是什么角色,然后每个角色对应了他有哪些菜单的权限。菜单的权限有目录、菜单、授权标识三种级别 ,如果有权限,侧边栏就有响应的内容与操作。

核心模块是由 Shiro 来做认证和授权的

验证码

进入登录页面后,调用created函数,从而调用的里面getCaptcha(),方法就是请求获取验证码的方法。

请求这个验证码之前调用getUUID()方法获得uuid,这个uuid参数赋值给了this.dataForm.uuid,此时vue对象的data属性上的dataForm登录表单对象就有了这个uuid值。并且在其请求验证码的时候,携带了uuid参数,

Java端随机生成code,这个code的值就是真正的验证码,再将code 以及uuid以及创建时间封装成验证码的实体保存在数据库中,然后通过Java Swing将code处理为图片,当做完这些操作后,将生成的图片响应给前端。

登录认证

前端输入用户名,密码,以及uuid,验证码传入后端进行判断

用户名、密码正确性

uuid,验证码的正确性

验证码的有效期

全部正确,根据uid创建一个token实体类

token 实体类没有就添加数据库,有就更新token

然后将token返回给前端保存在header

用户拿这个 token 去访问网站

请求被 OAuth2Filter 拦截,如果没有在 Shiro 登录,访问禁止,调用 OAuth2Filter.onAccessDenied(ServletRequest request, ServletResponse response)方法,

并且OAuth2Filter.onAccessDenied方法中执行登录,此时调用 OAuth2Realm 的doGetAuthenticationInfo(AuthenticationToken token)方法,查询数据库 token 对应的用户,

因为token有可能被篡改,所以要去查询这个token对应的数据用户,

完成登录,后面访问不会再被拦截

授权

Shiro 完成授权,自定义 OAuth2Realm 类

登录成功后, Shiro 保存用户的权限信息

用户在试图请求一个带@RequiresPermissions的方法,会被AuthorizationAttributeSourceAdvisor拦截

AuthorizationAttributeSourceAdvisor添加了PermissionAnnotationMethodInterceptor拦截器,判断用户是否具备权限

具备权限则放行,不具备则抛出AuthorizationException

当抛出AuthorizationException时,处理该异常,给用户友好提示

app模块的认证
  1. app模块登录,创建 JWT token
  2. 请求需要登录权限的方法,进入AuthorizationInterceptor查询是否登录
  3. 确认已登录,如果方法需要登录信息,从 request 域中获取

Spring Cloud Gateway + Jwt + Oauth2 实现网关的鉴权操作

后台管理只需要一个总的请求路径,这个请求路径是交给网关的,由网关来请求转发

权限控制四部分

权限设计

首先,每个用户的账号对应了他是什么角色,然后每个角色对应了他有哪些菜单的权限。菜单的权限有目录、菜单、授权标识三种级别。该项目对应的前端项目 renren-fast-vue 中,会一次性从服务器获取用户对应的所有角色和菜单权限,然后通过目录和菜单级别的权限显示左侧导航栏,以及通过授权标识权限显示按钮。

认证

核心模块是由 Shiro 来做认证和授权的 , 系统使用 OAuth2Filter 这个过滤器对核心模块资源进行了过滤

登录成功,用用户id查询token实体类

有就更新token 的时间与内容

没有就创建一个,加入数据库中

授权

一、商品模块

发布商品是会调用优惠券模块

满几件打几折

满几千减少几百

以及优惠券叠加功能

发布商品是会调用会员模块

不同会员拥不同商品价格

发布商品是会调用搜索模块

在商城搜索框可以通过某些检索条件搜索该商品

发布商品是会调用存储模块

1.1后台管理平台发布商品

//1.保存spu基本信息 pms_spu_info
//2.保存spu的描述图片 pms_spu_info_desc
//3.保存spu的图片集 pms_spu_images
//4.保存spu的规格参数 pms_product_attr_value
//5.保存spu的积分信息 gmall_sms >> sms_spu_bounds
//6.保存当前spu对应的所有sku信息
//6.1保存sku的基本信息 pms_sku_info
//6.2保存sku的图片信息 pms_sku_images
//6.3保存sku的销售属性信息 pms_sku_sale_attr_value
//6.4保存sku的优惠、满减等信息 gmall_sms
//sku的打折信息 kcmall_sms >> sms_sku_ladder
//sku的满减信息 kcmall_sms >> sms_sku_full_reduction
//会员价格信息 kcmall_sms >> sms_member_price

1.2商品上架

1.商品上架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKF0myTK-1636682641563)(C:\Users\wyh\Desktop\商品上架.png)]

从数据库保存到ES中展示在首页。
上架的商品才可以在网站展示。
上架的商品需要可以被检索

分析:商品上架在 es 中是存 sku 还是 spu?

1)、检索的时候输入名字,是需要按照 sku 的 title 进行全文检索的
2)、检索使用商品规格,规格是 spu 的公共属性,每个 spu 是一样的
3)、按照分类 id 进去的都是直接列出 spu 的,还可以切换。
4)、我们如果将 sku 的全量信息保存到 es 中(包括 spu 属性)就太多量字段了。
5)、我们如果将 spu 以及他包含的 sku 信息保存到 es 中,也可以方便检索。但是 sku 属于
spu 的级联对象,在 es 中需要 nested 模型,这种性能差点。
6)、但是存储与检索我们必须性能折中。
7)、如果我们分拆存储,spu 和 attr 一个索引,sku 单独一个索引可能涉及的问题。
检索商品的名字,如“手机”,对应的 spu 有很多,我们要分析出这些 spu 的所有关联属性,
再做一次查询,就必须将所有 spu_id 都发出去。假设有 1 万个数据,数据传输一次就
10000*4=4MB;并发情况下假设 1000 检索请求,那就是 4GB 的数据,,传输阻塞时间会很
长,业务更加无法继续。

商品上架步骤:

上架是将后台的商品放在 es 中可以提供检索和查询功能

1)、hasStock:代表是否有库存。默认上架的商品都有库存。如果库存无货的时候才需要

更新一下 es

2)、库存补上以后,也需要重新更新一下 es

3)、hotScore 是热度值,我们只模拟使用点击率更新热度。点击率增加到一定程度才更新

热度值。

4)、下架就是从 es 中移除检索项,以及修改 mysql 状态

商品上架步骤:

1)、先在 es 中按照之前的 mapping 信息,建立 product 索引。

2)、点击上架,查询出所有 sku 的信息,保存到 es 中

3)、es 保存成功返回,更新数据库的上架状态信息。

数据的一致性

1)、商品无库存的时候需要更新 es 的库存信息
2)、商品有库存也要更新 es

点击上架,商品就从上架新建状态变为已上架

1.3检索已上架的产品

es以及nginx安装完毕

es低版本安装ik开源分词器,高版内置,ik分词器要jdk 环境

查看可以安装得jdk版本

yum -y list java*

选择安装合适的

yum install -y java-1.8.0-openjdk-devel.x86_64  

测试安装成功

Java -version

javadoc

javac

jdk自动安装位置/usr/lib/jvm

[root@WangYiHui ~]# cd /usr/lib/jvm
[root@WangYiHui jvm]# ls
java
java-1.8.0
java-1.8.0-openjdk
java-1.8.0-openjdk-1.8.0.302.b08-0.el8_4.x86_64
java-openjdk
jre
jre-1.8.0
jre-1.8.0-openjdk
jre-1.8.0-openjdk-1.8.0.302.b08-0.el8_4.x86_64
jre-openjdk
[root@WangYiHui jvm]# 

配置环境变量vim /etc/profile文件 最后添加

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

kibana 是es的可视化界面

下载ik分词器要和你的es版本一致

在容器外部插件文件下新建ik文件改变权限

mkdir ik
cd ./ik
chmod -R 777 ik

下载ik分词器,解压安装包、删除安装包、重启

wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.8.0/elasticsearch-analysis-ik-7.8.0.zip
unzip elasticsearch-analysis-ik-7.8.0.zip
rm -rf elasticsearch-analysis-ik-7.8.0.zip
docker restart 容器id或名字

原来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPatJJa3-1636682641565)(C:\Users\wyh\Desktop\截图\会员服务\ik01.PNG)]

使用ik分词器,请求体body内容

{
"analyzer":"ik_max_word",
"text":"我是中国人"
}

响应

  "tokens": [{
    "token": "我",
    "start_offset": 0,
    "end_offset": 1,
    "type": "CN_CHAR",
    "position": 0
  }, {
    "token": "是",
    "start_offset": 1,
    "end_offset": 2,
    "type": "CN_CHAR",
    "position": 1
  }, {
    "token": "中国人",
    "start_offset": 2,
    "end_offset": 5,
    "type": "CN_WORD",
    "position": 2
  }, {
    "token": "中国",
    "start_offset": 2,
    "end_offset": 4,
    "type": "CN_WORD",
    "position": 3
  }, {
    "token": "国人",
    "start_offset": 3,
    "end_offset": 5,
    "type": "CN_WORD",
    "position": 4
  }]
}

线上部署前置知识:

本机->域名->服务器->nginx反向代理和负载均衡->网关的负载均衡->真正的网址

二、商城的检索服务

后台页面上架成功之后,商城检索都是从es里面查询数据

建立一个索引

put http://47.108.170.87:9200/product

{
  "mappings": {
    "properties": {
      "skuId":{
        "type": "long"
      },
      "spuId":{
        "type": "keyword"
      },
      "skuTitle":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice":{
        "type": "keyword"
      },
      "skuImg":{
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount":{
        "type": "long"
      },
      "hasStock":{
        "type": "boolean"
      },
      "hotScore":{
        "type": "long"
      },
      "brandId":{
        "type": "long"
      },
      "catalogId":{
        "type": "long"
      },
      "brandName":{
        "type":"keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg":{
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName":{
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs":{
        "type": "nested",
        "properties": {
          "attrId":{
            "type":"long"
          },
          "attrName":{
            "type":"keyword",
            "index":false,
            "doc_values": false
          },
          "attrValue":{
            "type":"keyword"
          }
        }
      }
    }
  }
}

post http://47.108.170.87:9200/_reindex 重建索引

{
  "source": {
    "index": "product"
  }
  , "dest": {
    "index": "kkmall_product"
  }
}

上面报错,删除索引,新建立索引

put http://47.108.170.87:9200/kkmall_product

body

{
  "mappings": {
    "properties": {
      "skuId":{
        "type": "long"
      },
      "spuId":{
        "type": "keyword"
      },
      "skuTitle":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice":{
        "type": "keyword"
      },
      "skuImg":{
        "type": "keyword"
      },
      "saleCount":{
        "type": "long"
      },
      "hasStock":{
        "type": "boolean"
      },
      "hotScore":{
        "type": "long"
      },
      "brandId":{
        "type": "long"
      },
      "catalogId":{
        "type": "long"
      },
      "brandName":{
        "type":"keyword"
      },
      "brandImg":{
        "type": "keyword"
      },
      "catalogName":{
        "type": "keyword"
      },
      "attrs":{
        "type": "nested",
        "properties": {
          "attrId":{
            "type":"long"
          },
          "attrName":{
            "type":"keyword"
          },
          "attrValue":{
            "type":"keyword"
          }
        }
      }
    }
  }
}

三、存储服务

采购单分为

  • 商品库存紧缺,自动生成采购单
  • 人工生成采购单,然后合并

新增采购单其实是选择采购人员、新增采购需求,选择该新增,合并整单,就是该采购需求分给某个采购人员

这时候采购单和采购需求都是已分配的状态

模拟员工完成采购单

分析

  • 因为采购单中包含多个采购需求,因此需要对每一项采购结果判断处理

  • 采购完成调用远程的服务procduct完成sku数量的更新

postman 请求 请求体采购单的id 发送请求成功之后,购物单的状态是已领取状态,采购需求正在采购

http://localhost:88/api/ware/purchase/received

[5,6]   #采购单id可以是多个或一个

上传采购结果,成功采购需求以及采购单的状态是已完成,并且通过skuid调用商品服务补全采购信息,库存数据增加

反之采购需求采购失败,采购单有异常

post请求http://localhost:88/api/ware/purchase/done

{
     "id":"24",                                      #采购单id
     "items":[
            {"itemId":14,"status":3,"reason":""},     #采购需求id  状态3是完成
            
            {"itemId":15,"status":4,"reason":"缺货"}   
     ]
 }

四、Nginx

商城业务 — Nginx搭建域名访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AlUwmKxQ-1636682641566)(C:\Users\wyh\Desktop\截图\nginx11.png)]

本机host文件 C:\Windows\System32\drivers\etc\hosts

虚拟机ip 域名

niginx

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toBdJfx3-1636682641567)(C:\Users\wyh\Desktop\截图\n13.png)]

#config文件
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;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

网关模块

 - id: gulimall_host
        uri: lb://gulimall-product                    #网关中nacous的转发也就是找到springboot
        predicates:                                   #项目启动之后名字是gulimall-product   
        - Host=**.gulimall.com,gulimall.com           #浏览器上的网页访问路径

由于我的nginx是安装在阿里云服务器上,没有购买域名所以就自己用hosts文件

127.0.0.1       localhost
127.0.0.1       order.kkmall.com seckill.kkmall.com kkmall.com search.kkmall.com
127.0.0.1       item.kkmall.com auth.kkmall.com member.kkmall.com cart.kkmall.com

127.0.0.1       https://kkmall.com

4.1反向代理

正向代理与反向代理。

  • 正向代理就是我们发出请求给服务器,服务器将我们的请求转发给互联网。

  • 反向代理就是为了更好的保护后端集群不被攻击,从而找一台服务器暴露在外,用户想要访问内部信息,需要通过公网ip访问代理服务器从而转发给相应服务。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sa6jVqlx-1636682641569)(C:\Users\wyh\Desktop\截图\动静分离.png)]

外部文件与docker nginx内部关联,将静态文件拷贝到外部关联文件

修改配置文件

[root@localhost conf.d]# cat gulimall.conf
server {
    listen       80;
    server_name  gulimall.com;

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

        # 如果路径包含static,则直接访问nginx/html的静态路径资源,其余走proxy_pass代理
    location /static/ {
       root /usr/share/nginx/html;
    }

    location / {
       #proxy_pass http://192.168.10.1:10000;
        proxy_set_header Host $host;
        proxy_pass http://gulimall; #use nginx.conf upstream load balancing
    }

    #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;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

重启容器

本机nginx

hosts文件

127.0.0.1 kkmall.com 

浏览可以通过域名加端口访问,这个并不是我们需要的

nginx反向代理

 server {
        listen       80;             
        server_name  kkmall.com;                      //通过hosts文件找到
        location / {
             proxy_pass http://localhost:10001/;      //要代理的IP地址
        }
        }

nginx转网关

修改本的的hosts文件

127.0.0.1       localhost
127.0.0.1       order.kkmall.com seckill.kkmall.com kkmall.com search.kkmall.com
127.0.0.1       item.kkmall.com auth.kkmall.com member.kkmall.com cart.kkmall.com

127.0.0.1       https://kkmall.com

修改nginx配置文件

 upstream kkmall{
       server 127.0.0.1:88;    //外面添加网关ip
    }
    server{
        listen      80;
        server_name kkmall.com *.kkmall.com;

        location /{              
            proxy_set_header Host $host;      //补全丢失的头信息
            proxy_pass http://kkmall;
        }
    } 

nginx命令:

cmd进入nginx安装包

启动:nginx.exe 或者 start nginx

停止:nginx.exe -s stop

重新加载配置文件:nginx.exe - s reload

重启: nginx.exe -s reopen

4.2动静分离

项目的静态资源请求和动态请求都是经过Nginx反向代理后到我们后端的Tomcat服务器,然后再返回数据,如果请求量非常的大,则后端服务器Tomcat的压力也就非常大,经过测试发现,大多数请求都是静态资源的,所以,如果降静态资源放在Nginx下,不用再到Tomcat处理,那就可以大大减轻Tomcat的压力。

五、分布式缓存与分布式锁

1.缓存使用

为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落
盘工作。哪些数据适合放入缓存?

即时性、数据一致性要求不高的
访问量大且更新频率不高的数据(读多,写少)

举例:电商类应用,商品分类,商品列表等适合缓存并加一个失效时间(根据数据更新频率
来定),后台如果发布一个商品,买家需要 5 分钟才能看到新的商品一般还是可以接受的
缓存流程

注意: 在开发中,凡是放入缓存中的数据我们都应该指定过期时间,使其可以在系统即使没
有主动更新数据也能自动触发数据加载进缓存的流程。避免业务崩溃导致的数据永久不一致
问题。

一、缓存穿透

谷粒商城业务逻辑(一)_第1张图片

缓存穿透

指查询一个一定不存在的数据,由于缓存是不命中,将去查数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

风险

利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃。

解决

null结果缓存,并加入短暂的过期时间。

二、缓存雪崩

谷粒商城业务逻辑(一)_第2张图片

缓存雪崩

缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一个时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决

原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体的失效事件。

三、缓存击穿

谷粒商城业务逻辑(一)_第3张图片

缓存击穿
  • 对于一些设置了过期时间的key,如果这些key可能会在某一时间点被高并发的访问,是一种非常“热点“的数据。

  • 如果这个key在大量请求同时进来前刚好失效,那么所有对这个key的查询都落在db,我们成为缓存击穿。

    解决

    加锁来处理。
    大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db。

小结

穿透:查询一个永不存在的数据
雪崩:缓存key同一时间大面积失效
击穿:某一个单热点被高频访问,在前一点突然失效。

结果方案:
1、空结果缓存:解决缓存穿透
2、设置过期时间(加随机值),解决缓存雪崩
3、加锁:解决缓存击穿

四、 持久化(RDB、AOF)

六、认证服务

短信验证码

逻辑:

  • 认证服务中短信controller接收到电话号请求后,认证服务生成一个验证码

  • 认证服务发送电话与验证码,调用第三方服务

  • 第三方服务调用短信服务商提供的接口,让短信服务商给手机发送生成好的验证码信息

  • 手机接收到验证码后,封装到账号信息中,发送给注册controller

短信验证码时间问题,当用户接收到验证码同时,把验证码放入到redis中,设置时间期限,用户接受到验证码是与redis中比较。

1)发送验证码:接收 手机号 -> 查看redis中有没有

->不存在 -> 随机生成验证码 -> 存入 redis (60s)-> 调发短信接口。
-> 存在,不能重复发送
2)验证:Valied校验 -> 手机号 去 redis 查数据

-> 如果 输入 和 redis 相同,验证通过。-> 删除 redis,调用 远程 注册接口。
-> 不同则验证失败。

phone,用户名存在,抛出异常。密码使用 MD5 消息摘要算法 加密。

单点登陆

OAuth2.0

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

认证流程:
a:用户申请 微博登录认证,跳转到 微博登录页
b:登录之后,获得一个 Code,根据 Code 获取 Acess_Token
c:根据 Acess_Token ,获取微博 开放的内容。
d:根据 uid 查询,如果没有,则第一次登录,注册用户。如果有,则 更新字段。
e:无论 登录成功还是 注册成功,都返回 当前 用户信息。
注意:
a:使用 Code 换取 Acess_token ,只能换取一次。
b:每个用户的 Acess_token,即使多次获取,一段时间内不会变化。
微博开发平台

微连接

网站接入

创建新应用

生成 client_id:是你创建网站应用时的app key,
配置 redirect_uri:是用户使用微博登录后重定向到哪里去。

登陆页面的微博登陆标签跳转 引导用户至授权页

<a href="https://api.weibo.com/oauth2/authorize?client_id=1595881874&response_type=code&redirect_uri=http://auth.kkmall.com/oauth2.0/weibo/success">
<img style="width: 50px; height: 18px" src="/static/login/JD_img/weibo.png"/>a>

前台用户用微博登录后我们会拿到用户的code,后台在请求到微博里面用code换取token,这样才能用token访问到用户基本信息SocialUser;然后拿着SocialUser到OpenFeign远程调用gulimall-member的oauth2Login()方法,在该方法中会先用SocialUser的uid查询数据库来判断用户是否是第一次用微博登录,如果是第一次的话我们就得给该用户注册(拿着token到微博里面查询该用户的基本信息,然后insert到咱们的数据库里面);如果该用户之前已经用微博登陆过,那就到数据库中更新一下token。用户每登陆一次访问微博的token就会变一次,所以当用户下次用微博登陆时我们需要到数据库更新一下;

如果一切顺利,gulimall-member就会带着MemberEntity(封装着用户的所有信息)返回到gulimall-auth-server,然后gulimall-auth-server会把MemberEntity设置到RedirectAttributes然后重定向到http://gulimall.com;如果不顺利就把error信息返回到gulimall-auth-server,然后gulimall-auth-server会把error信息封装到RedirectAttributes然后重定向到http://auth.gulimall.com/login.html

七、分布式Session

1、SpringSession—session不共享、不跨域问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pXIREgI2-1636682641573)(C:\Users\wyh\Desktop\截图\登陆\session.png)]

多台服务器都有会员服务,你在A服务器上把用户信息保存到内存上了,下次如果落在B服务器上,即使浏览器带着cookie来了,由于B服务器内存肯定没有存储用户信息,这也是问题。

session共享问题的解决方案

session复制
用户登录后,A服务器得到session后,把session也复制到别的机器上,显然这种处理很不好

客户端存储
把session存储到浏览器上,肯定相当不安全

hash一致性
根据用户,到指定的机器上登录。但是远程调用还是不好解决

redis统一存储
最终的选择方案,把session放到redis中,这样每个微服务都可以获取到session

浏览器会在auth.gulimall.com里面登录成功,auth.gulimall.com会将登陆成功的用户的从数据库查到的用户相关信息存到session里面,而且存session时不是存到自己的内存里面而是存到redis里面,然后auth.gulimall.com给浏览器发cookie,而且发的cookie的作用域不能仅仅是auth.gulimall.com而是要放大服务到.gulimall.com,此时浏览器访问其它任何服务都会带上这个cookie。
如果你把redis里面的session清空,那就是把登陆过的用户信息清空,虽然前台的浏览器访问后台时携带了cookie信息,但是到redis里面查不到用户信息,所以你就得重新登陆。而且我们设置了redis里面的session默认30分钟过期,也就是30分钟后redis里面的用户信息就没有了
修改微博端的代码

①修改sprinsession的存储类型是redis(这很重要·,以后存到session中就是存到redis中)

spring:
  session:
    store-type: redis

②增加一个配置类,由于默认使用jdk进行序列化,通过导入RedisSerializer修改为json序列化,并且通过修改CookieSerializer扩大session的作用域至**.gulimall.com

@Configuration
public class GulimallSessionConfig {
 
    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");
        return cookieSerializer;
    }
 
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
        return new GenericJackson2JsonRedisSerializer();
    }
}

controller

以前:

把MemberEntity设置到RedirectAttributes然后重定向到http://gulimall.com

现在:

把MemberEntity设置到SpringSession中然后重定向到http://gulimall.com

2、单点登录SSO

springsession只能把auth.gulimall.com作用域放大到gulimall.com,解决了同域名的共享session问题,但要是访问同样是尚硅谷的atguigu.com怎么办呢?这种不同的域名也想共享session该怎么做呢?
你在新浪微博里面注册登录了,同时就要保证在新浪体育、新浪新闻里面全都可以拿到session数据

简单来说

在不同域名服务器加入一个认证服务器,通过认证服务器转发到目标服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zIShr8rI-1636682641574)(C:\Users\wyh\Desktop\截图\登陆\sso.png)]

先说明一下这个路径的含义:http://ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees的含义就是让你访问http://ssoserver.com:8080/login.html登陆页面,而 redirect_url=http:I/client1.com:8081/employees的含义是当你完成登陆后会重定向到http:I/client1.com:8081/employees的位置

第1-11步的解析:只有登陆了才能查看员工信息。一开始浏览器访问client1.com的员工信息http:I/client1.com:8081/employees,client1会根据这个url有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees,ssoserver.com会判断是否登陆过,没有登陆过就展示这个登陆页面,用户会输入账号密码进行登录,提交登陆请求http:/ssoserver.com:8080/doLogin?usermame,password,redirect_url给ssoserver.com,那么ssoserver.com会保存用户状态到redis,同时ssoserver.com会命令重定向到http: /lclient1.com:8081/employees?token=dadadadsdeuieu(浏览器访问路径),同时ssoserver.com会命令浏览器保存sso_token=dadadadsdeuieu这样式的cookie。浏览器这次就可以访问员工信息了,他的访问路径是刚刚提到的http://lclient1.com:8081/employees?token=dadadadsdeuieu比一开始访问员工信息的http:I/client1.com:8081/employees多了token=dadadadsdeuieu,这就回到第2步了,client1会根据有没有token参数判断是否登录,这次client1会觉得它登陆过了就可以访问员工信息了。

第12-19步解析:这次浏览器要访问客户端2的boss信息http:I/client2.com:8081/boss,client2会根据有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client2.com:8081/boss,ssoserver.com会判断是否登陆过,由于浏览器有sso_token=dadadadsdeuieu这样式的cookie,而且从redis能查到,说明它之前在client1或者client2登陆过,ssoserver.com会命令重定向到http:/lclient2.com:8082/boss?token=dadadadsdeuieu,所以浏览器就会访问http://lclient2.com:8082/boss?token=dadadadsdeuieu,这就回到了第2步,client2会根据有没有token参数判断是否登录,登陆过就响应页面。

所以说,以后浏览器无论访问client1还是client2,由于浏览器中保存了cookie,所以ssoserver.com就会判定它登陆过,所以以后都不用登陆。

八、购物车

在购物车的所有Controller执行之前,我们先执行一个拦截器。在拦截器里判断用户是否登录,从session中获取不到用户信息就说明他没有登录,没有登录的话就从浏览器中获取一下user-key,如果浏览器中没有user-key那就说明用户是第一次没有登录的状态下进入京东,我们就得创建一个cookie名字叫做user-key,而且设置cookie的作用域、过期时间,假如明天他来了,我们能从浏览器中获取到该用户的user-key。

一个用户进来我们执行的 “ 拦截器—Controller—Service—Dao ” 这一套流程让同一个线程执行,这就使用了ThreadLocal技术,ThreadLocal是同一个线程共享数据,这个线程里面的数据会共享,使用过程就是:

ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();//创建一个threadLocal
threadLocal.set(userInfoTo);//把要共享的数据设置进去
....
UserInfoTo userInfoTo = threadLocal.get();//后期就可以获取到这个共享的数据
details/119544560

RabbitMQ

只需要安装rabbit mq时配置好端口

docker

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

服务端端口 与web后台端端口

创建交换机(Exchange)、队列(Queue)、以及绑定关系(Binding)

指定交换机与指定目的地进行绑定,目的地一般都是队列,根本不需要在web界面创建,全都用Java搞定

九、支付宝支付

支付宝开放平台https://developers.alipay.com/platform/appManage.htm

蚂蚁金服官方文档https://opendocs.alipay.com/open/291/106130

登陆支付宝开放平台,选择自研开发服务、提交个人信息

自研 网页 支付接入提交创建信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x0tdJNVj-1636682641576)(C:\Users\wyh\Desktop\截图\支付宝\kcmall.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCf6s3OB-1636682641577)(C:\Users\wyh\Desktop\支付宝.PNG)]

十、秒杀服务

后台:新增秒杀场次,场次关联商品

瞬间流量大,主要解决瞬间高并发,独立模块,独立部署,页面上架之前,商品以及缓存加入到内存中,减轻数据库的压力

定时任务:

前一天晚上吧第二天将要上架的商品放入到redis中

cron表达式:https://cron.qqe2.com/

Spring中 只允许6位 不包含年,
秒分时日月周
[* * * ? * 1] : 每周一每秒执行一次 
[* /5 * * ? * 1] : 每周一 每5秒执行一次

定时任务不应该阻塞,默认是阻塞的

@Async@Scheduled(cron = "*/5 * * ? * 1")
public void hello() {    
log.info("i love you...");    
try {        Thread.sleep(3000);    
} catch (InterruptedException e) {
}}

阻塞、8秒

1、业务运行以异步方式、自己提交到线程池

2、定时任务线程池,默认1,修改5 有bug

3、springboot开启异步任务

解决:异步+定时任务,解决定时任务不阻塞

重点要解决:

服务器单一职责

独立部署一台蹦全部不崩

秒杀链接加密

秒杀活动时间到了发配随机码

用户有随机码能参与秒杀活动

库存预热、快速扣减

秒杀商品的库存放入到redis中,信号量控制,原子减量,来一个减少一个

动静分离

静态资源放入到nginx中,动态请求才会通过nginx进行路由转发

恶意请求拦截

没有登陆不能抢购

流量错峰&削峰

限流&熔断&降级

前端限流,后台

队列削峰

秒杀过程:

// 1.校验随机码跟商品id是否匹配

// 2.说明数据合法

// 3.验证这个人是否已经购买过了

// 让数据自动过期

// 占位成功 说明从来没买过

// 快速下单 发送MQ

库存超卖

在SQL加上判断库存防止库存为负数

更新库存操作时库存总量大于1,库存才能减少1

数据库加上唯一索引防止用户重复购买

redis预库存减少数据库操作,内存标记

利用redis的单线程预减库存。比如商品有100件。那么我在Redis存储一个K,V。例如 ,每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象

悲观锁 加同步代码块 效率低

乐观锁 Version版本 效率高

你可能感兴趣的:(谷粒商城,spring,maven)