项目重要技术点介绍

正文共: 8929字 10图 预计阅读时间: 23分钟

项目仓库

https://github.com/EthanYan6/E-commerce-sites.git

结合代码查看笔记,效果更佳。笔记只是记录重点或者难点。

项目重要技术点介绍

项目简介

我们的项目是一个B2C模式的电商网站,采用的是前后端分离开发模式。前端主要使用vue.js开发,后端则主要使用DRF框架。

1. celery

celery是一个专注于实时处理和任务调度的分布式任务队列。本质上来说就是通过提前创建的进程调用函数来实现异步的任务。它有三个比较重要的组成部分:任务发出者,中间人和任务执行者。任务发出者通过 .delay()来发出指令,发出的指令好像排队一样按先后顺序在中间人的地方进行排列,而任务执行者则是实时的检测中间人中的任务指令,一有任务,立马调用封装的函数进行处理。

它有很多的优点,比如任务执行者可以单独的创建在其他的电脑上,这样的分布式也可以一定程度上缓解服务器的压力;异步执行任务,减少等待时间,提高效率等等。

在我们的项目中一共有三个地方用到了celery异步任务。分别是发送短信验证码、发送验证邮件以及生成详情页面。下面拿发送短信验证码的例子来简单的说一下celery。

传统的发送短信的方法是客户端向服务器请求短信验证码,服务器再向云通讯发送请求,让其帮我们发送短信,但是有一个很大的问题,就是每一步请求都是需要等待响应的,如果网络较差,服务器迟迟得不到响应,那么客户端也得不到响应,最直观的现象就是用户点击了发送短信验证码后,没有任何反应。为了解决这一问题,我们使用了celery异步发送短信,减少了等待的时间。使用之后过程就变成了用户点击发送短信验证码按钮,服务器向中间人的任务队列中添加一条任务,立马向客户端返回响应,客户端开始倒计时。celery的任务执行者调用发送短信的任务函数,使用云通讯给指定的手机号发送短信验证码。

下面再描述一下用户的邮箱设置:用户输入邮箱后点击进行设置,浏览器就会请求后端的接口进行业务处理。服务器接收参数并进行校验,然后向任务队列中添加一条发送验证激活邮件的任务消息,与此同时,向客户端返回响应。celery的任务执行者从任务队列中检测到任务后,调用发送邮件任务的函数进行邮件的发送。

2. docker

docker就好像一个容器,它是完全封闭的,比我们的虚拟机还好用。它可以将我们项目完整的封装到一起,连同环境。这样项目在开发阶段是什么样,测试的时候就是什么样,不会有任何改变,使用也很简单,直接将容器打包给其他人,其他人直接按住docker就可以使用了。

我们的项目中利用docker搭建了FDFS文件存储系统。

我们在项目中常用的就是启动文件存储系统时的命令

docker container start tracker
docker container start storage
docker container start elasticsearch
# 查看运行的服务器
docker container ls

容器操作命令:

命令

说明

docker run -it --name=<容器名> <镜像名> <容器启动之后执行命令>

创建一个交互式容器

docker run -itd --name=<容器名> <镜像名>

创建一个守护式容器

docker exec -it <容器名|容器id> <进行容器执行命令>

进入已经运行容器

docker container ls

查看本地正在运行的容器

docker container ls --all

查看本地所有的容器

docker container stop|kill <容器名|容器id>

停止正在运行的容器

docker container start <容器名|容器id>

启动已经停止的容器

docker container rm <容器名|容器id>

删除已有容器

还有一些docker命令:

# 列出镜像
docker image ls
# 拉取镜像
docker image pull
# 删除镜像
docker image rm
# 创建容器
docker run [option] 镜像名 [向启动容器中传入的命令]

3. jwt

1.JWT使用的过程中服务器端保存了什么,客户端保存了什么?

答:服务器端保存的是SECRET私钥;客户端保存的是服务器加密后的jwt token。

2.JWT的校验过程?

