django-dailyfresh
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,
- type key
- del key1
- expire key seconds #设置过期时间
- ttl key #查看过期时间
. 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
. 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的属性
. list
- 列表中元素类型为string
- lpush key value1 value2
- lrange key #start stop 返回列表里指定范围的元素
- lrange key - #查询整列元素
- rpush key value1 value2
- linsert key before/after b
- b 现有元素
- 插入元素
- lset key index value #设置指定元素的值
- lrem key count value
- count > 从左向右移除
- count < 从右向左移除
- count = 移除所有
. set
- 元素为string类型
- 无序集合
- sadd key zhangsan lisi wangwu
- smembers key
- srem key wangwu . 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. open django工程,改setting配置redis SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT =
SESSION_REDIS_DB =
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session' 通过redis-cli客户端查看
最后在Base64在线解码 主从配置实现读写分离
一个master可以拥有多个slave,一个slave可以有多个slave
- 实现读写分离
- 备份主服务、防止主服务挂掉后数据丢失
bind 192.168.26.128
slaveof 192.168.26.128
port redis集群
集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
主服务、从服务
集群:
- 软件层面
- 只有一台电脑,在这一台电脑上启动了多个redis服务。
- 硬件层面
- 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。 集群和python交互:
pip install redis-by-cluster
from rediscluster import *
if __name == 'main':
try:
startup_nodes = [
{'host':'192..','port':''},...]
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:})
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 != 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'],) if __name__ == '__main__':
unittest.main() cmd
python test.python class LoginTest(unittest.TestCase):
def setup(self):
#相当于 __init__
self.client = app.test_client()
#开启测试模式,获取错误信息
app.config['TESTING'] = True
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 -b 127.0.0.1: --access-logfile ./logs/og main:app if self.server_version_info < (,,):
cursor.execute(”SELECT @@tx_isolation")
else:
cursor.execute("SELECT @@transaction_isolation") --------------- 测试----------------- jekins
- Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
jmeter
- 软件做压力测试
postman
- 创建天天生鲜 B2C 大型网站 . 电商概念
B2B Alibaba
C2C 瓜子二手车、淘宝、易趣
B2C 唯品会
C2B 尚品宅配
O2O 美团
F2C 戴尔 . 开发流程
产品原型的设计 --- 产品经理-----axure 非常关键:
- 架构设计
- 数据库设计 . 数据库分析
mysql
-
redis
- 若用户多,session服务器
- 对于经常访问的如首页,则用缓存服务器
xxx
-异步任务处理celery (注册页面发邮件之类的) 分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
- . 数据库设计: 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.
- 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 开始设计前后端
. 如何设计四个app
. 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 =
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, )
info = {'confirm':user.id}
token = serializer.dumps(info) # bytes
token = token.decode()
#发邮件
. django本身有秘钥、
subject
message
sender
receiver
html_message
dend_mail(subject,message,sender,receiver,html_message
=html_message) #发送html格式的
- django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
.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() ii. # 发邮件
send_register_active_email.delay(email, username, token) iii.# Ubuntu虚拟机启动worker
.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
.vi celery_tasks/tasks.py
django环境的初始化
# 在任务处理者一端加这几句
# import os
# import django
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
# django.setup()
.启动
celery -A celery_tasks.tasks worker -l info
- 激活成功后返回登陆页面
class ActiveView(View):
'''用户激活'''
def get(self, request, token):
'''进行用户激活'''
# 进行解密,获取要激活的用户信息
serializer = Serializer(settings.SECRET_KEY, )
try:
info = serializer.loads(token)
# 获取待激活用户的id
user_id = info['confirm'] # 根据id获取用户信息
user = User.objects.get(id=user_id)
user.is_active =
user.save() # 跳转到登录页面
return redirect(reverse('user:login'))
except SignatureExpired as e:
# 激活链接已过期
return HttpResponse('激活链接已过期') . 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=**)
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 }}>
. 用户中心
- 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=**)
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 . class AddressManager(models.Manager):
'''地址模型管理器类'''
# .改变原有查询的结果集:all()
# .封装方法:用户操作模型类对应的数据表(增删改查)
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
. class Address(BaseModel):
'''地址模型类'''
....
# 自定义一个模型管理器对象
objects = AddressManager()
..
. views调用
address = Address.objects.get_default_address(user) - 用户中心个人信息页历史浏览记录
- 在用户访问详情页面(SKU),需要添加历史浏览记录
- 存在表中要经常增删改查不放方便,所以存redis
- redis数据库->内存性的数据库
- 表格设计
. 所有用户历史记录用一条数据保存
hash
history:'user_id':'1,2,3'
. 一个用户历史记录用一条数据保存
list
history_user_id:,,
添加记录时,用户最新浏览的商品id从列表左侧插入
- 实际使用
. 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='', db=)
history_key = 'history_%d'%user.id # 获取用户最新浏览的5个商品的id
sku_ids = con.lrange(history_key, , ) # [,,] ******** # 从数据库中查询用户浏览的商品的具体信息
# 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 %} . from django_redis import get_redis_connection
con = get_redis_connection('default')
其它同上 . fastdfs
- 概念
- 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的
文件服务器集群提供文件上传、下载等服务。
- 架构包括 Tracker server 和 Storage server。
- 客户端请求 Tracker server 进行文件上传、下载,通过
Tracker server 调度最终由 Storage server 完成文件上传和下载。
- Tracker server 作用是负载均衡和调度
- Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上
- 优势:海量存储、存储容量扩展方便、文件内容重复 . 商品搜索引擎
- 搜索引擎
. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
- 如 select * from xxx where name like '%草莓%' or desc like
'%草莓%'
- 很
好吃
的
草莓:sku_id1 sku_id2 sku_id5
字典
. 全文检索框架
可以帮助用户使用搜索引擎
用户->全文检索框架(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 . 订单并发处理
- 悲观锁
- select * from xx where id= 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():
#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
if i==: #尝试第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 != 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'],) 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
回顾:
.谈谈你对django和flask的认识。 .flask和django最大的不同点:request/session .flask知识点
- 模板+静态文件,app= Flask(__name__,....)
- 路由
@app.route('/index',methods=["GET"])
- 请求
request.form
request.args
request.method
- 响应
""
render
redirect
- session
session['xx'] =
session.get('xx')
. 路飞总共有几个项目
- 管理后台
- 导师后台
- 主站 . 路飞主站业务
- 课程
- 课程列表
- 课程详细
- 大纲、导师、推荐课程
- 价格策略
- 章节和课时
- 常见问题
- 深科技
- 文章列表
- 文章详细
- 收藏
- 评论
- 点赞
- 支付
- 购物车()
- 结算中心()
- 立即支付()
知识点:
- redis
- 支付宝
- 消息推送
- 构建数据结构
- 优惠券+贝里+支付宝
- 个人中心
- 课程中心 . 播放视频:CC视频
- 加密
- 非加密 今日内容:
. 配置文件
. 路由系统
. 视图
. 请求相关
. 响应
. 模板渲染
. session
. 闪现
. 中间件
. 蓝图(blueprint)
. 特殊装饰器 内容详细:
知识点:
- 给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。 settings.py
class Foo:
DEBUG = True
TEST = True xx.py
import importlib path = "settings.Foo" p,c = path.rsplit('.',maxsplit=)
m = importlib.import_module(p)
cls = getattr(m,c) # 如果找到这个类?
for key in dir(cls):
if key.isupper():
print(key,getattr(cls,key))
. 配置文件 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
. 路由系统
- endpoint,反向生成URL,默认函数名
- url_for('endpoint') / url_for("index",nid=)
- 动态路由:
@app.route('/index/<int:nid>',methods=['GET','POST'])
def index(nid):
print(nid)
return "Index" . FBV . 请求相关
# 请求相关信息
# 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)) . 响应:
响应体:
return “asdf”
return jsonify({'k1':'v1'})
return render_template('xxx.html')
return redirect() 定制响应头:
obj = make_response("asdf")
obj.headers['xxxxxxx'] = ''
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') . 模板渲染
- 基本数据类型:可以执行python语法,如:dict.get() list['xx']
- 传入函数
- django,自动执行
- flask,不自动执行
- 全局定义函数
@app.template_global()
def sb(a1, a2):
# {{sb(,)}}
return a1 + a2 @app.template_filter()
def db(a1, a2, a3):
# {{ |db(,) }}
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.}} {% 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") . session
当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。
视图函数:
@app.route('/ses')
def ses():
session['k1'] =
session['k2'] =
del session['k1'] return "Session" session['xxx'] =
session['xxx'] 当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。 . 闪现,在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" . 中间件
- 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() . 特殊装饰器 . before_request . 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() . before_first_request from flask import Flask
app = Flask(__name__) @app.before_first_request
def x1():
print('') @app.route('/index')
def index():
print('index')
return "Index" @app.route('/order')
def order():
print('order')
return "order" if __name__ == '__main__': app.run() . template_global . template_filter . errorhandler
@app.errorhandler()
def not_found(arg):
print(arg)
return "没找到" 总结:
- 配置文件
- 路由
- 视图:FBV
- 请求
- 响应
obj = make_response("adfasdf")
obj.headers['x'] = asdfasdf
return obj
- 模板
- session
- flash
- 中间件
- 特殊装饰器 . 上下文
- 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
处理请求时,用于临时存储的对象,每次
请求都会重设这个变量 . 请求钩子
- before_first_request
在第一次请求处理之前先被执行
- before request
在每次请求之前都被执行
- after_request
在每次请求之后执行前提是视图无异常
- teaddown_request
在每次请求之后都被执行 . 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
.sqlalchemy
- 其它框架都能用的关系型数据库
- pip install flask-sqlalchemy
flask-sqlacchemy
.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(), 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(), unique=True)
email = db.Column(db.String(), unique=True)
password = db.Column(db.String())
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='', role_id=role1.id)
us2 = User(name='zhang', email='zhang@189.com', password='', role_id=role2.id)
us3 = User(name='chen', email='chen@126.com', password='', role_id=role2.id)
us4 = User(name='zhou', email='zhou@163.com', password='', role_id=role1.id) # 一次保存多条数据
db.session.add_all([us1, us2, us3, us4])
db.session.commit() - 查询数据
- 查询多条、查询一条
Role.query.all()
Out[]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In []: li = Role.query.all() In []: li
Out[]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In []: r = li[] In []: type(r)
Out[]: db_demo.Role In []: r.name
Out[]: u'admin' In []: Role.query.first()
Out[]: <db_demo.Role at 0x10388d190> In []: r = Role.query.first() In []: r.name
Out[]: u'admin' # 根据主键id获取对象
In []: r = Role.query.get() In []: r
Out[]: <db_demo.Role at 0x10388d310> In []: r.name
Out[]: u'stuff' In []: # 另一种查询方式
In []: db.session.query(Role).all()
Out[]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>] In []: db.session.query(Role).get()
Out[]: <db_demo.Role at 0x10388d310> In []: db.session.query(Role).first()
Out[]: <db_demo.Role at 0x10388d190> In []: In []: User.query.filter_by(name="wang")
Out[]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0> In []: User.query.filter_by(name="wang").all()
Out[]: [<db_demo.User at 0x1038c87d0>] In []: User.query.filter_by(name="wang").first()
Out[]: <db_demo.User at 0x1038c87d0> In []: user = User.query.filter_by(name="wang").first() In []: user.name
Out[]: u'wang' In []: user.email
Out[]: u'wang@163.com' In []: User.query.filter_by(name="wang", role_id=).first()
Out[]: <db_demo.User at 0x1038c87d0> In []: User.query.filter_by(name="wang", role_id=).first() In []: user = User.query.filter_by(name="wang", role_id=).first() In []: type(user)
Out[]: NoneType In []: In []: user = User.query.filter(User.name=="wang", User.role_id==).first
...: () In []: user
Out[]: <db_demo.User at 0x1038c87d0> In []: user.name
Out[]: u'wang' In []: from sqlalchemy import or_ In []: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com")
...: )).all()
Out[]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>] In []: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163.
...: com"))).all() In []: li[].name
Out[]: u'wang' In []: li[].name
Out[]: u'zhou' In []: # offset偏移 跳过几条
In []: User.query.offset().all()
Out[]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>] In []: li = User.query.offset().all() In []: li[].name
Out[]: u'chen' In []: li[].name
Out[]: u'zhou' In []: In []: li = User.query.offset().limit().all() In []: li
Out[]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>] In []: li[].name
Out[]: u'zhang' In []: li[].name
Out[]: u'chen' In []: In []: User.query.order_by("-id").all()
Out[]:
[<db_demo.User at 0x1038ef310>,
<db_demo.User at 0x1038c0950>,
<db_demo.User at 0x1038fd990>,
<db_demo.User at 0x1038c87d0>] In []: In []: li = User.query.order_by(User.id.desc()).all() In []: li
Out[]:
[<db_demo.User at 0x1038ef310>,
<db_demo.User at 0x1038c0950>,
<db_demo.User at 0x1038fd990>,
<db_demo.User at 0x1038c87d0>] In []: li[].name
Out[]: u'zhou' In []: li[].name
Out[]: u'wang' In []: In []: from sqlalchemy import func In []: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
...: d)
Out[]: <flask_sqlalchemy.BaseQuery at 0x103a38050> In []: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
...: d).all()
Out[]: [(1L, 2L), (2L, 2L)] In []: - 跨表查询数据
- 关联查询
- 定义显示信息
def __repr__(self):
return "User object: name=%s" % self.name In []: ro = Role.query.get() In []: type(ro)
Out[]: db_demo.Role In []: ro.users
Out[]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>] In []: ro.users[].name
Out[]: u'wang' In []: ro.users[].name
Out[]: u'zhou' In []: In []: user
Out[]: <db_demo.User at 0x1038c87d0> In []: user.role_id
Out[]: 1L In []: Role.query.get(user.role_id)
Out[]: <db_demo.Role at 0x10388d190> In []: user.role
Out[]: <db_demo.Role at 0x10388d190> In []: user.role.name
Out[]: u'admin' In []: - 数据的修改与删除
# 更新
In []: User.query.filter_by(name="zhou").update({"name": "python", "emai
...: l": "python@itast.cn"})
Out[]: 1L In []: db.session.commit() In []:
# 删除
In []: user = User.query.get() In []: db.session.delete(user) In []: db.session.commit() In []: .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,
- type key
- del key1
- expire key seconds #设置过期时间
- ttl key #查看过期时间
. 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
. 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的属性
. list
- 列表中元素类型为string
- lpush key value1 value2
- lrange key #start stop 返回列表里指定范围的元素
- lrange key - #查询整列元素
- rpush key value1 value2
- linsert key before/after b
- b 现有元素
- 插入元素
- lset key index value #设置指定元素的值
- lrem key count value
- count > 从左向右移除
- count < 从右向左移除
- count = 移除所有
. set
- 元素为string类型
- 无序集合
- sadd key zhangsan lisi wangwu
- smembers key
- srem key wangwu . 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. open django工程,改setting配置redis SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT =
SESSION_REDIS_DB =
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session' 通过redis-cli客户端查看
最后在Base64在线解码 主从配置实现读写分离
一个master可以拥有多个slave,一个slave可以有多个slave
- 实现读写分离
- 备份主服务、防止主服务挂掉后数据丢失
bind 192.168.26.128
slaveof 192.168.26.128
port redis集群
集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
主服务、从服务
集群:
- 软件层面
- 只有一台电脑,在这一台电脑上启动了多个redis服务。
- 硬件层面
- 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。 集群和python交互:
pip install redis-by-cluster
from rediscluster import *
if __name == 'main':
try:
startup_nodes = [
{'host':'192..','port':''},...]
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:})
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 != 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'],) if __name__ == '__main__':
unittest.main() cmd
python test.python class LoginTest(unittest.TestCase):
def setup(self):
#相当于 __init__
self.client = app.test_client()
#开启测试模式,获取错误信息
app.config['TESTING'] = True
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 -b 127.0.0.1: --access-logfile ./logs/og main:app if self.server_version_info < (,,):
cursor.execute(”SELECT @@tx_isolation")
else:
cursor.execute("SELECT @@transaction_isolation") --------------- 测试----------------- jekins
- Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
jmeter
- 软件做压力测试
postman
- 创建天天生鲜 B2C 大型网站 . 电商概念
B2B Alibaba
C2C 瓜子二手车、淘宝、易趣
B2C 唯品会
C2B 尚品宅配
O2O 美团
F2C 戴尔 . 开发流程
产品原型的设计 --- 产品经理-----axure 非常关键:
- 架构设计
- 数据库设计 . 数据库分析
mysql
-
redis
- 若用户多,session服务器
- 对于经常访问的如首页,则用缓存服务器
xxx
-异步任务处理celery (注册页面发邮件之类的) 分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
- . 数据库设计: 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.
- 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 开始设计前后端
. 如何设计四个app
. 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 =
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, )
info = {'confirm':user.id}
token = serializer.dumps(info) # bytes
token = token.decode()
#发邮件
. django本身有秘钥、
subject
message
sender
receiver
html_message
dend_mail(subject,message,sender,receiver,html_message
=html_message) #发送html格式的
- django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
.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() ii. # 发邮件
send_register_active_email.delay(email, username, token) iii.# Ubuntu虚拟机启动worker
.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
.vi celery_tasks/tasks.py
django环境的初始化
# 在任务处理者一端加这几句
# import os
# import django
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
# django.setup()
.启动
celery -A celery_tasks.tasks worker -l info
- 激活成功后返回登陆页面
class ActiveView(View):
'''用户激活'''
def get(self, request, token):
'''进行用户激活'''
# 进行解密,获取要激活的用户信息
serializer = Serializer(settings.SECRET_KEY, )
try:
info = serializer.loads(token)
# 获取待激活用户的id
user_id = info['confirm'] # 根据id获取用户信息
user = User.objects.get(id=user_id)
user.is_active =
user.save() # 跳转到登录页面
return redirect(reverse('user:login'))
except SignatureExpired as e:
# 激活链接已过期
return HttpResponse('激活链接已过期') . 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=**)
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 }}>
. 用户中心
- 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=**)
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 . class AddressManager(models.Manager):
'''地址模型管理器类'''
# .改变原有查询的结果集:all()
# .封装方法:用户操作模型类对应的数据表(增删改查)
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
. class Address(BaseModel):
'''地址模型类'''
....
# 自定义一个模型管理器对象
objects = AddressManager()
..
. views调用
address = Address.objects.get_default_address(user) - 用户中心个人信息页历史浏览记录
- 在用户访问详情页面(SKU),需要添加历史浏览记录
- 存在表中要经常增删改查不放方便,所以存redis
- redis数据库->内存性的数据库
- 表格设计
. 所有用户历史记录用一条数据保存
hash
history:'user_id':'1,2,3'
. 一个用户历史记录用一条数据保存
list
history_user_id:,,
添加记录时,用户最新浏览的商品id从列表左侧插入
- 实际使用
. 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='', db=)
history_key = 'history_%d'%user.id # 获取用户最新浏览的5个商品的id
sku_ids = con.lrange(history_key, , ) # [,,] ******** # 从数据库中查询用户浏览的商品的具体信息
# 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 %} . from django_redis import get_redis_connection
con = get_redis_connection('default')
其它同上 . 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 . 商品搜索引擎
- 搜索引擎
. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
- 如 select * from xxx where name like '%草莓%' or desc like
'%草莓%'
- 很
好吃
的
草莓:sku_id1 sku_id2 sku_id5
字典
. 全文检索框架
可以帮助用户使用搜索引擎
用户->全文检索框架(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 . 订单并发处理
- 悲观锁
- select * from xx where id= 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():
#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
if i==: #尝试第3次查询
transaction.savepoint_rollback(save_id)
- mysql事务隔离性
事务隔离级别
- Read Uncommitted
- Read Committed(大多数数据库默认)
- Repeatable Read(mysql默认,产生幻读)
- serializable(可串行化,解决幻读问题,但容易引发竞争)
- 重新配置mysql.conf为read committed
总结:
- 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间
- 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁
- 判断的时候,更新失败不一定是库存不足,需要再去尝试 . 首页静态页面优化
- 将动态请求的数据保存为静态的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表示:
规格、颜色、款式
django-dailyfresh的更多相关文章
- Django【部署】uwsgi+nginx
uwsgi 遵循wsgi协议的web服务器 uwsgi的安装 pip install uwsgi uwsgi的配置 项目部署时,需要把settings.py文件夹下的: DEBUG = FALSE A ...
- python之Django实现商城从0到1
dailyfresh-B2Cdailyfresh mall based on B2C model 基于B2C的天天生鲜商城 项目托管地址:https://github.com/Ylisen/daily ...
- Django项目之Web端电商网站的实战开发(二)
说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 接着上一篇博客继续往下写 :https://blog.csdn.net/qq_41782425/article/details/8 ...
- Django项目之Web端电商网站的实战开发(一)
说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 目录 一丶项目介绍 二丶电商项目开发流程 三丶项目需求 四丶项目架构概览 五丶项目数据库设计 六丶项目框架搭建 一丶项目介绍 产品 ...
- Django开发学习BUG记录--RemovedInDjango19Warning:Model class apps.user.models.User doesn't declare an explicit app_label
报错信息: /home/python/PycharmProjects/dailyfresh/apps/user/models.py:8: RemovedInDjango19Warning: Model ...
- celery开启worker报错django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE o
其实挺简单的问题,但花了自己一个下午来解决,先是浏览各种博客,无果:没办法,然后去看celery官方文档,无果,近乎绝望,最后仔细看代码,找到问题所在(如下),自学狗这效率...... 下面是自己ta ...
- 异步任务队列Celery在Django中的使用
前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务.在同事的指引下接触了Celery这个异步任务队 ...
- 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...
- django server之间通过remote user 相互调用
首先,场景是这样的:存在两个django web应用,并且两个应用存在一定的联系.某些情况下彼此需要获取对方的数据. 但是我们的应用肯经都会有对应的鉴权机制.不会让人家随随便便就访问的对吧.好比上车要 ...
- Mysql事务探索及其在Django中的实践(二)
继上一篇<Mysql事务探索及其在Django中的实践(一)>交代完问题的背景和Mysql事务基础后,这一篇主要想介绍一下事务在Django中的使用以及实际应用给我们带来的效率提升. 首先 ...
随机推荐
- Echarts dataZoom缩放功能参数详解:
dataZoom=[ //区域缩放 { id: 'dataZoomX', show:true, //是否显示 组件.如果设置为 false,不会显示,但是数据过滤的功能还存在. backgroundC ...
- io重定向打开关闭 Eclipse中c开发printf无法输出解决办法
if(freopen("e:\\lstm-comparec\\lstm\\lstm\\output.txt","a",stdout)==NULL)fprintf ...
- JavaScript创建对象(三)——原型模式
在JavaScript创建对象(二)——构造函数模式中提到,构造函数模式存在相同功能的函数定义多次的问题.本篇文章就来讨论一下该问题的解决方案——原型模式. 首先我们来看下什么是原型.我们在创建一个函 ...
- Python之路-python基础一
本章内容: 一.编程语言分类 二.python之变量介绍 三.python交互命令(input,print,getpass) 四.Python之循环控制 ...
- python学习 面向对象高级编程
---恢复内容开始--- 面向对象编程---oop,是一种编程思想,oop把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数 ...
- bzoj3879
题解: 后缀数组 然后把读入的内容去重,按照rank排序 然后用单调栈处理一下 代码: #include<bits/stdc++.h> using namespace std; typed ...
- java -jar 使用要点
1.在将进程设为脱离终端运行时,输出流不能输出到当前窗口.否则,退出终端后,进程会pause.pause是停滞,是僵尸进程. 2.包含资源文件的war.jar文件是无法独立运行的.需要解压到临时目录. ...
- vue.set动态新增对象属性,触发dom渲染
当我们给一个props或者data中被观测的对象添加一个新的属性的时候,不能直接添加,必须使用Vue.set方法 /** * ==== 选择产品 ==== * 因为vue实现双向数据绑定的机制是数据劫 ...
- relativeURL 相对URL的坑
我正在尝试实现一个使用RestKit的iOS应用程序.在我迄今为止看到的所有示例中,以下代码用于创建URL: NSURL *baseURL = [NSURL URLWithString:@" ...
- 怎么搜索sci论文。
进入清华大学图书馆,选择常用数据库,找到 Web of Science平台(SCI/SSCI/AHCI.ISTP/ISSHP.DII.JCR.BP.CCC.CCR/IC.ESI.INSPEC…)即可. ...