同步操作将从 Admin/Python-100-Days 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
首页(商品分类、广告轮播、滚动快讯、瀑布加载、推荐、折扣、热销、……)
用户(登录(第三方登录)、注册、注销、自服务(个人信息、浏览历史、收货地址、……))
商品(分类、列表、详情、搜索、热门搜索、搜索历史、添加到购物车、收藏、关注、评论、……)
购物车(查看、编辑(修改数量、删除商品、清空))
订单(提交订单(支付)、历史订单、订单详情、订单评价、……)
首先要搞清楚两个概念:SPU(Standard Product Unit)和SKU(Stock Keeping Unit)。
第三方登录是指利用第三方网站(通常是知名社交网站)的账号进行登录验证(主要是通过知名第三方网站获取到用户相关信息),比如国内的 QQ、微博,国外的Google、Facebook等。第三方登录大部分都是使用OAuth协议,它是一个关于授权的开放网络标准(数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌,用来代替密码,供第三方应用使用),得到了广泛的应用,目前通常使用的是2.0版本。关于OAuth的基础知识,可以阅读阮一峰老师的《理解OAuth 2.0》。关于令牌和密码的区别,我们可以简单总结出以下三点差异:
所以,通过令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是OAuth协议的优势。
如果使用微博登录进行接入,其具体步骤可以参考微博开放平台上的“微博登录接入”文档。使用QQ登录进行接入,需要首先注册成为QQ互联开发者并通过审核,具体的步骤可以参考QQ互联上的“接入指南”,具体的步骤可以参考“网站开发流程”。
提示:在Gitbook上面有一本名为《Django博客入门》的书以Github为例介绍了第三方账号登录,有兴趣的可以自行阅读。
通常电商网站在使用第三方登录时,会要求与网站账号进行绑定或者根据获取到的第三方账号信息(如:手机号)自动完成账号绑定。
所谓缓存预热,是指在启动服务器时将数据提前加载到缓存中,为此可以在Django应用的apps.py
模块中编写AppConfig
的子类并重写ready()
方法,代码如下所示。
import pymysql
from django.apps import AppConfig
from django.core.cache import cache
SELECT_PROVINCE_SQL = 'select distid, name from tb_district where pid is null'
class CommonConfig(AppConfig):
name = 'common'
def ready(self):
conn = pymysql.connect(host='1.2.3.4', port=3306,
user='root', password='pass',
database='db', charset='utf8',
cursorclass=pymysql.cursors.DictCursor)
try:
with conn.cursor() as cursor:
cursor.execute(SELECT_PROVINCE_SQL)
provinces = cursor.fetchall()
cache.set('provinces', provinces)
finally:
conn.close()
接下来,还需要在应用的__init__.py
中编写下面的代码。
default_app_config = 'common.apps.CommonConfig'
或者在项目的settings.py
文件中注册应用。
INSTALLED_APPS = [
...
'common.apps.CommonConfig',
...
]
自定义装饰器实现查询结果的缓存。
from pickle import dumps, loads
from django.core.cache import caches
MODEL_CACHE_KEY = 'project:modelcache:%s'
def my_model_cache(key, section='default', timeout=None):
"""实现模型缓存的装饰器"""
def wrapper1(func):
def wrapper2(*args, **kwargs):
real_key = '%s:%s' % (MODEL_CACHE_KEY % key, ':'.join(map(str, args)))
serialized_data = caches[section].get(real_key)
if serialized_data:
data = loads(serialized_data)
else:
data = func(*args, **kwargs)
cache.set(real_key, dumps(data), timeout=timeout)
return data
return wrapper2
return wrapper1
@my_model_cache(key='provinces')
def get_all_provinces():
return list(Province.objects.all())
问题一:已登录用户的购物车放在哪里?未登录用户的购物车放在哪里?
class CartItem(object):
"""购物车中的商品项"""
def __init__(self, sku, amount=1, selected=False):
self.sku = sku
self.amount = amount
self.selected = selected
@property
def total(self):
return self.sku.price * self.amount
class ShoppingCart(object):
"""购物车"""
def __init__(self):
self.items = {}
self.index = 0
def add_item(self, item):
if item.sku.id in self.items:
self.items[item.sku.id].amount += item.amount
else:
self.items[item.sku.id] = item
def remove_item(self, sku_id):
if sku_id in self.items:
self.items.remove(sku_id)
def clear_all_items(self):
self.items.clear()
@property
def cart_items(self):
return self.items.values()
@property
def cart_total(self):
total = 0
for item in self.items.values():
total += item.total
return total
已登录用户的购物车可以放在数据库中(可以先在Redis中缓存);未登录用户的购物车可以保存在Cookie、localStorage或sessionStorage中(减少服务器端内存开销)。
{
'1001': {sku: {...}, 'amount': 1, 'selected': True},
'1002': {sku: {...}, 'amount': 2, 'selected': False},
'1003': {sku: {...}, 'amount': 3, 'selected': True},
}
request.get_signed_cookie('cart')
cart_base64 = base64.base64encode(pickle.dumps(cart))
response.set_signed_cookie('cart', cart_base64)
问题二:用户登录之后,如何合并购物车?(目前电商应用的购物车几乎都做了持久化处理,主要是方便在多个终端之间共享数据)
问题一:支付信息如何持久化?(必须保证每笔交易都有记录)
问题二:如何接入支付宝?(接入其他平台基本类似)
配置文件:
ALIPAY_APPID = '......'
ALIPAY_URL = 'https://openapi.alipaydev.com/gateway.do'
ALIPAY_DEBUG = False
获得支付链接(发起支付):
# 创建调用支付宝的对象
alipay = AliPay(
# 在线创建应用时分配的ID
appid=settings.ALIPAY_APPID,
app_notify_url=None,
# 自己应用的私钥
app_private_key_path=os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'keys/app_private_key.pem'),
# 支付宝的公钥
alipay_public_key_path=os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'keys/alipay_public_key.pem'),
sign_type='RSA2',
debug=settings.ALIPAY_DEBUG
)
# 调用获取支付页面操作
order_info = alipay.api_alipay_trade_page_pay(
out_trade_no='...',
total_amount='...',
subject='...',
return_url='http://...'
)
# 生成完整的支付页面URL
alipay_url = settings.ALIPAY_URL + '?' + order_info
return JsonResponse({'alipay_url': alipay_url})
通过上面返回的链接可以进入支付页面,支付完成后会自动跳转回上面代码中设定好的项目页面,在该页面中可以获得订单号(out_trade_no)、支付流水号(trade_no)、交易金额(total_amount)和对应的签名(sign)并请求后端验证和保存交易结果,代码如下所示:
# 创建调用支付宝的对象
alipay = AliPay(
# 在线创建应用时分配的ID
appid=settings.ALIPAY_APPID,
app_notify_url=None,
# 自己应用的私钥
app_private_key_path=os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'keys/app_private_key.pem'),
# 支付宝的公钥
alipay_public_key_path=os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'keys/alipay_public_key.pem'),
sign_type='RSA2',
debug=settings.ALIPAY_DEBUG
)
# 请求参数(假设是POST请求)中包括订单号、支付流水号、交易金额和签名
params = request.POST.dict()
# 调用验证操作
if alipay.verify(params, params.pop('sign')):
# 对交易进行持久化操作
支付宝的支付API还提供了交易查询、交易结算、退款、退款查询等一系列的接口,可以根据业务需要进行调用,此处不再进行赘述。
select ... for update
对数据加锁,这样的话用户1查询库存时,用户2因无法读取库存数量被阻塞,直到用户1提交或者回滚了更新库存的操作后才能继续,从而解决了超卖问题。但是这种做法对并发访问量很高的商品来说性能太过糟糕,实际开发中可以在库存小于某个值时才考虑加锁,但是总的来说这种做法不太可取。select
)和更新(update
)操作合并为一条SQL操作,更新库存的时候,在where
筛选条件中加上库存>=购买数量
或库存-购买数量>=0
的条件,这种做法要求事务隔离级别为读提交(read committed)。提示:有兴趣的可以自己在知乎上看看关于这类问题的讨论。
静态资源的管理可以自己架设文件服务器或者分布式文件服务器(FastDFS),但是一般的项目中没有必要这样做而且效果未必是最好的,我们建议使用云存储服务来管理网站的静态资源,国内外的云服务提供商如亚马逊、阿里云、七牛、LeanCloud、Bmob等都提供了非常优质的云存储服务,而且价格也是一般公司可以接受的,具体的操作可以参考官方文档,例如:阿里云的对象存储 OSS开发人员指南。
ElasticSearch既是一个分布式文档数据库又是一个高可扩展的开源全文搜索和分析引擎,它允许存储、搜索和分析大量的数据,并且这个过程是近实时的。它通常被用作底层引擎和技术,为复杂的搜索功能和要求提供动力,大家熟知的维基百科、Stack-Overflow、Github都使用了ElasticSearch。
ElasticSearch的底层是开源搜索引擎Lucene,但是直接用Lucene会非常麻烦,必须自己编写代码去调用它的接口而且只支持Java语言。ElasticSearch相当于对Lucene进行了一次全面的封装,提供了REST风格的API接口,通过基于HTTP协议的访问方式屏蔽了编程语言的差异。ElasticSearch会为数据构建倒排索引,但是ElasticSearch内置的分词器对中文分词的支持几乎为零,因此需要通过安装elasticsearch-analysis-ik插件来提供中文分词服务。
ElasticSearch的安装和配置可以参考《ElasticSearch之Docker安装》。除了ElasticSearch之外,也可以使用Solr、Whoosh等来提供搜索引擎服务,基本上Django项目中可以考虑如下几种方案:
####安装和使用ElasticSearch
使用Docker安装ElasticSearch。
docker pull elasticsearch:7.6.0
docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name es elasticsearch:7.6.0
说明:上面创建容器时通过
-e
参数指定了使用单机模式和Java虚拟机最小最大可用堆空间的大小,堆空间大小可以根据服务器实际能够提供给ElasticSearch的内存大小来决定,默认为2G。
创建数据库。
请求:PUT - http://1.2.3.4:9200/demo/
响应:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "demo"
}
查看创建的数据库。
请求:GET - http://1.2.3.4:9200/demo/
响应:
{
"demo": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1552213970199",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "ny3rCn10SAmCsqW6xPP1gw",
"version": {
"created": "6050399"
},
"provided_name": "demo"
}
}
}
}
插入数据。
请求:POST - http://1.2.3.4:9200/demo/goods/1/
请求头:Content-Type: application/json
参数:
{
"no": "5089253",
"title": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
"brand": "Apple",
"name": "Apple iPhone X",
"product": "中国大陆",
"resolution": "2436 x 1125",
"intro": "一直以来,Apple都心存一个设想,期待能够打造出这样一部iPhone:它有整面屏幕,能让你在使用时,完全沉浸其中,仿佛忘了它的存在。它是如此智能,哪怕轻轻一瞥,都能得到它心有灵犀的回应。而这个设想,终于随着iPhone X的到来成为了现实。现在,就跟未来见个面吧。"
}
响应:
{
"_index": "demo",
"_type": "goods",
"_id": "1",
"_version": 4,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
删除数据。
请求:DELETE - http://1.2.3.4:9200/demo/goods/1/
响应:
{
"_index": "demo",
"_type": "goods",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
更新数据。
请求:PUT - http://1.2.3.4:9200/demo/goods/1/_update
请求头:Content-Type: application/json
参数:
{
"doc": {
"no": "5089253",
"title": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
"brand": "Apple(苹果)",
"name": "Apple iPhone X",
"product": "美国",
"resolution": "2436 x 1125",
"intro": "一直以来,Apple都心存一个设想,期待能够打造出这样一部iPhone:它有整面屏幕,能让你在使用时,完全沉浸其中,仿佛忘了它的存在。它是如此智能,哪怕轻轻一瞥,都能得到它心有灵犀的回应。而这个设想,终于随着iPhone X的到来成为了现实。现在,就跟未来见个面吧。"
}
}
响应:
{
"_index": "demo",
"_type": "goods",
"_id": "1",
"_version": 10,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 9,
"_primary_term": 1
}
查询数据。
请求:GET - http://1.2.3.4:9200/demo/goods/1/
响应:
{
"_index": "demo",
"_type": "goods",
"_id": "1",
"_version": 10,
"found": true,
"_source": {
"doc": {
"no": "5089253",
"title": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
"brand": "Apple(苹果)",
"name": "Apple iPhone X",
"product": "美国",
"resolution": "2436 x 1125",
"intro": "一直以来,Apple都心存一个设想,期待能够打造出这样一部iPhone:它有整面屏幕,能让你在使用时,完全沉浸其中,仿佛忘了它的存在。它是如此智能,哪怕轻轻一瞥,都能得到它心有灵犀的回应。而这个设想,终于随着iPhone X的到来成为了现实。现在,就跟未来见个面吧。"
}
}
}
进入Docker容器的plugins目录。
docker exec -it es /bin/bash
下载和ElasticSearch版本对应的ik和pinyin插件。
yum install -y wget
cd plugins/
mkdir ik
cd ik
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
unzip elasticsearch-analysis-ik-7.6.0.zip
rm -f elasticsearch-analysis-ik-7.6.0.zip
cd ..
mkdir pinyin
cd pinyin
wget https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v7.6.0/elasticsearch-analysis-pinyin-7.6.0.zip
unzip elasticsearch-analysis-pinyin-7.6.0.zip
rm -f elasticsearch-analysis-pinyin-7.6.0.zip
退出容器,重启ElasticSearch。
docker restart es
测试中文分词效果。
请求:POST - http://1.2.3.4:9200/_analyze
请求头:Content-Type: application/json
参数:
{
"analyzer": "ik_smart",
"text": "中国男足在2022年卡塔尔世界杯预选赛中勇夺小组最后一名"
}
响应:
{
"tokens": [
{
"token": "中国",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "男足",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "在",
"start_offset": 4,
"end_offset": 5,
"type": "CN_CHAR",
"position": 2
},
{
"token": "2022年",
"start_offset": 5,
"end_offset": 10,
"type": "TYPE_CQUAN",
"position": 3
},
{
"token": "卡塔尔",
"start_offset": 10,
"end_offset": 13,
"type": "CN_WORD",
"position": 4
},
{
"token": "世界杯",
"start_offset": 13,
"end_offset": 16,
"type": "CN_WORD",
"position": 5
},
{
"token": "预选赛",
"start_offset": 16,
"end_offset": 19,
"type": "CN_WORD",
"position": 6
},
{
"token": "中",
"start_offset": 19,
"end_offset": 20,
"type": "CN_CHAR",
"position": 7
},
{
"token": "勇夺",
"start_offset": 20,
"end_offset": 22,
"type": "CN_WORD",
"position": 8
},
{
"token": "小组",
"start_offset": 22,
"end_offset": 24,
"type": "CN_WORD",
"position": 9
},
{
"token": "最后",
"start_offset": 24,
"end_offset": 26,
"type": "CN_WORD",
"position": 10
},
{
"token": "一名",
"start_offset": 26,
"end_offset": 28,
"type": "CN_WORD",
"position": 11
}
]
}
测试拼音分词效果。
请求:POST - http://1.2.3.4:9200/_analyze
请求头:Content-Type: application/json
参数:
{
"analyzer": "pinyin",
"text": "张学友"
}
响应:
{
"tokens": [
{
"token": "zhang",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "zxy",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "xue",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 1
},
{
"token": "you",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 2
}
]
}
可以通过GET或者POST请求进行搜索,下面演示了搜索有“未来”关键词商品。
GET - http://120.77.222.217:9200/demo/goods/_search?q=未来
注意:URL中的中文应该要处理成百分号编码。
{
"took": 19,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.73975396,
"hits": [
{
"_index": "demo",
"_type": "goods",
"_id": "1",
"_score": 0.73975396,
"_source": {
"doc": {
"no": "5089253",
"title": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
"brand": "Apple(苹果)",
"name": "Apple iPhone X",
"product": "美国",
"resolution": "2436*1125",
"intro": "一直以来,Apple都心存一个设想,期待能够打造出这样一部iPhone:它有整面屏幕,能让你在使用时,完全沉浸其中,仿佛忘了它的存在。它是如此智能,哪怕轻轻一瞥,都能得到它心有灵犀的回应。而这个设想,终于随着iPhone X的到来成为了现实。现在,就跟未来见个面吧。"
}
}
},
{
"_index": "demo",
"_type": "goods",
"_id": "3",
"_score": 0.68324494,
"_source": {
"no": "42417956432",
"title": "小米9 透明尊享版 手机 透明尊享 全网通(12GB + 256GB)",
"brand": "小米(MI)",
"name": "小米(MI)小米9透明",
"product": "中国大陆",
"resolution": "2340*1080",
"intro": "全面透明机身,独特科幻机甲风,来自未来的设计。"
}
}
]
}
}
URL中可用的搜索参数如下表所示:
参数 | 说明 |
---|---|
q | 查询字符串 |
analyzer | 分析查询字符串使用的分词器 |
analyze_wildcard | 通配符或者前缀查询是否被分析,默认为false |
default_operator | 多个条件之间的关系,默认为OR,可以修改为AND |
explain | 在返回的结果中包含评分机制的解释 |
fields | 只返回索引中指定的列,多个列中间用逗号隔开 |
sort | 排序参考的字段,可以用:asc和:desc来指定升序和降序 |
timeout | 超时时间 |
from | 匹配结果的开始值,默认为0 |
size | 匹配结果的条数,默认为10 |
POST - http://120.77.222.217:9200/demo/goods/_search
请求头:Content-Type: application/json
参数:
{
"query": {
"term": {
"type": ""
}
}
}
POST搜索是基于DSL的。
Python对接ElasticSearch的第三方库是HayStack,在Django项目中可以使用django-haystack,通过HayStack可以在不修改代码对接多种搜索引擎服务。
pip install django-haystack elasticsearch
配置文件:
INSTALLED_APPS = [
...
'haystack',
...
]
HAYSTACK_CONNECTIONS = {
'default': {
# 引擎配置
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
# 搜索引擎服务的URL
'URL': 'http://1.2.3.4:9200',
# 索引库的名称
'INDEX_NAME': 'goods',
},
}
# 添加/删除/更新数据时自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
索引类:
from haystack import indexes
class GoodsIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Goods
def index_queryset(self, using=None):
return self.get_model().objects.all()
编辑text字段的模板(需要放在templates/search/indexes/demo/goods_text.txt):
{{object.title}}
{{object.intro}}
配置URL:
urlpatterns = [
# ...
url('search/', include('haystack.urls')),
]
生成初始索引:
python manage.py rebuild_index
说明:可以参考《Django Haystack 全文检索与关键词高亮》一文来更深入的了解基于Haystack的全文检索操作。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。