答:客户端发起请求的时候,传递给服务器一个jwt token,jwt token分为三部分:头部(header)、载荷(payload)和签证(signature)。服务器在收到这个token的时候将前两部分header和payload使用header中的加密算法HMACSHA256进行加盐SECRET组合加密,然后将生成的签名信息与jwt token中的第三部分signature进行对比,如果一致,说明token合法,否则就是伪造的。因为生成签名信息的SECRET只有服务器知道,所以相对来说很安全。

3.JWT中是如何加密的,安全吗?

答:JWT中header、payload都是由base64加密的,而base64是对称加密解密的过程,不安全,详细内容介绍见5。signature部分则是将上面加密过的头部和载荷利用头部中声明的加密方式(HMACSHA256)进行加盐 secret组合加密,这样一来三部分组合而成的JWT就相对安全了。因为万事没有绝对,只能是相对的安全。

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

4.token中可以存放敏感的信息吗?

答:不可以,前面已经提到了,token的header和payload是经过base64加密的,而base64是对称加密,并不安全,因此不建议存放敏感信息。

5.为什么使用jwt认证机制?

答:在美多商城项目中,jwt token认证机制是对session认证机制的替代,基于之前的session认证机制,存在很多问题。比如,session信息存储在服务器端,如果登录用户过多,会占用过多服务器的空间;session依赖于cookie,session信息的标识保存在cookie中,如果cookie被截获,可能会造成 CSRF(跨站请求伪造攻击);session认证不适合的分布式站点的应用场景。

6.jwt认证机制?

答:对于jwt token的认证机制,在用户登录时,服务器会签发( 生成)一个jwt token字符串。然后服务器在响应时将jwt token数据返回给客户端,客户端需保存jwt token数据。之后客户端在请求服务器时,如果需要进行用户的认证,需要将jwt token数据通过请求头传递给服务器,服务器会核验jwt token数据的有效性。

4. FDFS

FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制, 充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。它包含tracker server调度服务器和storage server存储服务器。顾名思义,tracker就是调度Storage来存储文件。

在美多商城项目中,使用Docker搭建FDFS文件存储系统,同时为了Admin界面能够直接上传文件到FDFS文件存储系统中(因为默认是存储在服务器上的),自定义了Django框架文件存储类。需要注意的是,自定义文件存储类之后,需要指定DEFAULTFILESTORAGE配置项。

5. ES

ES是java语言实现的一个开源的搜索引擎,很火,一般我们首选ES搜索。它的原理就是先建立索引结构数据,类似于咱们新华字典的前面索引表。在通过搜索引擎查询的时候,和咱们查字典一样,先通过拆分关键字的方式查一下这个数据在哪,然后直接就找到了。

我们使用haystack全文检索框架,它是python中的全文搜索框架,支持多种搜索引擎,能帮助开发者利用搜索引擎建立数据表的索引数据。能帮助开发者利用搜索引擎进行关键词搜索,获取对应的索引数据。还能利用索引数据查找到对应数据表的数据。也就是它什么都有了,你直接使用就好了。

在美多商城项目中,使用Docker搭建es搜索引擎服务器并使用haystack对接es搜索引擎来实现商品的搜索功能。

6. redis

redis数据库是非关系型数据库,将数据存储在缓存中,读取速度快是其最大的优点。在Django中需要引入第三方扩展django-redis来使用。redis适用于存储使用频繁的数据,这样减少访问数据库的次数,提高了运行效率。它又五种数据类型列表、字符串、哈希hash、无序集合set和有序集合zset。详细的操作流程点击链接『redis操作命令总结

在购物车记录存储的时后用到了redis,因为如果存储在mysql中,用户频繁的操作购物车的记录(删除或这添加),就需要频繁操作mysql数据库。在redis中存储登录用户的购物车记录。读写效率要快很多。每个登录用户的购物车数据采用两条数据保存。其hash用于保存用户购物车记录中添加的商品id和对应数量;set用于保存用户购物车记录勾选状态(保存勾选商品id)。

浏览记录的保存的时候也用到了redis。采用的是列表数据类型。因为字符串和hash存储的时候需要额外的字符串操作,而列表直接可以存储,然后直接取值。zset需要额外的权重值来保证有序,而列表不需要。

