Hold on ,learn by myself!
redis nosql - 不支持sql语法 - 存储数据都是KV形式 - Mongodb - Redis - Hbase hadoop - Cassandra hadoop 关系型数据库 mysql/oracle/sql server/关系型数据库 通用的操作语言 关系型比非关系数据库: - sql适用关系特别复杂的数据查询场景 - sql对事务支持非常完善 - 两者不断取长补短 redis对比其他nosql产品: - 支持数据持久化 - 支持多样数据结构,list,set,zset,hash等 - 支持数据备份,即master-slave模式的数据备份 - 所有操作都是原子性的,指多线程没有抢数据的过程 redis 应用场景: 用来做缓存(echcache/memcached),redis所有数据放在内存中 社交应用 redis-server redis redis-cli redis 测试是否通信: ping ------------> pang 默认数据库16,通过0-15标示,select n 切换 常用通用命令 命令集 http://doc.redisfans.com - keys * keys a* #查询以a开头的key - exists key1 #返回1,0 - type key - del key1 - expire key seconds #设置过期时间 - ttl key #查看过期时间 1. string - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息 - set name value - get name - mset key1 python key2 linux - get key1 ,get key2 - mget key1 key2 - append a1 haha 追加字符串 - get a1 #a1+'haha' - setex key seconds value 2. hash - 用于存储对象,对象结果是属性、值 - 值的类型为string - hset user name itheima - hmset key field1 value1 field2 value2 - hkeys key - hget key field - hmget key field1 field2 - hvals key #获取所有的属性 - del key1 #删除整个hash键值, - hdel key field1 field2 #删除field1 field2的属性 3. list - 列表中元素类型为string - lpush key value1 value2 - lrange key 0 2 #start stop 返回列表里指定范围的元素 - lrange key 0 -1 #查询整列元素 - rpush key value1 value2 - linsert key before/after b 3 - b 现有元素 - 3 插入元素 - lset key index value #设置指定元素的值 - lrem key count value - count > 0 从左向右移除 - count < 0 从右向左移除 - count = 0 移除所有 4. set - 元素为string类型 - 无序集合 - sadd key zhangsan lisi wangwu - smembers key - srem key wangwu 5. zset - 有序集合 - 元素唯一性、不重复 - 每个元素都关联一个double类型的score权重,通常 从小到大排序 - zadd key score1 member1 score2 member2 - zrange key start stop - zrangebyscore key min max - zscore key member - zrem key member1 member2 - zremrangebyscore key min max python 操作 redis pip install redis from redis import StrictRedis redis 存储session 而session默认存储在django-session表里 pip install django-redis-sessions==0.5.6 open django工程,改setting配置redis SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS_HOST = 'localhost' SESSION_REDIS_PORT = 6379 SESSION_REDIS_DB = 2 SESSION_REDIS_PASSWORD = '' SESSION_REDIS_PREFIX = 'session' 通过redis-cli客户端查看 最后在Base64在线解码 主从配置实现读写分离 一个master可以拥有多个slave,一个slave可以有多个slave - 实现读写分离 - 备份主服务、防止主服务挂掉后数据丢失 bind 192.168.26.128 slaveof 192.168.26.128 6379 port 6378 redis集群 集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器 主服务、从服务 集群: - 软件层面 - 只有一台电脑,在这一台电脑上启动了多个redis服务。 - 硬件层面 - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。 集群和python交互: pip install redis-by-cluster from rediscluster import * if __name == 'main': try: startup_nodes = [ {'host':'192..','port':'700'},...] src = StricRedisCluster(startup_nodes=startup_nodes, decode_response = True) result = src.set('name','di') print(result) name = src.geg('name') print(name) except exception as e: print(e) ---------------mongodb----------- not only sql 有点: - 易扩展 - 大数据量、高性能 - 灵活的数据模型 缺点: - 占据的内存比之mysql要多 mongodb mongo show databases use douban db #查看当前数据路 db.dropDatabase() 不手动创建集合(类似mysql的表) db.createCollection(name,options) db.createCollection('sub',{capped:true,size:10}) show collections db.xxx.drop() Object ID String Boolean Integer false true ---类似json里的小写false Double Arrays Object Null Timestamp Date -------------------------flask 单元测试------------ 单元测试 - 程序员自测 - 一般用于测试一些实现某功能的代码 集成测试 系统测试 def num_div(num1num2): #断言为真、则成功,为假,则失败抛出异常/终止程序执行 #assert num1 int assert isinstance(num1,int) assert isinstance(num2,int) assert num2 != 0 print(num1/num2) if __name__ == '__main__' num_div('a','b') #AssertionError assertEqual assertNotEqual assertTrue assertFalse assertIsNone assertIsNotNone 必须以test_开头 classs LoginTest(unittest.TestCase): def test_empty_user_name_password(self): client = app.test_client() ret = client.post('/login',data={}) resp = ret.data resp = json.loads(resp) self.assertIn('code',resp) self.assertEqual(resp['code'],1) if __name__ == '__main__': unittest.main() cmd python test.python class LoginTest(unittest.TestCase): def setup(self): #相当于 __init__ self.client = app.test_client() #开启测试模式,获取错误信息 1 app.config['TESTING'] = True 2 app.testing = True def test_xxxx(self): ..... 简单单元测试 网络接口测试(视图) 数据库测试 import unittest from author_book import Author,db,app class DatabaseTest(unittest.TestCase): def setUp(self): app.testing = True #构建测试数据库 app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test" db.create_all() def test_add_user(self): author = Author(name="zhang",email='xxx'..) db.session.add(author) db.session.commit() result_author = Author.query.filter_by(name="zhang").first() self.assertNotNone(result_author) def tearDown(self): db.session.remove() db.drop_all() ------------部署------------- django uwsgi nginx 用户 Nginx 负载均衡 提供静态文件 业务服务器 flask + Gunicorn mysql/redis pip install gunicorn gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app if self.server_version_info < (5,7,20): cursor.execute(”SELECT @@tx_isolation") else: cursor.execute("SELECT @@transaction_isolation") --------------- 测试----------------- jekins - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目 jmeter - 软件做压力测试 postman - 创建天天生鲜 B2C 大型网站 1. 电商概念 B2B Alibaba C2C 瓜子二手车、淘宝、易趣 B2C 唯品会 C2B 尚品宅配 O2O 美团 F2C 戴尔 2. 开发流程 产品原型的设计 --- 产品经理-----axure 非常关键: - 架构设计 - 数据库设计 3. 数据库分析 mysql - redis - 若用户多,session服务器 - 对于经常访问的如首页,则用缓存服务器 xxx -异步任务处理celery (注册页面发邮件之类的) 分布式文件存储系统fastdfs(不用django默认的media上传文件方式) - 4. 数据库设计: a.用户模块、商品模块 用户表 - ID - 用户名 - 密码 - 邮箱 - 激活标识 - 权限标识 地址表(一个用户可能有多个地址) - ID - 收件人 - 地址 - 邮编 - 联系方式 - 是否默认 - 用户ID 商品SKU表 - ID - 名称 - 简介 - 价格 - 单位 - 库存 - 销量 - 详情 - *图片(就放一张,以空间换取时间) - 状态 - 种类ID - sup ID 商品SPU表 - ID - 名称 - 详情 商品种类表 - ID - 种类名称 - logo - 图片 商品图片表 - ID - 图片 - sku ID 首页轮播商品表 - ID - sku - 图片 - index 首页促销表 - ID - 图片 - 活动url - index 首页分类商品展示表 - ID - sku ID - 种类ID - 展示标识 - index b. 购物车模块 redis实现 - redis保存用户历史浏览记录 c. 订单模块 订单信息表 - 订单ID - 地址ID - 用户ID - 支付方式 - *总金额 - *总数目 - 运费 - 支付状态 - 创建时间 订单商品表 - ID - sku ID - 商品数量 - 商品价格 健表时须知: - 此时用的是Ubanto的mysql数据库,需要 - grant all on test2.* to 'root'@'1.2.3.4' identified by 'root' - flush privileges - migrate - choices - 富文本编辑器 - tinymce - pip install django-tinymce==2.6.0 - LANGUAGE_CODE = 'zh-hans' - TIME_ZONE = 'Asia/Shanghai' - url(r'^',include('goods.urls',namespace='goods')) - vervose_name - 项目框架搭建 - 四个app - user - goods - cart - order - 使用Abstractuser时,settings里需要 AUTH_USER_MODEL = 'user.User' class BaseModel(models.Model): '''模型类抽象基类’'' create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间') cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间') is_delete = models.BooleanField(default=False,verbose_name='删除标记') class Meta: #说明是一个抽象类模型 abstract = True 开始设计前后端 1. 如何设计四个app 2. register.html - 动态导入静态文件,{% load staticfiles %} link .... href="{% static 'css/reset.css' %}" - 前端post一个勾选按钮(阅读同意),后端收到是 if allow !== 'on': pass - 几乎每一个URL都有namespace,注册成功后跳转首页用 反向解析 return redirect(reverse('goods:index')) - goods 是app域名的别名 - index 是goods里面的别名 - 在mysql里输入 select * from df_user \G 信息会竖排显示 - 数据完整性校验 if not all([username,password,email]): pass - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活, 需要,在创建用户的时候加入如下: user=User.objects.create_user(username,email,password) user.is_active = 0 user.save() - 在注册之前先进性校验,register_handle 判断用户名是否重复, try: #get有的话只返回一个,没有的话会包异常 user=User.objects.get(username=username) except User.DoesNotExist: user = None if user: return ... - 类视图的使用 - 原理关键在dispatch,getattr - from django.view.generic import View class RegisterView(View): def get(self,request): pass def post(self,request): pass url(r'^register$', RegisterView.as_view(), name='register'), # 注册 - 发送激活邮件,包含激活连接 - 激活连接中需包含用户身份信息,并加密 - pip install itsdangerous from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from itsdangerous import SignatureExpired # 加密用户的身份信息,生成激活token serializer = Serializer(settings.SECRET_KEY, 3600) info = {'confirm':user.id} token = serializer.dumps(info) # bytes token = token.decode() #发邮件 1. django本身有秘钥、 subject message sender receiver html_message dend_mail(subject,message,sender,receiver,html_message =html_message) #发送html格式的 - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱 2.celery使用 任务发出者 -- 任务队列(broker)-- 任务处理者(worker) 发出任务 监听任务队列 pip install celery 任务队列是一种跨线程、跨机器工作的一种机制. celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者) 和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给 worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件 from celery import Celery # 创建一个Celery类的实例对象 app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8') # 定义任务函数 @app.task i. def send_register_active_email(to_email, username, token): '''发送激活邮件''' # 组织邮件信息 subject = '天天生鲜欢迎信息' message = '' sender = settings.EMAIL_FROM receiver = [to_email] html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token) send_mail(subject, message, sender, receiver, html_message=html_message) time.sleep(5) ii. # 发邮件 send_register_active_email.delay(email, username, token) iii.# Ubuntu虚拟机启动worker 1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件 一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准 2.vi celery_tasks/tasks.py django环境的初始化 # 在任务处理者一端加这几句 # import os # import django # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings") # django.setup() 3.启动 celery -A celery_tasks.tasks worker -l info - 激活成功后返回登陆页面 class ActiveView(View): '''用户激活''' def get(self, request, token): '''进行用户激活''' # 进行解密,获取要激活的用户信息 serializer = Serializer(settings.SECRET_KEY, 3600) try: info = serializer.loads(token) # 获取待激活用户的id user_id = info['confirm'] # 根据id获取用户信息 user = User.objects.get(id=user_id) user.is_active = 1 user.save() # 跳转到登录页面 return redirect(reverse('user:login')) except SignatureExpired as e: # 激活链接已过期 return HttpResponse('激活链接已过期') 3. login - 因为用户多,不能经常调用数据库,使用redis存储session https://django-redis-chs.readthedocs.io/zh_CN/latest/ - pip install django-redis - django缓存配置 - 指定ip的redis数据库 - 配置session存储 - redis-cli -h 192.169.12.1 - 是否记住用户名 i. class LoginView(View): '''登录''' def get(self, request): '''显示登录页面''' # 判断是否记住了用户名 if 'username' in request.COOKIES: username = request.COOKIES.get('username') checked = 'checked' else: username = '' checked = '' # 使用模板 return render(request, 'login.html', {'username':username, 'checked':checked}) ii. def post(self, request): '''登录校验''' # 接收数据 username = request.POST.get('username') password = request.POST.get('pwd') # 校验数据 if not all([username, password]): return render(request, 'login.html', {'errmsg':'数据不完整'}) # 业务处理:登录校验 user = authenticate(username=username, password=password) if user is not None: # 用户名密码正确 if user.is_active: # 用户已激活 # 记录用户的登录状态 login(request, user) # 跳转到首页 response = redirect(reverse('goods:index')) # HttpResponseRedirect # 判断是否需要记住用户名 remember = request.POST.get('remember') if remember == 'on': # 记住用户名 response.set_cookie('username', username, max_age=7*24*3600) else: response.delete_cookie('username') # 返回response return response else: # 用户未激活 return render(request, 'login.html', {'errmsg':'账户未激活'}) else: # 用户名或密码错误 return render(request, 'login.html', {'errmsg':'用户名或密码错误'}) iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名"> <input type="checkbox" name="remember" {{ checked }}> 4. 用户中心 - base模板的设计,分base.html base_no_cart.html,非常重要 {# 首页 注册 登录 #} <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> {% load staticfiles %} <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> {# 网页标题内容块 #} <title>{% block title %}{% endblock title %}</title> <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> {# 网页顶部引入文件块 #} {% block topfiles %}{% endblock topfiles %} </head> 。。。。。。 - 一个用户中心页面可以点三个页面 (用户/订单/收货地址) <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li> - 登录装饰器(如用户界面需要登录) i. login_required ?next=xxxx from django.contrib.auth.decorators import login_required settings # 配置登录url地址 LOGIN_URL='/user/login' # /accounts/login login.html <div class="form_input"> {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #} ..... url url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页 url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页 url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页 登录视图里的logic处理 if user.is_active: # 用户已激活 # 记录用户的登录状态 login(request, user) # 获取登录后所要跳转到的地址 # 默认跳转到首页 next_url = request.GET.get('next', reverse('goods:index')) # 跳转到next_url response = redirect(next_url) # HttpResponseRedirect # 判断是否需要记住用户名 remember = request.POST.get('remember') if remember == 'on': # 记住用户名 response.set_cookie('username', username, max_age=7*24*3600) else: response.delete_cookie('username') # 返回response return response ii. login_required - 一些经常用的python_package放在utils文件夹里,如mixin.py from django.contrib.auth.decorators import login_required class LoginRequiredMixin(object): @classmethod def as_view(cls, **initkwargs): # 调用父类的as_view view = super(LoginRequiredMixin, cls).as_view(**initkwargs) return login_required(view) - 同时类视图调用 from utils.mixin import LoginRequiredMixin class UserInfoView(LoginRequiredMixin, View):pass class UserOrderView(LoginRequiredMixin, View):pass settings(同上) url (不需要做处理了) url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页 url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页 url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页 登录视图里的logic处理(同上) - 用户登录欢迎信息(head) - 考点 # Django会给request对象添加一个属性request.user # 如果用户未登录->user是AnonymousUser类的一个实例对象 # 如果用户登录->user是User类的一个实例对象 # request.user.is_authenticated() - base.html {% if user.is_authenticated %} <div class="login_btn fl"> 欢迎您:<em>{{ user.username }}</em> <span>|</span> <a href="{% url 'user:logout' %}">退出</a> </div> {% else %} <div class="login_btn fl"> <a href="{% url 'user:login' %}">登录</a> <span>|</span> <a href="{% url 'user:register' %}">注册</a> </div> {% endif %} - logout url url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录 views from django.contrib.auth import authenticate, login, logout class LogoutView(View): '''退出登录''' def get(self, request): '''退出登录''' # 清除用户的session信息 logout(request) # 跳转到首页 return redirect(reverse('goods:index')) - 用户中心地址页(默认地址和新添地址的设计) i. post 新上传地址数据,从数据库里查找是否有默认地址 get 在页面上显示是否有默认地址 class AddressView(LoginRequiredMixin, View): '''用户中心-地址页''' def get(self, request): '''显示''' # 获取登录用户对应User对象 user = request.user # 获取用户的默认收货地址 # try: # address = Address.objects.get(user=user, is_default=True) # models.Manager # except Address.DoesNotExist: # # 不存在默认收货地址 # address = None address = Address.objects.get_default_address(user) # 使用模板 return render(request, 'user_center_site.html', {'page':'address', 'address':address}) def post(self, request): '''地址的添加''' # 接收数据 receiver = request.POST.get('receiver') addr = request.POST.get('addr') zip_code = request.POST.get('zip_code') phone = request.POST.get('phone') # 校验数据 if not all([receiver, addr, phone]): return render(request, 'user_center_site.html', {'errmsg':'数据不完整'}) # 校验手机号 if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone): return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'}) # 业务处理:地址添加 # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址 # 获取登录用户对应User对象 user = request.user # try: # address = Address.objects.get(user=user, is_default=True) # except Address.DoesNotExist: # # 不存在默认收货地址 # address = None address = Address.objects.get_default_address(user) if address: is_default = False else: is_default = True # 添加地址 Address.objects.create(user=user, receiver=receiver, addr=addr, zip_code=zip_code, phone=phone, is_default=is_default) # 返回应答,刷新地址页面 return redirect(reverse('user:address')) # get请求方式 ii. 因为get.post里都用到去models里查询默认数据,可以优化 - 模型管理器类方法封装 每个models里都有models.Manager 1. class AddressManager(models.Manager): '''地址模型管理器类''' # 1.改变原有查询的结果集:all() # 2.封装方法:用户操作模型类对应的数据表(增删改查) def get_default_address(self, user): '''获取用户默认收货地址''' # self.model:获取self对象所在的模型类 try: address = self.get(user=user, is_default=True) # models.Manager except self.model.DoesNotExist: # 不存在默认收货地址 address = None return address 2. class Address(BaseModel): '''地址模型类''' .... # 自定义一个模型管理器对象 objects = AddressManager() .. 3. views调用 address = Address.objects.get_default_address(user) - 用户中心个人信息页历史浏览记录 - 在用户访问详情页面(SKU),需要添加历史浏览记录 - 存在表中要经常增删改查不放方便,所以存redis - redis数据库->内存性的数据库 - 表格设计 1. 所有用户历史记录用一条数据保存 hash history:'user_id':'1,2,3' 2. 一个用户历史记录用一条数据保存 list history_user_id:1,2,3 添加记录时,用户最新浏览的商品id从列表左侧插入 - 实际使用 1. StrictRedis i.# Django的缓存配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://172.16.179.130:6379/9", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } ii.form redis import StricRedis # 获取用户的历史浏览记录 # from redis import StrictRedis # sr = StrictRedis(host='172.16.179.130', port='6379', db=9) history_key = 'history_%d'%user.id # 获取用户最新浏览的5个商品的id sku_ids = con.lrange(history_key, 0, 4) # [2,3,1] ******** # 从数据库中查询用户浏览的商品的具体信息 # goods_li = GoodsSKU.objects.filter(id__in=sku_ids) # 数据库查询时按遍历的方式,只要id in里面,则查询出来 #这样就违背了用户真实历史浏览记录了 i. # goods_res = [] # for a_id in sku_ids: # for goods in goods_li: # if a_id == goods.id: # goods_res.append(goods) # 遍历获取用户浏览的商品信息 ii.goods_li = [] for id in sku_ids: goods = GoodsSKU.objects.get(id=id) goods_li.append(goods) iii.user_info.html 使用{% empty %}标签 ,相当于elseif {% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% empty %} <p>There are no athletes. Only computer programmers.</p> {% endfor %} 2. from django_redis import get_redis_connection con = get_redis_connection('default') 其它同上 5. fastdfs - 概念 - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的 文件服务器集群提供文件上传、下载等服务。 - 架构包括 Tracker server 和 Storage server。 - 客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。 - Tracker server 作用是负载均衡和调度 - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上 - 优势:海量存储、存储容量扩展方便、文件内容重复 6. 商品搜索引擎 - 搜索引擎 1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据 - 如 select * from xxx where name like '%草莓%' or desc like '%草莓%' - 很 好吃 的 草莓:sku_id1 sku_id2 sku_id5 字典 2. 全文检索框架 可以帮助用户使用搜索引擎 用户->全文检索框架(haystack)->搜索引擎(whoosh) - 安装即使用 - pip isntall django-haystack pip install whoosh - 在settings里注册haystack并配置 - INSTALLED_APPS = ( 'django.contrib.admin', ... 'tinymce', # 富文本编辑器 'haystack', # 注册全文检索框架 'user', # 用户模块 'goods', # 商品模块 'cart', # 购物车模块 'order', # 订单模块 ) - # 全文检索框架的配置 HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎 # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 索引文件路径 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 当添加、修改、删除数据时,自动生成索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' - 索引文件的生成 - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类 # 定义索引类 from haystack import indexes # 导入你的模型类 from goods.models import GoodsSKU # 指定对于某个类的某些数据建立索引 # 索引类名格式:模型类名+Index class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable): # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中 text = indexes.CharField(document=True, use_template=True) def get_model(self): # 返回你的模型类 return GoodsSKU # 建立索引的数据 def index_queryset(self, using=None): return self.get_model().objects.all() - 在templates下面新建目录search/indexes/goods。 templates search 固定 indexes 固定 goods 模型类所在应用app goodssku_text.txt 模型类名小写_text.txt - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。 # 指定根据表中的哪些字段建立索引数据 {{ object.name }} # 根据商品的名称建立索引 {{ object.desc }} # 根据商品的简介建立索引 {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询 - 使用命令生成索引文件 python manage.py rebuild_index - 会在whoosh_index文件夹里建立xx个商品索引(所有) - 全文检索的使用 - 配置url url(r'^search', include('haystack.urls')), # 全文检索框架 - 表单搜索时设置表单内容如下 base.html get和q固定 <div class="search_con fl"> <form method="get" action="/search"> <input type="text" class="input_text fl" name="q" placeholder="搜索商品"> <input type="submit" class="input_btn fr" name="" value="搜索"> </form> </div> - 全文检索结果 搜索出结果后,haystack会把搜索出的结果传递给templates/search 目录下的search.html,传递的上下文包括: query:搜索关键字 page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象, 对象的属性object才是模型类的对象。 paginator:分页paginator对象 通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。 - search.html分页器的经典使用 <div class="pagenation"> {% if page.has_previous %} <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a> {% endif %} {% for pindex in paginator.page_range %} {% if pindex == page.number %} <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a> {% else %} <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a> {% endif %} {% endfor %} {% if spage.has_next %} <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a> {% endif %} </div> </div> - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索 需要优化,商品搜索、改变分词方式 - 安装jieba分词模块 pip install jieba str = '很不错的草莓' res = jieba.cut(str,cut_all=True) for val in res: print(val) - 。。。。。 - settings里配置需要更改 - 最后重新创建索引数据 python manage.py rebuild_index 7. 订单并发处理 - 悲观锁 - select * from xx where id=2 for update - 应用场景: try: #select * from df_goods_sku where id=sku_id for update; sku=GoodsSKU.object.select_for_update().get(id=sku_id) except: transaction.savepoint_rollback(save_id) return ... - 乐观锁 - 查询数据时不加锁,在更新时进行判断 - 判断更新时的库存和之前查出的库存是否一致 - 操作 - for i in range(3): #update df_goods_sku set tock=stock,sales=new_sales where id= sku_id and stock=origin_stock res=GoodsSKU.object.filter(id=sku_id,stock=stock).update( stock=new_stock,sales=new_sales) if res==0: 库存为0 if i==2: #尝试第3次查询 transaction.savepoint_rollback(save_id) - mysql事务隔离性 事务隔离级别 - Read Uncommitted - Read Committed(大多数数据库默认) - Repeatable Read(mysql默认,产生幻读) - serializable(可串行化,解决幻读问题,但容易引发竞争) - 重新配置mysql.conf为read committed 总结: - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间 - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁 - 判断的时候,更新失败不一定是库存不足,需要再去尝试 SKU & SPU - SPU - Standard product unittest - 商品信息聚合的最小单位 - 如iphone, - SKU - STOCK keeping unittest - 库存量进出计量单位 - 如纺织品中一个SKU表示: 规格、颜色、款式
单元测试
单元测试 pip install unittest assert xxx xxx false true xitong jicheng 一些功能基础测试 网络报文测试 数据库测试 def test_div(num1,num2): assert num1 int assert num1 int assert isinstance(num1,int) assert num2 != 0 AssertionError assertEqual assertNotEqual assertIn assertNotIn assertTrue assertFalse assertNone assertNotNone class LoginTest(unittest.TestCase): def test_empty(self): client = app.test_client() rep = client.post('/login',data={}) rep = rep.data rep = json.loads(rep) self.assertIn('code',resp) self.assertEqual(rep['code'],1) if __name__ == '__main__': unittest.main() python test.py class LoginTest(unittest.TestCase): def setUp(self): self.client = app.test_client() app.testing = True def test_empty_user(self): def tearDown(self): sss
flask
回顾: 1.谈谈你对django和flask的认识。 2.flask和django最大的不同点:request/session 3.flask知识点 - 模板+静态文件,app= Flask(__name__,....) - 路由 @app.route('/index',methods=["GET"]) - 请求 request.form request.args request.method - 响应 "" render redirect - session session['xx'] = 123 session.get('xx') 4. 路飞总共有几个项目 - 管理后台 - 导师后台 - 主站 5. 路飞主站业务 - 课程 - 课程列表 - 课程详细 - 大纲、导师、推荐课程 - 价格策略 - 章节和课时 - 常见问题 - 深科技 - 文章列表 - 文章详细 - 收藏 - 评论 - 点赞 - 支付 - 购物车(4) - 结算中心(3) - 立即支付(1) 知识点: - redis - 支付宝 - 消息推送 - 构建数据结构 - 优惠券+贝里+支付宝 - 个人中心 - 课程中心 6. 播放视频:CC视频 - 加密 - 非加密 今日内容: 1. 配置文件 2. 路由系统 3. 视图 4. 请求相关 5. 响应 6. 模板渲染 7. session 8. 闪现 9. 中间件 10. 蓝图(blueprint) 11. 特殊装饰器 内容详细: 知识点: - 给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。 settings.py class Foo: DEBUG = True TEST = True xx.py import importlib path = "settings.Foo" p,c = path.rsplit('.',maxsplit=1) m = importlib.import_module(p) cls = getattr(m,c) # 如果找到这个类? for key in dir(cls): if key.isupper(): print(key,getattr(cls,key)) 1. 配置文件 app.config.from_object("settings.DevelopmentConfig") class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:' class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True 2. 路由系统 - endpoint,反向生成URL,默认函数名 - url_for('endpoint') / url_for("index",nid=777) - 动态路由: @app.route('/index/<int:nid>',methods=['GET','POST']) def index(nid): print(nid) return "Index" 3. FBV 4. 请求相关 # 请求相关信息 # request.method # request.args # request.form # request.values # request.cookies # request.headers # request.path # request.full_path # request.script_root # request.url # request.base_url # request.url_root # request.host_url # request.host # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) 5. 响应: 响应体: return “asdf” return jsonify({'k1':'v1'}) return render_template('xxx.html') return redirect() 定制响应头: obj = make_response("asdf") obj.headers['xxxxxxx'] = '123' obj.set_cookie('key', 'value') return obj 示例程序:学生管理 版本一: @app.route('/index') def index(): if not session.get('user'): return redirect(url_for('login')) return render_template('index.html',stu_dic=STUDENT_DICT) 版本二: import functools def auth(func): @functools.wraps(func) def inner(*args,**kwargs): if not session.get('user'): return redirect(url_for('login')) ret = func(*args,**kwargs) return ret return inner @app.route('/index') @auth def index(): return render_template('index.html',stu_dic=STUDENT_DICT) 应用场景:比较少的函数中需要额外添加功能。 版本三:before_request @app.before_request def xxxxxx(): if request.path == '/login': return None if session.get('user'): return None return redirect('/login') 6. 模板渲染 - 基本数据类型:可以执行python语法,如:dict.get() list['xx'] - 传入函数 - django,自动执行 - flask,不自动执行 - 全局定义函数 @app.template_global() def sb(a1, a2): # {{sb(1,9)}} return a1 + a2 @app.template_filter() def db(a1, a2, a3): # {{ 1|db(2,3) }} return a1 + a2 + a3 - 模板继承 layout.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>模板</h1> {% block content %}{% endblock %} </body> </html> tpl.html {% extends "layout.html"%} {% block content %} {{users.0}} {% endblock %} - include {% include "form.html" %} form.html <form> asdfasdf asdfasdf asdf asdf </form> - 宏 {% macro ccccc(name, type='text', value='') %} <h1>宏</h1> <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> <input type="submit" value="提交"> {% endmacro %} {{ ccccc('n1') }} {{ ccccc('n2') }} - 安全 - 前端: {{u|safe}} - 前端: MarkUp("asdf") 7. session 当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。 视图函数: @app.route('/ses') def ses(): session['k1'] = 123 session['k2'] = 456 del session['k1'] return "Session" session['xxx'] = 123 session['xxx'] 当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。 8. 闪现,在session中存储一个数据,读取时通过pop将数据移除。 from flask import Flask,flash,get_flashed_messages @app.route('/page1') def page1(): flash('临时数据存储','error') flash('sdfsdf234234','error') flash('adasdfasdf','info') return "Session" @app.route('/page2') def page2(): print(get_flashed_messages(category_filter=['error'])) return "Session" 9. 中间件 - call方法什么时候出发? - 用户发起请求时,才执行。 - 任务:在执行call方法之前,做一个操作,call方法执行之后做一个操作。 class Middleware(object): def __init__(self,old): self.old = old def __call__(self, *args, **kwargs): ret = self.old(*args, **kwargs) return ret if __name__ == '__main__': app.wsgi_app = Middleware(app.wsgi_app) app.run() 10. 特殊装饰器 1. before_request 2. after_request 示例: from flask import Flask app = Flask(__name__) @app.before_request def x1(): print('before:x1') return '滚' @app.before_request def xx1(): print('before:xx1') @app.after_request def x2(response): print('after:x2') return response @app.after_request def xx2(response): print('after:xx2') return response @app.route('/index') def index(): print('index') return "Index" @app.route('/order') def order(): print('order') return "order" if __name__ == '__main__': app.run() 3. before_first_request from flask import Flask app = Flask(__name__) @app.before_first_request def x1(): print('123123') @app.route('/index') def index(): print('index') return "Index" @app.route('/order') def order(): print('order') return "order" if __name__ == '__main__': app.run() 4. template_global 5. template_filter 6. errorhandler @app.errorhandler(404) def not_found(arg): print(arg) return "没找到" 总结: - 配置文件 - 路由 - 视图:FBV - 请求 - 响应 obj = make_response("adfasdf") obj.headers['x'] = asdfasdf return obj - 模板 - session - flash - 中间件 - 特殊装饰器 1. 上下文 - request在django和flask中不一样 全局变量-->线程局部变量,使用起来就像线程的局部 变量一样 - 如用户A,B..同时访问/index,name=XX,在视图里 request.form.get('name')是多少呢, 由此才有上下文的概念将之隔开处理 { “线程A”:{ form:{'name':"zhangsan"} args: }, “线程A”:{ form:{'name':"lisi"} args: }, } - 并发(处理多少个线程资源) - 请求上下文 - request/session(每个用户独有的session信息) - 应用上下文 - current_app 表示当前运行程序文件的程序实例 - g 处理请求时,用于临时存储的对象,每次 请求都会重设这个变量 2. 请求钩子 - before_first_request 在第一次请求处理之前先被执行 - before request 在每次请求之前都被执行 - after_request 在每次请求之后执行前提是视图无异常 - teaddown_request 在每次请求之后都被执行 3. flask_script 类似django的manage.py起管理作用 pipn install Flask-Script 使用 - 在xx.py 里需要 from flask_script import Manager #启动命令的管理类 app = Flask(__name__) manager = Manager(app) @app.route()"/index" def index(): return ccccc if __name__=='__main__': #通过管理对象来启动app manager.run() - python xx.py runserver -h -p.... - python xx.py shell 4.sqlalchemy - 其它框架都能用的关系型数据库 - pip install flask-sqlalchemy
flask-sqlacchemy
4.sqlalchemy - 其它框架都能用的关系型数据库 - pip install flask-sqlalchemy - 要连接mysql数据库,仍需要安装flask-mysqldb pip install flask-mysqldb - 初始化配置 from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) class Config(object): """配置参数""" # sqlalchemy的配置参数 SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/db_python04" # 设置sqlalchemy自动更跟踪数据库 SQLALCHEMY_TRACK_MODIFICATIONS = True app.config.from_object(Config) # 创建数据库sqlalchemy工具对象 db = SQLAlchemy(app) 。。。。 - 创建数据库模型表models 表名常见规范 数据库缩写_表名 ihome--> ih_user class Role(db.Model): """用户角色/身份表""" __tablename__ = "tbl_roles" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(32), unique=True) users = db.relationship("User", backref="role") class User(db.Model): """用户表""" __tablename__ = "tbl_users" # 指明数据库的表名 id = db.Column(db.Integer, primary_key=True) # 整型的主键,会默认设置为自增主键 name = db.Column(db.String(64), unique=True) email = db.Column(db.String(128), unique=True) password = db.Column(db.String(128)) role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id")) - 分析 - db.Column在表中都是真实存在的数据,relationship非真 - 有了外键,可以User.role_id.name正向查询,但不能表名小写_set反向查询 - 有了relationship,可以直接Role.users.name,对象查询 - 但是只有外键的话,如User.role_id仅仅是数字,若想为对象,需要加个 backref的方法,则User.role.name就可以直接查询了 - # 清除数据库里的所有数据(第一次才用) db.drop_all() # 创建所有的表 db.create_all() - 保存(增加)数据 - 增加单条数据 role1 = Role(name="admin") # session记录对象任务 db.session.add(role1) # 提交任务到数据库中 db.session.commit() - 增加多条数据 us1 = User(name='wang', email='wang@163.com', password='123456', role_id=role1.id) us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=role2.id) us3 = User(name='chen', email='chen@126.com', password='987654', role_id=role2.id) us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=role1.id) # 一次保存多条数据 db.session.add_all([us1, us2, us3, us4]) db.session.commit() - 查询数据 - 查询多条、查询一条 Role.query.all() Out[2]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In [3]: li = Role.query.all() In [4]: li Out[4]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In [5]: r = li[0] In [6]: type(r) Out[6]: db_demo.Role In [7]: r.name Out[7]: u'admin' In [8]: Role.query.first() Out[8]: <db_demo.Role at 0x10388d190> In [9]: r = Role.query.first() In [10]: r.name Out[10]: u'admin' # 根据主键id获取对象 In [11]: r = Role.query.get(2) In [12]: r Out[12]: <db_demo.Role at 0x10388d310> In [13]: r.name Out[13]: u'stuff' In [14]: # 另一种查询方式 In [15]: db.session.query(Role).all() Out[15]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In [16]: db.session.query(Role).get(2) Out[16]: <db_demo.Role at 0x10388d310> In [17]: db.session.query(Role).first() Out[17]: <db_demo.Role at 0x10388d190> In [18]: In [18]: User.query.filter_by(name="wang") Out[18]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0> In [19]: User.query.filter_by(name="wang").all() Out[19]: [<db_demo.User at 0x1038c87d0>] In [20]: User.query.filter_by(name="wang").first() Out[20]: <db_demo.User at 0x1038c87d0> In [21]: user = User.query.filter_by(name="wang").first() In [22]: user.name Out[22]: u'wang' In [23]: user.email Out[23]: u'wang@163.com' In [24]: User.query.filter_by(name="wang", role_id=1).first() Out[24]: <db_demo.User at 0x1038c87d0> In [25]: User.query.filter_by(name="wang", role_id=2).first() In [26]: user = User.query.filter_by(name="wang", role_id=2).first() In [27]: type(user) Out[27]: NoneType In [28]: In [28]: user = User.query.filter(User.name=="wang", User.role_id==1).first ...: () In [29]: user Out[29]: <db_demo.User at 0x1038c87d0> In [30]: user.name Out[30]: u'wang' In [31]: from sqlalchemy import or_ In [32]: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com") ...: )).all() Out[32]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>] In [33]: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163. ...: com"))).all() In [34]: li[0].name Out[34]: u'wang' In [35]: li[1].name Out[35]: u'zhou' In [36]: # offset偏移 跳过几条 In [36]: User.query.offset(2).all() Out[36]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>] In [37]: li = User.query.offset(2).all() In [38]: li[0].name Out[38]: u'chen' In [39]: li[1].name Out[39]: u'zhou' In [40]: In [42]: li = User.query.offset(1).limit(2).all() In [43]: li Out[43]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>] In [44]: li[0].name Out[44]: u'zhang' In [45]: li[1].name Out[45]: u'chen' In [46]: In [50]: User.query.order_by("-id").all() Out[50]: [<db_demo.User at 0x1038ef310>, <db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c87d0>] In [51]: In [51]: li = User.query.order_by(User.id.desc()).all() In [52]: li Out[52]: [<db_demo.User at 0x1038ef310>, <db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c87d0>] In [53]: li[0].name Out[53]: u'zhou' In [54]: li[3].name Out[54]: u'wang' In [55]: In [55]: from sqlalchemy import func In [56]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i ...: d) Out[56]: <flask_sqlalchemy.BaseQuery at 0x103a38050> In [57]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i ...: d).all() Out[57]: [(1L, 2L), (2L, 2L)] In [58]: - 跨表查询数据 - 关联查询 - 定义显示信息 def __repr__(self): return "User object: name=%s" % self.name In [61]: ro = Role.query.get(1) In [62]: type(ro) Out[62]: db_demo.Role In [63]: ro.users Out[63]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>] In [64]: ro.users[0].name Out[64]: u'wang' In [65]: ro.users[1].name Out[65]: u'zhou' In [66]: In [67]: user Out[67]: <db_demo.User at 0x1038c87d0> In [68]: user.role_id Out[68]: 1L In [69]: Role.query.get(user.role_id) Out[69]: <db_demo.Role at 0x10388d190> In [70]: user.role Out[70]: <db_demo.Role at 0x10388d190> In [71]: user.role.name Out[71]: u'admin' In [72]: - 数据的修改与删除 # 更新 In [14]: User.query.filter_by(name="zhou").update({"name": "python", "emai ...: l": "python@itast.cn"}) Out[14]: 1L In [15]: db.session.commit() In [16]: # 删除 In [16]: user = User.query.get(3) In [17]: db.session.delete(user) In [18]: db.session.commit() In [19]: 5.flask migrate 数据库迁移 - 安装 pip install flask-migrate
add daily
redis nosql - 不支持sql语法 - 存储数据都是KV形式 - Mongodb - Redis - Hbase hadoop - Cassandra hadoop 关系型数据库 mysql/oracle/sql server/关系型数据库 通用的操作语言 关系型比非关系数据库: - sql适用关系特别复杂的数据查询场景 - sql对事务支持非常完善 - 两者不断取长补短 redis对比其他nosql产品: - 支持数据持久化 - 支持多样数据结构,list,set,zset,hash等 - 支持数据备份,即master-slave模式的数据备份 - 所有操作都是原子性的,指多线程没有抢数据的过程 redis 应用场景: 用来做缓存(echcache/memcached),redis所有数据放在内存中 社交应用 redis-server redis redis-cli redis 测试是否通信: ping ------------> pang 默认数据库16,通过0-15标示,select n 切换 常用通用命令 命令集 http://doc.redisfans.com - keys * keys a* #查询以a开头的key - exists key1 #返回1,0 - type key - del key1 - expire key seconds #设置过期时间 - ttl key #查看过期时间 1. string - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息 - set name value - get name - mset key1 python key2 linux - get key1 ,get key2 - mget key1 key2 - append a1 haha 追加字符串 - get a1 #a1+'haha' - setex key seconds value 2. hash - 用于存储对象,对象结果是属性、值 - 值的类型为string - hset user name itheima - hmset key field1 value1 field2 value2 - hkeys key - hget key field - hmget key field1 field2 - hvals key #获取所有的属性 - del key1 #删除整个hash键值, - hdel key field1 field2 #删除field1 field2的属性 3. list - 列表中元素类型为string - lpush key value1 value2 - lrange key 0 2 #start stop 返回列表里指定范围的元素 - lrange key 0 -1 #查询整列元素 - rpush key value1 value2 - linsert key before/after b 3 - b 现有元素 - 3 插入元素 - lset key index value #设置指定元素的值 - lrem key count value - count > 0 从左向右移除 - count < 0 从右向左移除 - count = 0 移除所有 4. set - 元素为string类型 - 无序集合 - sadd key zhangsan lisi wangwu - smembers key - srem key wangwu 5. zset - 有序集合 - 元素唯一性、不重复 - 每个元素都关联一个double类型的score权重,通常 从小到大排序 - zadd key score1 member1 score2 member2 - zrange key start stop - zrangebyscore key min max - zscore key member - zrem key member1 member2 - zremrangebyscore key min max python 操作 redis pip install redis from redis import StrictRedis redis 存储session 而session默认存储在django-session表里 pip install django-redis-sessions==0.5.6 open django工程,改setting配置redis SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS_HOST = 'localhost' SESSION_REDIS_PORT = 6379 SESSION_REDIS_DB = 2 SESSION_REDIS_PASSWORD = '' SESSION_REDIS_PREFIX = 'session' 通过redis-cli客户端查看 最后在Base64在线解码 主从配置实现读写分离 一个master可以拥有多个slave,一个slave可以有多个slave - 实现读写分离 - 备份主服务、防止主服务挂掉后数据丢失 bind 192.168.26.128 slaveof 192.168.26.128 6379 port 6378 redis集群 集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器 主服务、从服务 集群: - 软件层面 - 只有一台电脑,在这一台电脑上启动了多个redis服务。 - 硬件层面 - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。 集群和python交互: pip install redis-by-cluster from rediscluster import * if __name == 'main': try: startup_nodes = [ {'host':'192..','port':'700'},...] src = StricRedisCluster(startup_nodes=startup_nodes, decode_response = True) result = src.set('name','di') print(result) name = src.geg('name') print(name) except exception as e: print(e) ---------------mongodb----------- not only sql 有点: - 易扩展 - 大数据量、高性能 - 灵活的数据模型 缺点: - 占据的内存比之mysql要多 mongodb mongo show databases use douban db #查看当前数据路 db.dropDatabase() 不手动创建集合(类似mysql的表) db.createCollection(name,options) db.createCollection('sub',{capped:true,size:10}) show collections db.xxx.drop() Object ID String Boolean Integer false true ---类似json里的小写false Double Arrays Object Null Timestamp Date -------------------------flask 单元测试------------ 单元测试 - 程序员自测 - 一般用于测试一些实现某功能的代码 集成测试 系统测试 def num_div(num1num2): #断言为真、则成功,为假,则失败抛出异常/终止程序执行 #assert num1 int assert isinstance(num1,int) assert isinstance(num2,int) assert num2 != 0 print(num1/num2) if __name__ == '__main__' num_div('a','b') #AssertionError assertEqual assertNotEqual assertTrue assertFalse assertIsNone assertIsNotNone 必须以test_开头 classs LoginTest(unittest.TestCase): def test_empty_user_name_password(self): client = app.test_client() ret = client.post('/login',data={}) resp = ret.data resp = json.loads(resp) self.assertIn('code',resp) self.assertEqual(resp['code'],1) if __name__ == '__main__': unittest.main() cmd python test.python class LoginTest(unittest.TestCase): def setup(self): #相当于 __init__ self.client = app.test_client() #开启测试模式,获取错误信息 1 app.config['TESTING'] = True 2 app.testing = True def test_xxxx(self): ..... 简单单元测试 网络接口测试(视图) 数据库测试 import unittest from author_book import Author,db,app class DatabaseTest(unittest.TestCase): def setUp(self): app.testing = True #构建测试数据库 app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test" db.create_all() def test_add_user(self): author = Author(name="zhang",email='xxx'..) db.session.add(author) db.session.commit() result_author = Author.query.filter_by(name="zhang").first() self.assertNotNone(result_author) def tearDown(self): db.session.remove() db.drop_all() ------------部署------------- django uwsgi nginx 用户 Nginx 负载均衡 提供静态文件 业务服务器 flask + Gunicorn mysql/redis pip install gunicorn gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app if self.server_version_info < (5,7,20): cursor.execute(”SELECT @@tx_isolation") else: cursor.execute("SELECT @@transaction_isolation") --------------- 测试----------------- jekins - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目 jmeter - 软件做压力测试 postman - 创建天天生鲜 B2C 大型网站 1. 电商概念 B2B Alibaba C2C 瓜子二手车、淘宝、易趣 B2C 唯品会 C2B 尚品宅配 O2O 美团 F2C 戴尔 2. 开发流程 产品原型的设计 --- 产品经理-----axure 非常关键: - 架构设计 - 数据库设计 3. 数据库分析 mysql - redis - 若用户多,session服务器 - 对于经常访问的如首页,则用缓存服务器 xxx -异步任务处理celery (注册页面发邮件之类的) 分布式文件存储系统fastdfs(不用django默认的media上传文件方式) - 4. 数据库设计: a.用户模块、商品模块 用户表 - ID - 用户名 - 密码 - 邮箱 - 激活标识 - 权限标识 地址表(一个用户可能有多个地址) - ID - 收件人 - 地址 - 邮编 - 联系方式 - 是否默认 - 用户ID 商品SKU表 - ID - 名称 - 简介 - 价格 - 单位 - 库存 - 销量 - 详情 - *图片(就放一张,以空间换取时间) - 状态 - 种类ID - sup ID 商品SPU表 - ID - 名称 - 详情 商品种类表 - ID - 种类名称 - logo - 图片 商品图片表 - ID - 图片 - sku ID 首页轮播商品表 - ID - sku - 图片 - index 首页促销表 - ID - 图片 - 活动url - index 首页分类商品展示表 - ID - sku ID - 种类ID - 展示标识 - index b. 购物车模块 redis实现 - redis保存用户历史浏览记录 c. 订单模块 订单信息表 - 订单ID - 地址ID - 用户ID - 支付方式 - *总金额 - *总数目 - 运费 - 支付状态 - 创建时间 订单商品表 - ID - sku ID - 商品数量 - 商品价格 健表时须知: - 此时用的是Ubanto的mysql数据库,需要 - grant all on test2.* to 'root'@'1.2.3.4' identified by 'root' - flush privileges - migrate - choices - 富文本编辑器 - tinymce - pip install django-tinymce==2.6.0 - LANGUAGE_CODE = 'zh-hans' - TIME_ZONE = 'Asia/Shanghai' - url(r'^',include('goods.urls',namespace='goods')) - vervose_name - 项目框架搭建 - 四个app - user - goods - cart - order - 使用Abstractuser时,settings里需要 AUTH_USER_MODEL = 'user.User' class BaseModel(models.Model): '''模型类抽象基类’'' create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间') cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间') is_delete = models.BooleanField(default=False,verbose_name='删除标记') class Meta: #说明是一个抽象类模型 abstract = True 开始设计前后端 1. 如何设计四个app 2. register.html - 动态导入静态文件,{% load staticfiles %} link .... href="{% static 'css/reset.css' %}" - 前端post一个勾选按钮(阅读同意),后端收到是 if allow !== 'on': pass - 几乎每一个URL都有namespace,注册成功后跳转首页用 反向解析 return redirect(reverse('goods:index')) - goods 是app域名的别名 - index 是goods里面的别名 - 在mysql里输入 select * from df_user \G 信息会竖排显示 - 数据完整性校验 if not all([username,password,email]): pass - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活, 需要,在创建用户的时候加入如下: user=User.objects.create_user(username,email,password) user.is_active = 0 user.save() - 在注册之前先进性校验,register_handle 判断用户名是否重复, try: #get有的话只返回一个,没有的话会包异常 user=User.objects.get(username=username) except User.DoesNotExist: user = None if user: return ... - 类视图的使用 - 原理关键在dispatch,getattr - from django.view.generic import View class RegisterView(View): def get(self,request): pass def post(self,request): pass url(r'^register$', RegisterView.as_view(), name='register'), # 注册 - 发送激活邮件,包含激活连接 - 激活连接中需包含用户身份信息,并加密 - pip install itsdangerous from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from itsdangerous import SignatureExpired # 加密用户的身份信息,生成激活token serializer = Serializer(settings.SECRET_KEY, 3600) info = {'confirm':user.id} token = serializer.dumps(info) # bytes token = token.decode() #发邮件 1. django本身有秘钥、 subject message sender receiver html_message dend_mail(subject,message,sender,receiver,html_message =html_message) #发送html格式的 - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱 2.celery使用 任务发出者 -- 任务队列(broker)-- 任务处理者(worker) 发出任务 监听任务队列 pip install celery 任务队列是一种跨线程、跨机器工作的一种机制. celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者) 和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给 worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件 from celery import Celery # 创建一个Celery类的实例对象 app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8') # 定义任务函数 @app.task i. def send_register_active_email(to_email, username, token): '''发送激活邮件''' # 组织邮件信息 subject = '天天生鲜欢迎信息' message = '' sender = settings.EMAIL_FROM receiver = [to_email] html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token) send_mail(subject, message, sender, receiver, html_message=html_message) time.sleep(5) ii. # 发邮件 send_register_active_email.delay(email, username, token) iii.# Ubuntu虚拟机启动worker 1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件 一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准 2.vi celery_tasks/tasks.py django环境的初始化 # 在任务处理者一端加这几句 # import os # import django # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings") # django.setup() 3.启动 celery -A celery_tasks.tasks worker -l info - 激活成功后返回登陆页面 class ActiveView(View): '''用户激活''' def get(self, request, token): '''进行用户激活''' # 进行解密,获取要激活的用户信息 serializer = Serializer(settings.SECRET_KEY, 3600) try: info = serializer.loads(token) # 获取待激活用户的id user_id = info['confirm'] # 根据id获取用户信息 user = User.objects.get(id=user_id) user.is_active = 1 user.save() # 跳转到登录页面 return redirect(reverse('user:login')) except SignatureExpired as e: # 激活链接已过期 return HttpResponse('激活链接已过期') 3. login - 因为用户多,不能经常调用数据库,使用redis存储session https://django-redis-chs.readthedocs.io/zh_CN/latest/ - pip install django-redis - django缓存配置 - 指定ip的redis数据库 - 配置session存储 - redis-cli -h 192.169.12.1 - 是否记住用户名 i. class LoginView(View): '''登录''' def get(self, request): '''显示登录页面''' # 判断是否记住了用户名 if 'username' in request.COOKIES: username = request.COOKIES.get('username') checked = 'checked' else: username = '' checked = '' # 使用模板 return render(request, 'login.html', {'username':username, 'checked':checked}) ii. def post(self, request): '''登录校验''' # 接收数据 username = request.POST.get('username') password = request.POST.get('pwd') # 校验数据 if not all([username, password]): return render(request, 'login.html', {'errmsg':'数据不完整'}) # 业务处理:登录校验 user = authenticate(username=username, password=password) if user is not None: # 用户名密码正确 if user.is_active: # 用户已激活 # 记录用户的登录状态 login(request, user) # 跳转到首页 response = redirect(reverse('goods:index')) # HttpResponseRedirect # 判断是否需要记住用户名 remember = request.POST.get('remember') if remember == 'on': # 记住用户名 response.set_cookie('username', username, max_age=7*24*3600) else: response.delete_cookie('username') # 返回response return response else: # 用户未激活 return render(request, 'login.html', {'errmsg':'账户未激活'}) else: # 用户名或密码错误 return render(request, 'login.html', {'errmsg':'用户名或密码错误'}) iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名"> <input type="checkbox" name="remember" {{ checked }}> 4. 用户中心 - base模板的设计,分base.html base_no_cart.html,非常重要 {# 首页 注册 登录 #} <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> {% load staticfiles %} <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> {# 网页标题内容块 #} <title>{% block title %}{% endblock title %}</title> <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> {# 网页顶部引入文件块 #} {% block topfiles %}{% endblock topfiles %} </head> 。。。。。。 - 一个用户中心页面可以点三个页面 (用户/订单/收货地址) <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li> - 登录装饰器(如用户界面需要登录) i. login_required ?next=xxxx from django.contrib.auth.decorators import login_required settings # 配置登录url地址 LOGIN_URL='/user/login' # /accounts/login login.html <div class="form_input"> {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #} ..... url url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页 url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页 url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页 登录视图里的logic处理 if user.is_active: # 用户已激活 # 记录用户的登录状态 login(request, user) # 获取登录后所要跳转到的地址 # 默认跳转到首页 next_url = request.GET.get('next', reverse('goods:index')) # 跳转到next_url response = redirect(next_url) # HttpResponseRedirect # 判断是否需要记住用户名 remember = request.POST.get('remember') if remember == 'on': # 记住用户名 response.set_cookie('username', username, max_age=7*24*3600) else: response.delete_cookie('username') # 返回response return response ii. login_required - 一些经常用的python_package放在utils文件夹里,如mixin.py from django.contrib.auth.decorators import login_required class LoginRequiredMixin(object): @classmethod def as_view(cls, **initkwargs): # 调用父类的as_view view = super(LoginRequiredMixin, cls).as_view(**initkwargs) return login_required(view) - 同时类视图调用 from utils.mixin import LoginRequiredMixin class UserInfoView(LoginRequiredMixin, View):pass class UserOrderView(LoginRequiredMixin, View):pass settings(同上) url (不需要做处理了) url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页 url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页 url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页 登录视图里的logic处理(同上) - 用户登录欢迎信息(head) - 考点 # Django会给request对象添加一个属性request.user # 如果用户未登录->user是AnonymousUser类的一个实例对象 # 如果用户登录->user是User类的一个实例对象 # request.user.is_authenticated() - base.html {% if user.is_authenticated %} <div class="login_btn fl"> 欢迎您:<em>{{ user.username }}</em> <span>|</span> <a href="{% url 'user:logout' %}">退出</a> </div> {% else %} <div class="login_btn fl"> <a href="{% url 'user:login' %}">登录</a> <span>|</span> <a href="{% url 'user:register' %}">注册</a> </div> {% endif %} - logout url url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录 views from django.contrib.auth import authenticate, login, logout class LogoutView(View): '''退出登录''' def get(self, request): '''退出登录''' # 清除用户的session信息 logout(request) # 跳转到首页 return redirect(reverse('goods:index')) - 用户中心地址页(默认地址和新添地址的设计) i. post 新上传地址数据,从数据库里查找是否有默认地址 get 在页面上显示是否有默认地址 class AddressView(LoginRequiredMixin, View): '''用户中心-地址页''' def get(self, request): '''显示''' # 获取登录用户对应User对象 user = request.user # 获取用户的默认收货地址 # try: # address = Address.objects.get(user=user, is_default=True) # models.Manager # except Address.DoesNotExist: # # 不存在默认收货地址 # address = None address = Address.objects.get_default_address(user) # 使用模板 return render(request, 'user_center_site.html', {'page':'address', 'address':address}) def post(self, request): '''地址的添加''' # 接收数据 receiver = request.POST.get('receiver') addr = request.POST.get('addr') zip_code = request.POST.get('zip_code') phone = request.POST.get('phone') # 校验数据 if not all([receiver, addr, phone]): return render(request, 'user_center_site.html', {'errmsg':'数据不完整'}) # 校验手机号 if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone): return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'}) # 业务处理:地址添加 # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址 # 获取登录用户对应User对象 user = request.user # try: # address = Address.objects.get(user=user, is_default=True) # except Address.DoesNotExist: # # 不存在默认收货地址 # address = None address = Address.objects.get_default_address(user) if address: is_default = False else: is_default = True # 添加地址 Address.objects.create(user=user, receiver=receiver, addr=addr, zip_code=zip_code, phone=phone, is_default=is_default) # 返回应答,刷新地址页面 return redirect(reverse('user:address')) # get请求方式 ii. 因为get.post里都用到去models里查询默认数据,可以优化 - 模型管理器类方法封装 每个models里都有models.Manager 1. class AddressManager(models.Manager): '''地址模型管理器类''' # 1.改变原有查询的结果集:all() # 2.封装方法:用户操作模型类对应的数据表(增删改查) def get_default_address(self, user): '''获取用户默认收货地址''' # self.model:获取self对象所在的模型类 try: address = self.get(user=user, is_default=True) # models.Manager except self.model.DoesNotExist: # 不存在默认收货地址 address = None return address 2. class Address(BaseModel): '''地址模型类''' .... # 自定义一个模型管理器对象 objects = AddressManager() .. 3. views调用 address = Address.objects.get_default_address(user) - 用户中心个人信息页历史浏览记录 - 在用户访问详情页面(SKU),需要添加历史浏览记录 - 存在表中要经常增删改查不放方便,所以存redis - redis数据库->内存性的数据库 - 表格设计 1. 所有用户历史记录用一条数据保存 hash history:'user_id':'1,2,3' 2. 一个用户历史记录用一条数据保存 list history_user_id:1,2,3 添加记录时,用户最新浏览的商品id从列表左侧插入 - 实际使用 1. StrictRedis i.# Django的缓存配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://172.16.179.130:6379/9", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } ii.form redis import StricRedis # 获取用户的历史浏览记录 # from redis import StrictRedis # sr = StrictRedis(host='172.16.179.130', port='6379', db=9) history_key = 'history_%d'%user.id # 获取用户最新浏览的5个商品的id sku_ids = con.lrange(history_key, 0, 4) # [2,3,1] ******** # 从数据库中查询用户浏览的商品的具体信息 # goods_li = GoodsSKU.objects.filter(id__in=sku_ids) # 数据库查询时按遍历的方式,只要id in里面,则查询出来 #这样就违背了用户真实历史浏览记录了 i. # goods_res = [] # for a_id in sku_ids: # for goods in goods_li: # if a_id == goods.id: # goods_res.append(goods) # 遍历获取用户浏览的商品信息 ii.goods_li = [] for id in sku_ids: goods = GoodsSKU.objects.get(id=id) goods_li.append(goods) iii.user_info.html 使用{% empty %}标签 ,相当于elseif {% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% empty %} <p>There are no athletes. Only computer programmers.</p> {% endfor %} 2. from django_redis import get_redis_connection con = get_redis_connection('default') 其它同上 5. fastdfs - 概念 - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的 文件服务器集群提供文件上传、下载等服务。 - 架构包括 Tracker server 和 Storage server。 - 客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。 - Tracker server 作用是负载均衡和调度 - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上 - 优势:海量存储、存储容量扩展方便、文件内容重复,结合nginx提高网站提供图片效率 - 项目上传图片和使用图片的过程 (通过admin页面上传图片)浏览器 --请求上传文件----django服务器(修改django默认的上传行为) --->Fastdfs文件存储服务器 随后返回文件/group/文件id--->在django上保存对应的image表 用户请求页面时,显示页面,<img src='127.2.2.2:80'>,浏览器请求nginx获取图片 返回图片内容到浏览器上 ****- 修改django默认文件上传方式, - python.usyiyi.cn - 自定义文件存储类,使得django存储在fastdfs存储器上 class FDFSStorage(Storage): '''fast dfs文件存储类''' def __init__(self, client_conf=None, base_url=None): '''初始化''' if client_conf is None: client_conf = settings.FDFS_CLIENT_CONF self.client_conf = client_conf if base_url is None: base_url = settings.FDFS_URL self.base_url = base_url def _open(self, name, mode='rb'): '''打开文件时使用''' pass def _save(self, name, content): '''保存文件时使用''' # name:你选择上传文件的名字 # content:包含你上传文件内容的File对象 # 创建一个Fdfs_client对象 client = Fdfs_client(self.client_conf) # 上传文件到fast dfs系统中 res = client.upload_by_buffer(content.read()) # dict # { # 'Group name': group_name, # 'Remote file_id': remote_file_id, # 'Status': 'Upload successed.', # 'Local file name': '', # 'Uploaded size': upload_size, # 'Storage IP': storage_ip # } if res.get('Status') != 'Upload successed.': # 上传失败 raise Exception('上传文件到fast dfs失败') # 获取返回的文件ID filename = res.get('Remote file_id') return filename def exists(self, name): '''Django判断文件名是否可用''' return False def url(self, name): '''返回访问文件的url路径''' return self.base_url+name 6. 商品搜索引擎 - 搜索引擎 1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据 - 如 select * from xxx where name like '%草莓%' or desc like '%草莓%' - 很 好吃 的 草莓:sku_id1 sku_id2 sku_id5 字典 2. 全文检索框架 可以帮助用户使用搜索引擎 用户->全文检索框架(haystack)->搜索引擎(whoosh) - 安装即使用 - pip isntall django-haystack pip install whoosh - 在settings里注册haystack并配置 - INSTALLED_APPS = ( 'django.contrib.admin', ... 'tinymce', # 富文本编辑器 'haystack', # 注册全文检索框架 'user', # 用户模块 'goods', # 商品模块 'cart', # 购物车模块 'order', # 订单模块 ) - # 全文检索框架的配置 HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎 # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 索引文件路径 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 当添加、修改、删除数据时,自动生成索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' - 索引文件的生成 - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类 # 定义索引类 from haystack import indexes # 导入你的模型类 from goods.models import GoodsSKU # 指定对于某个类的某些数据建立索引 # 索引类名格式:模型类名+Index class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable): # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中 text = indexes.CharField(document=True, use_template=True) def get_model(self): # 返回你的模型类 return GoodsSKU # 建立索引的数据 def index_queryset(self, using=None): return self.get_model().objects.all() - 在templates下面新建目录search/indexes/goods。 templates search 固定 indexes 固定 goods 模型类所在应用app goodssku_text.txt 模型类名小写_text.txt - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。 # 指定根据表中的哪些字段建立索引数据 {{ object.name }} # 根据商品的名称建立索引 {{ object.desc }} # 根据商品的简介建立索引 {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询 - 使用命令生成索引文件 python manage.py rebuild_index - 会在whoosh_index文件夹里建立xx个商品索引(所有) - 全文检索的使用 - 配置url url(r'^search', include('haystack.urls')), # 全文检索框架 - 表单搜索时设置表单内容如下 base.html get和q固定 <div class="search_con fl"> <form method="get" action="/search"> <input type="text" class="input_text fl" name="q" placeholder="搜索商品"> <input type="submit" class="input_btn fr" name="" value="搜索"> </form> </div> - 全文检索结果 搜索出结果后,haystack会把搜索出的结果传递给templates/search 目录下的search.html,传递的上下文包括: query:搜索关键字 page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象, 对象的属性object才是模型类的对象。 paginator:分页paginator对象 通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。 - search.html分页器的经典使用 <div class="pagenation"> {% if page.has_previous %} <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a> {% endif %} {% for pindex in paginator.page_range %} {% if pindex == page.number %} <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a> {% else %} <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a> {% endif %} {% endfor %} {% if spage.has_next %} <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a> {% endif %} </div> </div> - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索 需要优化,商品搜索、改变分词方式 - 安装jieba分词模块 pip install jieba str = '很不错的草莓' res = jieba.cut(str,cut_all=True) for val in res: print(val) - 。。。。。 - settings里配置需要更改 - 最后重新创建索引数据 python manage.py rebuild_index 7. 订单并发处理 - 悲观锁 - select * from xx where id=2 for update - 应用场景: try: #select * from df_goods_sku where id=sku_id for update; sku=GoodsSKU.object.select_for_update().get(id=sku_id) except: transaction.savepoint_rollback(save_id) return ... - 乐观锁 - 查询数据时不加锁,在更新时进行判断 - 判断更新时的库存和之前查出的库存是否一致 - 操作 - for i in range(3): #update df_goods_sku set tock=stock,sales=new_sales where id= sku_id and stock=origin_stock res=GoodsSKU.object.filter(id=sku_id,stock=stock).update( stock=new_stock,sales=new_sales) if res==0: 库存为0 if i==2: #尝试第3次查询 transaction.savepoint_rollback(save_id) - mysql事务隔离性 事务隔离级别 - Read Uncommitted - Read Committed(大多数数据库默认) - Repeatable Read(mysql默认,产生幻读) - serializable(可串行化,解决幻读问题,但容易引发竞争) - 重新配置mysql.conf为read committed 总结: - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间 - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁 - 判断的时候,更新失败不一定是库存不足,需要再去尝试 8. 首页静态页面优化 - 将动态请求的数据保存为静态的HTML文本,供访问用户下次直接返回。 - celery - 什么时候首页的静态页面需要重新生成? 当管理员后台修改了首页信息对应的表格中的数据的时候,需要重新生成首页静态页 - 配置nginx提交静态页面 - ps aux | grep nginx - admin管理更新数据表时重新生成index静态页面 后台管理员操作(重写类,admin.ModelAdmin)-修改首页中表的数--》celery任务函数 -----》celery服务器生成index.html 用户访问时直接访问aginx的80端口,即index.html - 页面数据的缓存 把页面中使用的数据存储在缓存中,当再次使用这些数据时,先从缓存中获取,若获取 不到,再去数据库查询。减少数据库查询的次数 - 设置缓存 - 先判断缓存中是否有数据,cache.get('xx') - views里设置cache.set(key,zidian,time) - 获取缓存 - 什么时候需要更新首页的缓存数据 当管理员修改首页信息对应的表格中的数据的时候,需要 - 在BaseModelAdmin里清除cache ****- 网站本身性能的优化,减少数据查询的次数,防止恶意的攻击, DDOS功能 nginx在提供静态文件方面效率很高,采用epoll SKU & SPU - SPU - Standard product unittest - 商品信息聚合的最小单位 - 如iphone, - SKU - STOCK keeping unittest - 库存量进出计量单位 - 如纺织品中一个SKU表示: 规格、颜色、款式