美多商城
业务模式:B2C (个人到企业)
开发环境 ubuntu16.04+django1.11.11+mysql8+python3.5
项目模块:
- 1,用户
- 包括:注册(图形验证码,短信验证码)、登录(多条件登录,第三方登录,忘记密码)、用户中心(基本信息,邮箱激活,收获地址,个人订单,修改密码)
- 2,商品
- 包括:首页(首页广告,商品列表,商品搜索),列表页(热销排行),详情页(商品浏览记录)
- 3,购物车
- 包括:购物车管理,购物车合并,首页展示简单购物
- 4,商品订单
- 包括:确认订单,提交订单,订单商品评价
- 5,用户支付
- 支付宝支付
- 6,程序上线
- 页面静态化,数据库主从分离,消息队列优化,Nginx,uwsgi
- 7,后台管理(MIS系统)
- 数据统计,用户管理,权限管理,商品管理,订单管理
开发模式:
前后端不分离:为了提高搜索排名 SEO
页面整体刷新:django+jinja2
页面局部刷新:vue
项目流程架构:
用户请求--》Nginx代理服务器---》静态资源(css,js,首页,详情页)
---》动态资源(uwsgi服务器转发)--》框架--》内部流程
内部功能:数据存储MySQL 缓存Redis 异步任务Celery 消息队列RabbitMQ
文件存储FastDFS 全文检索ES 部署NGinx+uwsgi 定时任务Crontab
调用接口:云短信,支付宝SDK,Oauth2.0认证 QQ互联
Django默认用户认证系统
处理用户账号、组、权限和基于cookie的用户会话
位置 django.contrib.auth
同时处理认证和授权
包含内容:用户,权限,组,密码
Django默认用户模型类
User对象是认证系统的核心 继承AbstractUser
自定义用户模型
原因:要新增用户字段(手机号)
迁移自定义用户模型类要配置 AUTH_USER_MODEL = 'users.User'
注册
-
用户注册后端逻辑:
- 接收参数–校验参数是否齐全–检验参数格式是否符合要求–检验短信验证码–保存用户数据–响应结果,重定向到首页
-
用户状态保持:
- 调用默认的login() 方法,把通过认证的用户唯一信息如user.id写入到session会话中
- 前后端分离情况下:后端存储session,前端在cookie中存储session_id,实现用户登录后的身份认证
-
用户名、手机号重复注册:
- 用户输入信息–离开输入框触发js事件–ajax局部刷新–后台查询对应的数据是否存在–返回数据给前端–前端根据信息显示对应内容
-
图形验证码:
- 调用模块Captcha生成验证码
- 前端页面刷新生成uuid–将uuid传给后端–调用模块生成验证码–将内容存到redis(过期时间5分)–将图片返回给前端显示
- 注意:在这里图形验证码的目的是为短信验证码服务,防止机器疯狂发送短信
-
短信验证码:
- 发送调用第三方云短信
- 点击发送短信(手机号,图形验证码)–接收参数–校验参数–提取图形码,删除图形码,对比图形码–生成,保存短信验证码–发送短信验证码
- 避免频发短信:在后端缓存中存储对应标记,保存验证码同时保存发送标记,再点击就校验,通过返回json信息给前端提示
-
前后端分离下跨域问题:
- 根据浏览器同源策略:域=协议+域名+端口,在两个域中,以上三者中任意一个条件不同,均涉及到跨域的问题
- 浏览器策略:浏览器先发一个options请求,检查当前的域名是否支持跨域,如果支持才会发送对应请求
- 处理:CORS 使用django-cors-headers扩展
- 流程:在后端注册应用’corsheaders’,配置中间件,添加白名单CORS_ORIGIN_WHITELIST,前端查询是否支持跨域,后端查询白名单中有没有对应信息
-
异步任务:
- 采用第三方短信发送验证码,会因为网络阻塞程序向下执行,影响用户体验
- 采用Celery进行任务发送(分布式异步任务队列调度框架)
- 优点:简单(即插即用),高效(每分钟数百万任务),错误自动重启(retry)
- 1,支持定时任务和异步任务两种方式
- 2,组成:client客户端发送任务–broker中间件–worker执行者–backend任务执行结果
- 3,可以开启多线程也可以是多进程
- 4,应用:解耦,耗时,队列
-
redis操作优化:
- redis是B/S架构,客户端发送请求,监听socket返回,在服务端响应前是阻塞的
- 同时处理多个请求,应采用管道pipeline发送(收集多个指令一次性发送一次性将结果返回)降低延时
- 原理:队列,redis是单进程的服务,可以将多个命令放在一个tcp报文中发送,服务端将结果放在一个报文中返回,由于队列是先进先出,保证了顺序性
登录
登录逻辑:
输入参数提交--接收参数--校验参数(必传,格式)--认证登录用户--状态保持(设置时间一般2周)--返回响应
认证用户:使用django自带的authenticate()进行认证
状态保持:使用django自带的login() #其实内部实现了session记录
保持时间:session.set_expiry(0)设置session时间 0是关闭就过期, None是默认2周
登录的核心是:认证和状态保持
多账号登录:
实现用户名/手机号/邮箱登录
需要重写认证后端,即重写authenticate()方法
定义方法,当用户输入信息,判断是用户名还是手机号
然后重写认证方法,根据信息获取用户,再检查用户的密码user.check_password(password)
认证核心是:使用用户的唯一信息去查询数据库中是否有该用户
首页用户名展示:
登录成功后将用户名写入cookie,设定保持时间 set_cookie('username', user.username, max_age=3600 * 24 * 15)
在页面js从cookie中获取用户名,展示在前端页面上 this.username = getCookie('username')
退出登录:
调用自带的logout() # 实际上内部调用了cookie的刷新方法,删除当前会话
清除cookie中信息 delete_cookie('username')
判断用户是否登录:
使用django认证系统的LoginRequiredMixin类来判断,只需要在类中继承即可
也可以使用request.user.is_authenticated()来判断用户是否登录
继承后需要配置重定向页面,默认是重定向 '/' LOGIN_URL = '/login/',重定向到登录页
用户认证系统还提供了一个next参数,用于记录用户未登录时页面信息,可以在登录成功后跳转回登录前页面
next 可以配合 login_required装饰器使用,都是判断是否登录
next = request.GET.get('next')
前后端分离情况下的登录:
前后端分离情况下,用户登录后状态保持不能依赖于cookie,自然session也不适用(session基于cookie)
session一般保持在服务器,客户端保存session_id,一般在cookie中,当服务器保存大量的session会对服务器造成压力
所以选择 token,token保存在客户端
认证机制:用户携带用户名和密码后端验证--服务器验证成功后生成token--token返回前端记录用户信息--再次请求,携带token,服务器解析验证,确定用户状态
可以使用JWT,构成分为头,载荷,签证,JWT-token生成需要密钥,密钥一般保存在服务器
在正常情况下,用户token有效期不会设置过长,为了保存用户状态,需要两个token,token和刷新token
token一般2个小时,刷新token一般2周,用户浏览中如果token失效,刷新token会自动请求一个新的token保持状态
单点登录SSO:
服务器中有多个子系统(如淘宝对应的还用天猫,超市,生鲜等)
用户在不同的系统中,服务器需要认证用户保持状态
一般采用的方法是重定向,即用户在主系统的登录成功后,如果在子系统登录,首先重定向到主的登录页面查看登录状态,如果登录,将对应的信息返回进行保持,
一般大型系统会有一个登录认证模块专门处理
QQ登录:
登录流程:
用户点击第三方qq登录
网站跳转到qq登录页面
用户输入用户名,密码或者扫码向qq发起登录请求(qq的扫码登录页面)
qq服务器认证成功后将用户引导到回调网址中,并返回qq服务器的code值(code拼接重定向网址)
用户重定向到美多页面并携带了qq服务器的code
后端接收到code后向qq服务器请求access token
qq服务器返回access token
后端再通过access token向qq服务器获取用户的唯一标识open_id
qq服务器返回open_id,后端通过open_id查询当前用户是否绑定用户
如果绑定过用户,记录存在,就实现状态保持,跳转到首页
如果没有绑定用户,跳转到绑定页面,在这里进行用户绑定,
用户存在(即手机号注册过)直接绑定,用户不存在就直接书册用户进行绑定
要定义对应的模型,是用户和open_id的对应信息
使用的第三方包是 QQLoginTool
在用户进行绑定用户时,需要将open_id携带到绑定页面,为了数据安全,需要使用数据加密
这是使用第三方包itsdangerous,可以生成加密后的数据,还可以设置有效期
初始化加密工具 使用密钥 Serializer(settings.SECRET_KEY, expires_in=3600)
忘记密码:
用户发送请求,携带用户名和验证码
后端验证图片验证码,通过账号查询用户,返回用户的手机号给前端,并生成发送短信的token(载荷mobile),发送验证码
用户填写验证码并携带token到后端(token是为了标记用户 验证码是谁发的,校验又是校验谁的)
后端接收到收集验证码(校验),通过手机号获取对应用户,生成修改密码的token(载荷user对象),重定向到修改密码页面
前端填写完新密码后,携带token到后端重置密码(有用户,旧密码,新密码)
用户中心:
用户基本信息:
用户信息是登录用户才能访问,使用LoginRequiredMixin验证登录用户
前端页面加载完成后向后端发送请求
后端验证后判断用户登录,获取用户user模型
将用户信息返回前端,进行展示
添加验证邮箱:
添加邮箱:
接收参数(在请求体中添加的数据通过request.body获取)--校验参数--给用户添加邮箱
用户响应json数据(默认的验证登录MIxin类不返回json数据)--重写MIxin类,定义为JsonMixin类
发送邮件:
需要配置发送的邮箱服务器(邮箱回调--授权密码)
使用异步任务发送邮件--对邮件中验证用户信息使用itsdangerous进行加密(用户id,用户email)
验证邮箱:用户点击邮箱链接--指定页面--后端获取加密后token信息--验证token--获取用户--修改用户激活邮箱字段--返回结果
收货地址:
页面刷新--点击获取地址数据--判断是否有省份数据--如果没有就查询省份数据--有就查询市区数据--通过父级数据获取子级数据parent_model.subs.all()
序列化数据返回
数据需要缓存--运用django自身的缓存工具cache,设置缓存时间为1小时--具体查询时先查询缓存再查询数据库
新增地址逻辑:
填写提交--判断用户现有地址数量(地址总数有限制)--接收参数--校验参数--保存地址信息--返回json数据,页面局部刷新
删除地址:
查询要删除地址--将地址逻辑删除设为True--保存信息--返回响应刷新
修改密码:
接收参数--验证参数(格式--2次密码是否一致)--验证原密码是否正确--更新密码(request.user.set_password(new_password))--重定向--删除cookie--重新登录
商品:
商品分析:
SPU:标准产品单位 SKU:库存量单位
广告表分为:广告类别,广告内容
商品表分为:商品频道组,商品频道,商品类别,商品(SPU),商品(SKU),商品品牌,spu规格,sku规格,sku图片,商品规格选项
首页广告:
展示频道分类:
查询商品频道--获取首级频道--通过首级查子级--通过子级查孙级(for cat2 in cat1.subs.all())--构建序列化数据输出
商品列表页:
面包屑导航:
利用三级分类--获取当前分类--判断是否存在父级(if category.parent is None)--判断是否存在子级(category.subs.count())
分页和排序:
接收排序关键字参数--按照关键字进行排序查询--order_by(sort_field)
分页利用django分页器--Paginator:查询商品数据--创建分页器--获取每页商品数据--获取列表总页数--序列化数据返回响应
热销排行:
热销商品在一定时间内是不变的--可以利用缓存进行缓存--减少服务器压力
查询按信息,按销量排序,取前三序列化返回前端页面展示
商品详情页:
路由调用需要获取具体的产品id,/detail/(?P<sku_id>\d+)/
后端接收路由传递的参数--查询对应信息--查询频道分类和面包屑导航--返回渲染结果
通过商品查询对应的规格信息(参数--评价--详情等) sku.spu.specs.order_by('id')
统计商品访问量:建立对应模型(商品分类--数量)
商品点开--获取商品id--获取当前日期--查询该商品今天访问量,有就+1没有就新建一个访问记录
用户浏览记录:临时数据,数据量不大,经常变化--选择存储在内存中,因为记录和顺序有关,且是一个id对应多个信息所以选择list
每个用户浏览记录最多存储5个商品sku信息
存储逻辑:先去重--再存储--最后截取
用户点击详情页--接收参数(商品id)--查询商品--执行存储lpush[user_id:sku_id]
商品搜索:
要求:输入商品关键字,返回相应的搜索结果
方案一:使用模糊查询like,缺点:效率低,多个字段使用不方便,[SQL使用like 配合% _, ORM使用 字段__contains=关键字 查询]
方案二:使用Elasticsearch全文检索
搜索引擎原理:先对数据库数据进行一遍预处理,建立一份索引数据结构--索引记录了关键字和词条的对应关系,并记录词条位置
Elacticsearch是java实现的开源搜索引擎,底层是lucene,python没法直接使用需要使用接口调用(haystack),默认不支持中文分词(IK, jieba)
使用流程:
安装haystack--注册应用和路由--配置引擎和索引库,自动更新索引--建立索引类(索引类文件,模板文件,生成索引)--配置搜索结果页面(search.html)
调用:请求参数q,调用API路径/search/,主路由文件直接关联haystack搜索--结果返回--可以配置页面显示条数(结合页面分页)--HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
购物车:
用户购物车数据存储时需要做唯一性标识
存储选择:登录用户存储在redis中(因为用户,商品,数量,勾选状态很难统一存储,所以选择不同类型 用户:商品,数量 hash 用户:勾选商品 set)
hash适合存储对象,比如一个人的个人信息,需要修改较多的数据等,商品对应的数量有很大可能变化,而set适合存储单一数据与列表不同是,列表存储是顺序的
未登录用户存在cokie中,存在cookie中需要对数据进行序列化 pickle.dumps() 将数据转成二进制,base64.b64encode()将二进制数据进行编码(8位转6位)
由于设计的格式不同,在需要合并购物车操作时,需要将数据合适转成一致的格式
也可以设计成:{sku id :[count,True]} True表示选中
订单:
结算后台逻辑:
获取用户--查询地址信息--从购物车中查询勾选的商品信息--计算金额和数量--补充运费--返回页面渲染
提交订单:
订单表格:订单基本信息,订单商品
提交订单流程:
后台获取地址和支付方式--校验信息--获取用户--生成订单编号--遍历购物车信息,增加销量减少库存,清除购物车中数据--保存订单信息--返回响应
事务:要么成功,要么失败,可以通过django.db.transaction模块提供的atomic定义事务
并发下数据出错问题使用乐观锁下单,保证数据的完整性
展示下单成功页面(订单id,支付方式,支付金额)在30分钟内要完成支付(如果没有完成那么就要回滚状态,恢复库存,减少销量)
支付:
对接支付宝SDK:
安装SDK--申请沙箱账号--配置RSA2公私钥
电脑网站支付流程:
用户下单支付--调用支付SDK向支付接口发起请求--返回登录界面--用户登录支付宝--选中支付方式,输入支付密码
--获取回调页面URL--支付成功以异步通知为准,查询支付结果,返回支付结果页面--返回回调页面,显示支付的具体结果,交易信息(支付订单号,交易流水号)
--后台收到回调结果后,检查信息--确认信息是支付宝发出的--保存支付结果--修改订单状态--显示订单详细信息
(文件存储方案)FastDFS:
是一款开源的轻量级分布式文件系统
功能包括:文件存储、文件访问(上传,下载)、文件同步,解决了大容量存储和负载均衡问题,适合以文件为载体的网站服务
架构:client、tracker(跟踪、调度、负载均衡)、storage(存储服务器)
storage群横向可以备份,纵向可以备份
文件存储后返回客户端一个文件索引(组名/虚拟磁盘路径/数据两级目录/文件名)
(容器方案)Docker:
保持相同的运行环境,避免相同重复的工作,缩短代码从开发、测试到部署、上线运行的周期(程序可一致易于协作)
docker可以创建虚拟化的沙箱环境可以供测试使用
Docker是一个C/S架构程序,轻量级应用容器框架
三个概念:镜像(只读的文件系统,可以创建容器),容器(镜像运行实例,实质是镜像层上加一个可操作的容器层),仓库(类似github,托管镜像)
安装(17.03)--检查安装结果--启动服务--从仓库拉取镜像(本地镜像安装)--开启容器服务--查看映射路径
页面静态化:
首页静态化:
目的:减少查询次数、提升页面响应效率
页面静态化:将动态渲染生成的页面保存成html文件,放到静态文件服务器中(Nginx)
注意点:动态数据不能静态化(热销、新品、分页排序),用户数据不能静态化(用户名,购物车数据),对于不能静态化数据可以先获得页面再异步请求获取数据
定义生成静态文件方法--执行获取首页数据--渲染页面--将页面写入文件的任务
启动静态服务器
定时任务:可以使用django-crontab扩展
安装--注册--设定定时任务(分时日月周)--配置定时任务--将定时任务添加到系统中
详情页静态化:
目的:减少用户数据库查询提升页面响应效率
注意:与首页不同的是,1,数据变化不频繁,2,有具体数据不能静态化
定义生成静态文件脚本--获取当前商品信息--获取商品规格信息--执行定时任务生成静态文件
MySQL读写分离:
优点:多台服务器提高读写性能,主从备份提升数据安全
主从同步:基于二进制日志,主服务器使用二进制日志记录数据库变化情况,从服务器读取和执行该日志文件保持数据一致
主从应保存SQL版本一致
从docker仓库获取mysql镜像--指定,修改丛机配置文件--运行,测试是否成功--配置主机,备份数据--从机收集数据执行备份--
django配置读写分离--配置中添加读写不同的数据库配置--创建读写的路由文件--配置指定读写路由文件
RabbitMQ:
消息队列:可靠性高,可以持久化消息,吞吐量大
安装镜像--运行创建容器--在celery连接配置(broker_url= 'amqp://guest:guest@127.0.0.1:5672')--这就是celery配合的原因,使用方便
部署:
Nginx服务器作为静态文件服务器
配置目录--收集静态文件--修改服务器配置--启动服务器--测试访问
利用Nginx反向代理动态业务
实际生产采用uwsgi服务器运行django程序
调整配置文件--配置生产环境启动文件--安装uwsgi--新建uwsgi配置文件--启动uwsgi服务器--部署nginx反向代理--启动nginx