7. nginx

Nginx相当于一个中转站,它的并发处理能力十分强劲,可以将客户端的请求转发给业务服务器,也可以将业务服务器的响应返回给客户端。我们在Nginx中设置了两台服务器,一台是静态文件服务器,一台是提供后端API服务器入口的服务器

1.静态文件服务器用来向客户端提供静态文件。静态服务器的域名使用的是 www.meiduo.site,端口用的是80。静态文件服务器在处理xadmin站点和富文本CKEditor的请求时,会报错。因为这两个接口是由业务服务器处理的,我们解决的办法就是修改静态文件服务器的配置文件,让其转发给业务服务器。

2.在Nginx中的另一台服务器,是后端API服务器的入口,向业务服务器转发请求,实现负载均衡。域名使用的是 api.meiduo.site,端口用的是8000。这台服务器收到请求后向业务服务器进行转发,默认的形式是轮流转发。

8. uwsgi

uwsgi就是一个web服务器,我们利用uwsgi+Django共同提供后端API的服务器,也叫做业务服务器或者应用服务器。我们用多个业务服务器来实现负载均衡。在项目中,我们的业务服务器启动两台。一台使用地址为 127.0.0.1:8001,另一台使用的地址为 127.0.0.1:8002

9.页面静态化

页面静态化是网站优化的一种方式。就是将页面用到的数据从数据库中查询出来,然后生成一个静态页面,比如生成静态首页,用户来访问时,直接返回静态页面。

我们需要提前准备一个模板文件,在模板文件把所用到的数据及数据展示代码都提前写好(模板语言)。然后定义一个函数,通过执行这个函数来生成静态页面。静态页面的生成过程就是先从数据库中查询出所需的数据,然后使用对应模板文件,给模板文件传递数据,进行模板渲染(将模板文件中变量进行替换,产生替换之后页面内容)最后将替换之后的页面内容保存成一个静态文件。

也许你会想,数据更新了怎么办?我们采用了定时任务和修改时更新两种方法解决这个问题,使静态页面数据和数据库表数据保持同步。对于那些更新频繁的界面就利用定时任务,让系统隔一小段时间就调用一下函数自动生成一个静态页面。而对于更新不频繁的就是在后台管理界面修改数据后再生成,也就是什么时候改了数据,什么时候更新。

在美多商城项目中,对网站的首页和详情页进行了页面静态化操作,首页数据更新比较频繁,采用定时任务进行首页静态页面的更新;而对于详情页面,当管理员通过Admin界面修改数据时才会更新对应商品的详情页面。

注:在Django框架中,通过Admin站点修改对应数据表的数据时,该数据表对应模型类Admin管理类中的savemodel和deletemodel会被调用。

10.定时任务

对于首页的静态化,考虑到页面的数据可能由多名运营人员维护,并且经常变动,所以将其做成定时任务,即定时执行静态化。利用扩展django_crontab来实现。

11.并发问题

当多人同时购买同一件商品时,有可能会产生订单并发问题。例如: 两个用户同时购买一件商品,每个购买5件,商品的库存为10件,产生订单并发问题之后,两人下单都成功,但是商品库存还有5件。

订单调度进程线程的顺序不确定

解决办法有引入悲观锁,使用乐观锁配合mysql的事务隔离级别READ-COMMITTED或者使用任务队列。

12跨域请求

对于两个url地址,如果协议,ip和port完整一致,这样的地址就是同源地址,否则就是不同源地址。客户端发出请求时,如果源请求地址和被请求地址不是同源,这个请求就是跨域请求。而浏览器在发起ajax跨域请求时,会有CORS跨域请求的限制。在发起跨域请求时,在请求中携带一个请求头Origin(源请求地址)。被请求的服务器在返回响应时,如果允许源地址对其进行跨域请求,需要中响应时携带一个响应头Access-Control-Allow-Origin(源请求地址),要是没有响应头,直接就报错,将请求驳回,概不受理。

