多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python-100-Days 电商网站技术要点全解析:从商业模式、OAuth 登录到秒杀防超卖与 ElasticSearch 全文检索

Python-100-Days 电商网站技术要点全解析:从商业模式、OAuth 登录到秒杀防超卖与 ElasticSearch 全文检索 文档教程【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址https://gitcode.com/GitHub_Trending/py/Python-100-Days点击查看免费下载本篇文章基于 Python-100-Days 项目 Day91-100/97.电商网站技术要点剖析.md 展开系统梳理电商网站从商业模式设计、物理模型规划到第三方登录、缓存、购物车、支付、秒杀防超卖、静态资源管理与全文检索的完整技术栈。读者学完本篇能够掌握一套可落地的电商系统架构方案并理解 OAuth 2.0、Redis 缓存预热、乐观锁防超卖、Haystack ElasticSearch 全文检索等关键技术的实战用法。商业模式电商平台的六种典型形态电商网站的商业模式直接决定了系统的交易主体关系与业务规则。常见的六种模式如下模式含义典型例子B2B商家对商家交易双方都是企业商家阿里巴巴C2C个人对个人淘宝、人人车B2C商家对个人唯品会、聚美优品C2B先有消费者提出需求后有商家按需求组织生产尚品宅配O2O线上到线下将线下商务机会与互联网结合让互联网成为线下交易平台美团外卖、饿了么B2B2C商家对商家对个人天猫、京东不同模式在订单流转、结算、供应链协作上的要求差异显著例如 B2B2C 模式中平台既要服务入驻商家B又要服务终端消费者C因此在订单分账、售后流转上需要更精细的模型设计这一点在后续物理模型设计与订单持久化环节会体现出来。需求要点用户端与管理端的功能全景一个电商网站的需求通常分为用户端与管理端两大块用户端首页商品分类、广告轮播、滚动快讯、瀑布加载、推荐、折扣、热销等用户登录含第三方登录、注册、注销、自服务个人信息、浏览历史、收货地址等商品分类、列表、详情、搜索、热门搜索、搜索历史、添加到购物车、收藏、关注、评论等购物车查看、编辑修改数量、删除商品、清空订单提交订单支付、历史订单、订单详情、订单评价等。管理端核心业务实体的 CRUD定时任务周期性和非周期性如处理未支付订单、采集数据对异常事件报警等报表功能导入导出 Excel、PDF 等以及前端 ECharts 统计图表展示权限控制RBAC、白名单、黑名单等业务流转如发起退款流程常用流程引擎有 Activity、Airflow、Spiff 等三方服务接入地图、短信、物流、支付、实名认证、天气、监控、云存储等。其中处理未支付订单这类定时任务正是 46.Django快速上手 之后课程中异步任务与定时任务Celery的典型应用场景而权限控制RBAC在仓库的 91.团队项目开发的问题和解决方案.md 中也有对应的 RBAC 权限模型图解rbac-full.png可供对照学习。物理模型设计SPU 与 SKU进行数据库物理模型设计前必须搞清楚两个核心概念SPUStandard Product Unit标准化产品单元是商品信息聚合的最小单位。例如iPhone 6sSKUStock Keeping Unit库存量单位是物理上不可分割的最小存货单元也是库存、价格、下单操作的最小粒度。例如iPhone 6s 64G 土豪金。简单理解一个 SPU 可以对应多个 SKU不同颜色、容量、版本的组合购物车与订单中真正参与加减库存的是 SKU。仓库中给出的电商物理模型图示如下该图展示了用户、地址、商品、SPU/SKU、购物车、订单、支付、售后等核心实体的关联关系是搭建电商数据库时的直接参考蓝图。第三方登录OAuth 2.0 授权流程第三方登录是指利用第三方网站通常是知名社交网站的账号进行登录验证主要是通过知名第三方网站获取用户相关信息如国内的 QQ、微博国外的 Google、Facebook 等。第三方登录大部分使用OAuth 协议——一个关于授权的开放网络标准数据的所有者告诉系统同意授权第三方应用进入系统获取这些数据系统因此产生一个短期的进入令牌用来代替密码供第三方应用使用。目前通常使用的是 2.0 版本。令牌与密码的三大区别时效性令牌是短期的到期自动失效用户自己无法修改密码一般长期有效用户不修改就不会变化。可撤销性令牌可以被数据所有者撤销并立即失效密码一般不允许被他人撤销。权限范围令牌有权限范围scope例如只能进小区的二号门只读令牌比读写令牌更安全密码一般是完整权限。正是通过令牌第三方应用既能获得权限又随时可控、不危及系统安全这就是 OAuth 协议的优势。OAuth 2.0 授权流程标准授权流程包含四个角色资源所有者、客户端、认证服务器、资源服务器与六个步骤用户打开客户端后客户端要求用户资源所有者给予授权用户资源所有者同意给予客户端授权客户端使用上一步获得的授权向认证服务器申请访问令牌认证服务器对客户端进行认证后发放访问令牌客户端使用访问令牌向资源服务器申请获取资源资源服务器确认访问令牌无误同意向客户端开放资源。国内平台接入要点微博登录接入参考微博开放平台的微博登录接入文档QQ 登录接入需要首先注册成为 QQ 互联开发者并通过审核参考 QQ 互联的接入指南与网站开发流程。接入三方登录的平台能力在仓库 57.接入三方平台.md 中有系统讲解——该文档指出实名认证、在线支付等无法自行完成的业务均应借助微信、支付宝、银联等三方平台而第三方登录本质上也是这一类三方能力接入的具体形态。实战中通常电商网站在使用第三方登录时会要求与网站账号进行绑定或者根据获取到的第三方账号信息如手机号自动完成账号绑定。从仓库 54.RESTful架构和DRF入门.md 对会话管理的讨论可知一旦接入第三方登录并发放自己的登录凭证若采用基于 session 的方案就需要考虑水平扩展时的 session 同步问题更推荐的是基于 token 的用户跟踪方案两者可与 OAuth 令牌体系形成呼应。缓存预热与查询缓存Web 应用性能瓶颈通常出现在关系型数据库上使用缓存是空间换时间的经典优化手段仓库 56.使用缓存.md 中有专门章节。电商系统中有两类典型的缓存实践缓存预热与查询缓存。缓存预热所谓缓存预热是指在启动服务器时就把数据提前加载到缓存中。为此可以在 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(host1.2.3.4, port3306, userroot, passwordpass, databasedb, charsetutf8, cursorclasspymysql.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中注册应用INSTALLED_APPS [ ... common.apps.CommonConfig, ... ]这样应用启动时省级行政区数据就被加载进缓存后续用户访问首页地址选择器时不再直连数据库。仓库 56.使用缓存.md 展示了更灵活的编程式缓存写法——通过get_redis_connection直接操作 Redis并特别提醒get_redis_connection能执行FLUSHDB、SHUTDOWN等危险操作商业项目中通常应对其再做一层封装只暴露项目需要的缓存操作方法同时借此实现Read Through / Write Through缓存更新模式。查询缓存自定义装饰器实现查询结果的缓存代码如下from pickle import dumps, loads from django.core.cache import caches MODEL_CACHE_KEY project:modelcache:%s def my_model_cache(key, sectiondefault, timeoutNone): 实现模型缓存的装饰器 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), timeouttimeout) return data return wrapper2 return wrapper1使用方式my_model_cache(keyprovinces) def get_all_provinces(): return list(Province.objects.all())该装饰器的关键设计缓存键由业务前缀project:modelcache:%s、业务 key 与参数序列化共同构成保证不同参数调用互不干扰命中时用pickle.loads反序列化返回未命中时执行原函数并用pickle.dumps序列化后写入缓存。需要注意缓存击穿key 恰好过期时大量并发直接压垮数据库是此类缓存方案必须面对的风险56.使用缓存.md 给出的解法是使用 Redis 的setnx实现互斥锁只有加锁成功的请求去数据库加载并写回缓存其余请求短暂休眠后重试。购物车实现登录态与非登录态的存储方案购物车实现首先要回答两个问题。问题一已登录/未登录用户的购物车放在哪里核心思路已登录用户的购物车放在数据库中可先在 Redis 中缓存未登录用户的购物车保存在 Cookie、localStorage 或 sessionStorage 中减少服务器端内存开销。购物车领域模型示例class CartItem(object): 购物车中的商品项 def __init__(self, sku, amount1, selectedFalse): 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未登录用户购物车保存在 Cookie 中的数据形态{ 1001: {sku: {...}, amount: 1, selected: True}, 1002: {sku: {...}, amount: 2, selected: False}, 1003: {sku: {...}, amount: 3, selected: True}, }用 Django 的签名 Cookie 读写购物车request.get_signed_cookie(cart) cart_base64 base64.base64encode(pickle.dumps(cart)) response.set_signed_cookie(cart, cart_base64)签名 Cookie 可以有效防止用户篡改购物车数据伪造价格、数量等同时省去了服务端会话存储的开销。问题二用户登录之后如何合并购物车目前电商应用的购物车几乎都做了持久化处理主要是方便在多个终端之间共享数据。因此登录后需要把 Cookie/localStorage 中的临时购物车与数据库或 Redis 缓存中的持久化购物车按 SKU 进行合并同一 SKU 数量累加不同 SKU 直接并入合并完成后清空本地临时购物车。集成支付功能支付宝接入实战接入支付首先面对两个问题支付信息如何持久化必须保证每笔交易都有记录如何接入支付宝接入其他平台基本类似关于交易必须有记录仓库 order_sys.sql 提供了完整的参考 DDLtransaction交易表记录交易单号、交易用户、交易金额、支付类型余额/微信/支付宝、支付来源wx/app/web/wap与支付状态-1 取消、0 未完成、1 已完成、-2 异常order订单表记录订单状态0 未付款、1 已付款、2 已发货、3 已签收、-1 退货申请等、out_trade_no订单支付单号与escrow_trade_no第三方支付流水号order_returns_apply与order_returns覆盖售后退款场景。这套表结构印证了每笔交易都有记录、支付单号与第三方流水号对应留存的持久化要求。支付宝接入步骤在蚂蚁金服开放平台注册入驻在开发者中心创建应用获取应用 IDAPPID配置密钥生成应用私钥与支付宝公钥参考文档中心与 SDK 集成说明alipay-sdk-python按 API 列表对接具体接口。配置支付参数ALIPAY_APPID ...... ALIPAY_URL https://openapi.alipaydev.com/gateway.do ALIPAY_DEBUG False发起支付生成支付页面链接# 创建调用支付宝的对象 alipay AliPay( # 在线创建应用时分配的ID appidsettings.ALIPAY_APPID, app_notify_urlNone, # 自己应用的私钥 app_private_key_pathos.path.join( os.path.dirname(os.path.abspath(__file__)), keys/app_private_key.pem), # 支付宝的公钥 alipay_public_key_pathos.path.join( os.path.dirname(os.path.abspath(__file__)), keys/alipay_public_key.pem), sign_typeRSA2, debugsettings.ALIPAY_DEBUG ) # 调用获取支付页面操作 order_info alipay.api_alipay_trade_page_pay( out_trade_no..., total_amount..., subject..., return_urlhttp://... ) # 生成完整的支付页面URL alipay_url settings.ALIPAY_URL ? order_info return JsonResponse({alipay_url: alipay_url})支付结果回跳验证支付完成后会自动跳转回代码中设定的return_url该页面可以获得订单号out_trade_no、支付流水号trade_no、交易金额total_amount和对应的签名sign随后请求后端验证并保存交易结果# 创建调用支付宝的对象 alipay AliPay( # 在线创建应用时分配的ID appidsettings.ALIPAY_APPID, app_notify_urlNone, # 自己应用的私钥 app_private_key_pathos.path.join( os.path.dirname(os.path.abspath(__file__)), keys/app_private_key.pem), # 支付宝的公钥 alipay_public_key_pathos.path.join( os.path.dirname(os.path.abspath(__file__)), keys/alipay_public_key.pem), sign_typeRSA2, debugsettings.ALIPAY_DEBUG ) # 请求参数假设是POST请求中包括订单号、支付流水号、交易金额和签名 params request.POST.dict() # 调用验证操作 if alipay.verify(params, params.pop(sign)): # 对交易进行持久化操作注意sign_typeRSA2是当前支付宝主推的签名算法SHA256WithRSA密钥使用 RSA2 格式生成。支付宝支付 API 还提供交易查询、交易结算、退款、退款查询等一系列接口可按业务需要调用。这一接入思路与 57.接入三方平台.md 中介绍的SDK 接入方式一致——安装三方库使用其封装好的类与方法调用平台服务。秒杀与超卖高并发下的库存控制秒杀流量控制与性能优化秒杀意味着在极短时间内处理极高并发系统需承受平时百倍以上的流量。其核心思路是流量控制和性能优化需要从前端到后台各环节配合前端通过 JavaScript 实现倒计时、避免重复提交、限制频繁刷新物理架构使用缓存读多写少把库存放在 Redis 中利用DECR原语实现减库存同时利用 Redis 限流原理与限制频繁发送手机验证码一致和消息队列核心作用是削峰和上下游节点解耦合服务设计采用无状态服务设计便于水平扩展通过增加设备为系统扩容。超卖现象及三种解决思路超卖现象某商品库存为 1用户 1 和用户 2 并发购买。用户 1 提交订单后库存被修改为 0而用户 2 在不知情的情况下也提交订单库存再次被修改为 -1这就是超卖。三种常见解决思路悲观锁控制查询商品数量时就用select ... for update对数据加锁。用户 1 查询库存时用户 2 因无法读取库存被阻塞直到用户 1 提交或回滚后才能继续。缺点是对并发访问量很高的商品性能太差实际开发中可在库存小于某个阈值时才加锁但总体不太可取。乐观锁控制查询库存不加锁更新库存时设定商品数量必须与之前查询数量相同才能更新否则说明其他事务已更新库存必须重新发起请求。尝试减库存将查询select与更新update合并为一条 SQL更新库存时在where筛选条件中加上库存 购买数量或库存 - 购买数量 0的条件。这种方式要求事务隔离级别为读提交read committed。三种方案从加锁阻塞到版本校验再到原子化条件更新性能与并发能力逐步提升第三种本质上已经是乐观锁的 SQL 合并形态适合秒杀等高并发场景。静态资源管理云存储优先静态资源管理可自建文件服务器或分布式文件服务器如 FastDFS但一般项目没有必要这样做且效果未必最好。更推荐使用云存储服务管理网站静态资源亚马逊 S3、阿里云 OSS、七牛、LeanCloud、Bmob 等国内外云服务商都提供优质云存储服务且价格可接受具体操作参考各官方文档。云存储通常附带 CDN 加速用于加快静态资源访问这点在 57.接入三方平台.md 中也有论述除非资源涉及个人或商业隐私否则托管到云存储是性价比更高的选择。全文检索从方案选型到 ElasticSearch 实战方案选择数据库模糊查询效率低每次需全表扫描不支持分词数据库全文检索MySQL 5.6 以前只适用于 MyISAM 引擎检索与 DML 操作耦合在数据库中数据量达百万级时性能显著下降开源搜索引擎索引数据与原始数据分离可使用 ElasticSearch 或 Solr 提供外置索引服务若不考虑高并发需求纯 Python 的 Whoosh 也可考虑。ElasticSearch 概述ElasticSearch 既是分布式文档数据库又是高可扩展的开源全文搜索和分析引擎允许存储、搜索、分析大量数据且这个过程是近实时的。维基百科、Stack Overflow、GitHub 等知名网站均使用了 ElasticSearch。ElasticSearch 底层是开源搜索引擎 Lucene但直接使用 Lucene 非常麻烦需自己编写代码调用其接口且只支持 Java。ElasticSearch 对 Lucene 做了全面封装提供 REST 风格 API通过 HTTP 协议屏蔽了编程语言差异。ElasticSearch 会为数据构建倒排索引但其内置分词器对中文分词的支持几乎为零因此需要安装elasticsearch-analysis-ik插件提供中文分词服务。Django 项目中常用的全文检索方案组合haystackdjango-haystack / drf-haystack whoosh Jiebahaystackdjango-haystack / drf-haystack elasticsearchrequests elasticsearchdjango-elasticsearch-dsl安装和使用 ElasticSearch使用 Docker 安装docker pull elasticsearch:7.6.0 docker run -d -p 9200:9200 -p 9300:9300 -e discovery.typesingle-node -e ES_JAVA_OPTS-Xms512m -Xmx512m --name es elasticsearch:7.6.0说明-e参数指定了单机模式和 JVM 最小/最大可用堆空间大小堆空间大小根据服务器实际可提供内存决定默认 2G。创建索引库PUT - http://1.2.3.4:9200/demo/响应{acknowledged: true, shards_acknowledged: true, index: demo}。查看索引库GET - http://1.2.3.4:9200/demo/响应中包含别名、mappings、settings分片数 5、副本数 1、创建时间、uuid 等。插入数据POST - http://1.2.3.4:9200/demo/goods/1/Content-Type: application/jsonbody 为商品的 no、title、brand、name、product、resolution、intro 字段响应包含_index/_type/_id/_version及result: created。删除数据DELETE - http://1.2.3.4:9200/demo/goods/1/响应result: deleted。更新数据PUT - http://1.2.3.4:9200/demo/goods/1/_updatebody 用{doc: {...}}包裹要更新的字段响应result: updated_version递增。查询数据GET - http://1.2.3.4:9200/demo/goods/1/响应中found: true且_source为最新文档。配置中文分词和拼音插件进入 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退出容器并重启docker restart es测试中文分词POST - http://1.2.3.4:9200/_analyze参数{analyzer: ik_smart, text: 中国男足在2022年卡塔尔世界杯预选赛中勇夺小组最后一名}返回按词切分的 tokens如中国男足2022年卡塔尔世界杯预选赛等词性标注为 CN_WORD/CN_CHAR/TYPE_CQUAN。测试拼音分词同样调用_analyze参数{analyzer: pinyin, text: 张学友}返回zhang、zxy、xue、you等 token——其中zxy是全拼首字母缩写这正是拼音搜索输入首字母即可命中的实现基础。全文检索功能可通过 GET 或 POST 请求搜索例如搜索包含未来关键词的商品GET - http://120.77.222.217:9200/demo/goods/_search?q未来注意URL 中的中文要处理成百分号编码。响应中hits.total为命中文档数hits.hits[]按_score相关性降序返回匹配文档及其_source。GET 方式 URL 中可用的搜索参数参数说明q查询字符串analyzer分析查询字符串使用的分词器analyze_wildcard通配符或前缀查询是否被分析默认为 falsedefault_operator多个条件之间的关系默认为 OR可修改为 ANDexplain在返回结果中包含评分机制的解释fields只返回索引中指定的列多个列用逗号隔开sort排序参考字段可用 :asc 和 :desc 指定升降序timeout超时时间from匹配结果的开始值默认为 0size匹配结果的条数默认为 10POST 搜索则基于DSLDomain Specific Language例如{query: {term: {type: }}}适用于更复杂的组合查询。Django 对接 ElasticSearchPython 对接 ElasticSearch 的第三方库是 HaystackDjango 项目使用 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(documentTrue, use_templateTrue) def get_model(self): return Goods def index_queryset(self, usingNone): return self.get_model().objects.all()text字段的索引模板需放在templates/search/indexes/demo/goods_text.txt{{object.title}} {{object.intro}}配置 URLurlpatterns [ # ... url(search/, include(haystack.urls)), ]生成初始索引python manage.py rebuild_index配合HAYSTACK_SIGNAL_PROCESSOR haystack.signals.RealtimeSignalProcessor模型数据增删改时会自动维护索引实现近实时的全文检索与关键词高亮。结语电商网站是软件工程各领域技术的集大成者从商业模式决定业务模型到 SPU/SKU 物理模型设计从 OAuth 2.0 第三方登录到 Redis 缓存预热、查询缓存从购物车双端存储与登录合并到支付宝 SDK 接入与交易持久化从秒杀流量控制、三种防超卖方案到云存储静态资源托管与基于 ElasticSearch 的全文检索。本文所涉代码均可直接落地到 Django 项目配合仓库中的 order_sys.sql、56.使用缓存.md、57.接入三方平台.md 等资料可以进一步构建完整、可运行的电商后端系统。赞分享文档教程【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址https://gitcode.com/GitHub_Trending/py/Python-100-Days点击查看免费下载相关推荐Predis电商系统秒杀场景下的库存控制与防超卖方案Predis电商系统秒杀场景下的库存控制与防超卖方案 引言秒杀场景的技术挑战 在电商平台的日常运营中秒杀活动是提升用户活跃度和销售额的重要手段。然而秒杀数据库后端秒杀系统零库存超卖Redisson分布式锁的电商解决方案秒杀系统零库存超卖Redisson分布式锁的电商解决方案 在电商平台的秒杀活动中零库存超卖是最令人头疼的问题之一。当大量用户同时抢购有限商品时传统的本地锁后端缓存数据库客户端分布式FreeCAD完整指南如何用开源软件实现专业级3D设计与仿真FreeCAD完整指南如何用开源软件实现专业级3D设计与仿真 FreeCAD是一款功能强大的开源3D参数化建模软件专为机械设计、建筑设计和产品开发而打造。作桌面应用3D建模图形学工业制造创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表