在我们的项目中使用了django-cors-headers这个扩展,通过设置白名单的方式指明可以访问后端的域名。

13.模块

主要分为四大部分:用户部分、商品部分、购物车部分以及订单部分。

用户部分中,主要的功能有用户注册、用户登录和用户中心,用户不光支持账号密码登录,还支持QQ登录。个人中心则包括了较多的内容:个人基本信息的获取、邮箱设置、邮箱激活以及地址管理。相关的技术点比较多:包括Django认证系统,云通讯发送短信验证码,celery异步任务队列,自定义Django认证后端类,jwt认证机制,QQ登录,邮件发送和数据缓存的使用。

商品部分中,主要的功能有商品首页、商品详情页,商品列表页(也就是点击三级菜单分类,比如手机分类,点击后有很多商品的列表)和商品搜索。用到了FDFS文件存储系统,自定义Django文件存储类,页面静态化,定时任务,Django Admin界面修改数据处理过程,es搜索引擎以及haystack全文搜索框架等相关技术

购物车部分,实现了购物车记录的增删改查,购物车记录的全选功能和登录之前与之后的购物车记录合并相关功能。用到的技术点就是登录用户和未登录用户购物车记录怎么存储,购物车记录的合并。

订单部分中,加入了订单结算,订单保存,订单支付,订单列表页和订单评论的功能。相关技术点有订单保存的流程,订单事务的操作以及支付宝支付流程。

14.QQ登录流程

1.用户点击QQ登录按钮时,浏览器请求后端API( 获取QQ登录的网址和参数)。

2.后端API进行业务处理,组织QQ的登录网址及参数并返回给浏览器。

3.浏览器请求QQ登录网址。

4.QQ服务器经过处理之后,最终给浏览器返回QQ授权登录页面。

5.QQ用户进行授权登录操作。

6.授权成功之后,QQ服务器会让用户的浏览器重定向访问设置好的回调网址,同时会在回调网址后携带code和state参数。

7.浏览器请求回调网址,在加载回调网址页面时,再次请求后端API( 获取QQ登录用户的openid并处理)。

8.后端API进行业务处理,根据code请求QQ服务器获取accesstoken,再根据accesstoken请求QQ服务器获取openid,然后根据openid进行处理,如果openid已经绑定过网站用户,直接签发jwt token数据并进行返回;如果openid未绑定过网站用户,则对openid进行加密并返回。

9.进行QQ登录用户绑定时,点击保存时,浏览器请求后端API( 保存用户绑定信息)。

10.后端API进行业务处理,如果mobile已注册,直接保存绑定信息;如果mobile未注册,则创建新用户并进行绑定,然后签发jwt token数据并返回。

15.数据缓存

数据的缓存就是为了对网站进行优化。我们将经常被用户访问的数据从数据库中获取之后放到缓存中,这样当其他用户来访问的时候,我们直接从缓存中取就好了,大大减少了查询数据库的操作,提升了网站的性能,只有缓存中没有的,也就是第一次访问的数据,我们查一下。在我们的项目中,地址管理的三级联动效果,因为省市县经常被访问,我们使用了缓存。用到了扩展 cache_response,使用的方法就是加装饰器。

16.购物车存储以及记录合并

购物车存储

网站中,我们采用的方案是用户登录和未登录的情况下都可以进行购物车记录的添加。这样根据两种情况就有两种存储方式。

登录用户的购物车数据保存在redis中,每个用户的购物车数据采用两条数据保存。其hash用于保存用户购物车记录中添加的商品id和对应数量;set用于保存用户购物车记录勾选状态(保存勾选商品id)。

# hash: 保存用户购物车记录中添加的商品id和对应数量
 cart_<user_id>: {
     <sku_id>: <value>,
     <sku_id>: <value>,
     ...
 }
 # set: 保存用户购物车记录勾选状态(保存勾选商品id)
 # 集合中元素唯一
 cart_selected_<user_id>: (<sku_id>, <sku_id>, ...)

 # 例如:
 cart_2: {
     '1': '3',
     '5': '2',
     '3': '1'
 }

 id为2的用户购物车记录:
     id为1的商品加了3件,
     id为5的商品加了2件,
     id为3的商品加了1件

 cart_selected_2: ('1', '3')
     id为2的用户的购物车记录勾选状态:
     id为1商品和id为3的商品被勾选。

未登录用户的购物车数据服务器不进行保存,直接保存在客户端浏览器的cookie中。

{
     '<sku_id>': {
         'count': '<count>',
         'selected': '<selected>',
     },
     '<sku_id>': {
         'count': '<count>',
         'selected': '<selected>',
     },
     ...
 }

 # 设置cookie购物车数据
 res = base64.b64encode(pickle.dumps(cart_dict)).decode()
 response.set_cookie('cart', res, max_age=365*24*3600)

 # 解析cookie中购物车数据
 cart_data = request.COOKIES.get('cart')
 res = pickle.loads(base64.b64decode(cart_data))

购物车合并

当用户登录时,需要将cookie中的购物车数据合并到登录用户的redis购物车记录中。我们采用的合并法案就是合并购物车记录时,如果cookie中的购物车数据和redis中的购物车数据发生了冲突,以cookie中的购物车数据为准,直接拿cookie中的购物车数据去覆盖redis中的购物车数据。购物车合并不是单独的一个接口,而是在普通账户密码登录和QQ登录的接口中加入相关逻辑。

17订单保存基本流程

1.向订单基本信息表中添加一条数据;

2.订单中包含几个商品,需要向订单商品表中添加几条记录。

3.清除redis购物车中对应的记录。

18订单事务

在订单信息保存的过程中,凡是涉及到数据库的操作,都应该放在同一个事务中,下单过程中任何一个地方出错,订单相关表中都不应该添加数据。在项目中我们将同一个事务都放在 withtransaction.atomic():中,利用 savepoint设置事务保存点,利用 savepoint_rollback回滚到指定的保存点。

19支付宝支付流程

1.用户点击去支付按钮,请求后端API(获取支付宝支付网址)。

2.后端API进行业务处理,返回支付宝支付网址及参数。

3.浏览器访问支付宝支付网址。

4.支付宝平台进行处理,调用下单支付接口 alipay.trade.page.pay,并最终返回登录支付页面。

5.用户登录支付宝,选择支付方式、输入支付密码,并点击确认付款。

6.在付款成功之后,支付宝让浏览器重定向访问return url回调地址并携带支付结果参数。

7.浏览器访问return url页面,在页面加载时请求后端API接口并携带支付结果参数。

8.后端API进行业务处理,保存支付结果并返回支付宝交易编号。

20.对称加密和非对称加密

20.1对称加密解密

在对称加密的过程中,只有一个秘钥,无论是加密还是解密,都是这一个秘钥。可想而知,如果是秘钥丢失,那么就相当于明文了。因此对称加密使用的秘钥的安全性相当重要,一定不能公开。

20.2非对称加密解密

在非对称加密的过程中,有一对秘钥(公钥和私钥),公钥是对外公开的,任何一个客户端都可以有,但是私钥有且只有一个。服务器保存公钥和唯一的私钥,客户端的公钥由服务器传递。特点是公钥和私钥都可以进行加密,但是公钥加密的密文只有私钥能够解密,私钥加密的密文所有公钥都可以进行解密,这就是非对称加密。但是也有缺点,问题就出现在server给client发送消息的时候就不安全,因为所有的公钥都可以解密。要想解决,使用HTTPS协议就可以了。

优质文章推荐:

公众号使用指南

redis操作命令总结

前端中那些让你头疼的英文单词

Flask框架重点知识总结回顾

项目重点知识点详解

难点理解&面试题问答

flask框架中的一些常见问题

团队开发注意事项

浅谈密码加密

Django框架中的英文单词

Django中数据库的相关操作

DRF框架中的英文单词

重点内容回顾-DRF

Django相关知识点回顾

原文发布于微信公众号 - 小闫笔记(Pythonnote)

原文发表时间:2019-02-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券