ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Django与Flask双框架实现电商网站:架构设计与实战解析

Django与Flask双框架实现电商网站:架构设计与实战解析 1. 项目背景与核心问题拆解拿到“django-flask基于python的电子商务网站的设计与实现”这个标题我第一反应是这不单是一个“用Python做个商城”的练手项目更是一个关于Web框架选型对比、系统架构演进的典型实战案例。标题里同时出现Django和Flask说明设计者希望在一套业务需求下让两个流派的技术栈各司其职或者是在同一项目中对比验证两种框架的工程化边界。电商网站的业务核心从来不是“页面好不好看”而是围绕商品、库存、订单、支付、用户这几条主线的状态流转。从工程角度看这个项目最少要拆出如下问题域用户体系注册、登录、会话保持、权限区分普通用户/管理员商品体系分类、SPU/SKU、库存、上下架、检索排序交易体系购物车、订单生成、库存扣减、支付回调管理后台商品管理、订单管理、数据统计标题选用了“django-flask”这种双框架组合我在多次带项目时见过两类极端做法一是Django一把梭后台管理系统写得飞快但API层总被ORM拖累二是Flask全程微服务化开发初期很爽到订单状态机时开始满地补丁。这次的设计思路我认为可以走一条更务实的路线用Django做管理后台和核心数据模型层用Flask做面向C端的高并发API网关两者共享同一套MySQL数据库和Redis缓存层。这样不仅贴合标题的“双框架”字眼也让每个框架用到它最擅长的场景。适合阅读这篇内容的人我建议是两类刚完成Python基础语法、想通过完整项目建立Web开发全局观的学习者以及工作中需要维护电商系统、想理解双框架混合架构取舍的开发者。前者可以看到一个项目的完整生命周期后者可以直接拿走架构设计中的核心思路和踩坑清单。2. 技术选型背后Django和Flask的边界究竟在哪2.1 Django的优势区域约定俗成的高效管理面Django最被人称道的是“自带电池”的全家桶模式。在这个电商项目里Django的Admin后台几乎不用额外开发就能完成运营人员90%的日常操作。用户管理Django自带的User模型和Permission体系可以快速实现角色划分。结合Django Admin自带的用户管理界面运营人员可以直接冻结违规账号、重置密码、查看登录日志。商品数据管理通过Django的ModelAdmin自定义list_display、search_fields、list_filter一个商品列表页在十分钟内就能拥有搜索、分页、筛选、批量上下架功能。ORM事务管理电商的核心是订单和库存Django的transaction.atomic()装饰器能够保证订单创建和库存扣减的一致性这是Flask原生不具备的易用性。我在这个项目中为Django分配的角色是基础设施与运营中心。所有数据库表结构、模型关系、迁移脚本都在Django侧定义管理后台也直接基于Django Admin做二次开发通过重写save_model和delete_model钩子实现商品删除前的库存校验和订单取消时的库存回补。2.2 Flask的用武之地灵活的C端API层Flask的优势在于轻量、灵活、路由可控。它的上下文管理机制让开发者能够精确控制每次请求的生命周期这在面对高并发的商品浏览和购物车操作时非常有利。API响应结构Flask配合自定义装饰器可以统一封装返回格式为{code: 0, data: {...}, msg: success}对前端来说契约清晰。蓝图Blueprint隔离按业务模块拆分为user_bp、cart_bp、order_bp、product_bp每个蓝图独立挂载到Flask应用上模块之间不互相污染。与Django的数据共享Flask并不直接操作Django的ORM而是通过SQLAlchemy连接同一个MySQL库读取已经由Django定义好的表结构。两者之间通过Redis做缓存和会话层面的协同。2.3 为什么不让一个框架干完所有事有人会问Django的DRFDjango REST Framework也能写APIFlask加扩展也能做后台何必搞两套我的回答是看团队和场景。开发效率Django Admin 3天就能交付运营后台Flask写API天然比DRF更轻序列化性能更可控。混搭后两边的开发都能在各自熟悉的地带高速推进。部署隔离Django服务跑在管理端口Flask服务跑在API端口两者可以独立水平扩展。大促前只需要扩Flask实例数管理后台不受请求量波动影响。代码边界清晰数据模型归Django业务API归Flask管理规则不缠绕。后续如果要做微服务拆分Flask层可以直接抽离成独立的商品服务、订单服务。这套架构的注意力要放在“模型统一、接口灵活”这句话上。双框架最难处理的不是写代码而是约束各自团队不越界。提示本项目不做微服务拆分只做“双进程、共享库”的模块级架构。所有Flask进程通过环境变量感知Django数据库配置避免硬编码连接串导致的上线事故。3. 系统架构与数据模型先把地基打牢3.1 整体部署拓扑项目在服务器上的进程布置如下Nginx负载均衡监听80/443端口按路径转发。/api/*转发到Flask服务/admin/*和/static/*转发到Django服务。Django服务默认使用uWSGI启动运行在内部端口3031。承载Admin后台、迁移命令、定时任务。Flask服务采用Gunicorn多worker启动运行在端口5000。承载用户端所有JSON接口。MySQL 8.0主库存储全部业务数据开启binlog便于后续扩容。Redis 7缓存商品热门信息、用户登录态Django session key和Flask token双重验证、分布式锁。3.2 数据库表设计细节数据表是整个项目里最不能返工的部分。我按电商的经典模型设计实际开发时表结构略有简化但核心关系不能乱。表名主要字段说明usersid, username, password_hash, role, statusrole区分管理员和普通用户categoriesid, name, parent_id, level支持二级分类productsid, category_id, title, price, stock, sales, is_on_sale商品主表skusid, product_id, specs, price_diff, stock规格维度库存和差价cartsid, user_id, sku_id, quantity, checked购物车项ordersid, order_no, user_id, total_amount, status, address_id订单主表order_itemsid, order_id, sku_id, product_title, price, quantity订单快照paymentsid, order_id, pay_method, transaction_id, amount, status支付流水这里有几个务实的细节订单快照字段order_items里必须冗余商品标题和下单价格不能通过外键实时去查商品表。因为商品价格随时会变订单要守住“成交时那一刻的价格”。这个设计在很多初学者项目中常被忽略。库存扣减方案下单时先扣“可售库存”用户取消或超时未支付再回补。表里除了stock字段我额外用一个locked_stock字段记录被锁定未支付的量。实际可下单量是stock - locked_stock。订单号生成不用数据库自增ID做订单号而是用时间戳 用户ID后四位 随机数拼成唯一订单号方便财务对账时从订单号反查用户和时间段。3.3 Redis缓存与数据一致性商城首页、商品分类页是访问热点。我的做法是缓存Key设计product:detail:{sku_id}缓存SKU详情category:{category_id}:products缓存分类下的商品ID列表。失效策略为主动删除加过期时间双保险。缓存穿透防护查询商品时如果Redis里没有且MySQL也没有就把空值也缓存30秒避免恶意请求直接打穿数据库。缓存与DB最终一致Django Admin后台修改商品库存或价格时操作完数据库后主动删除相关缓存Key。由于Flask只读缓存最多存在几秒的短暂不一致对电商场景完全可以接受。4. 核心功能模块的实操实现4.1 用户注册与登录的双层会话机制用户的登录态我是这样设计的Flask在用户验证密码通过后生成一个token基于itsdangerous签名的用户ID和过期时间同时把token写入Redis设置7天有效期。前端每次请求在Authorization: Bearer {token}头里带上它Flask侧用装饰器解析并缓存用户基础信息。管理后台登录则走Django自带认证流程。为了让两套体系不冲突在users表增加一个role字段Django Admin的登录只允许is_staffTrue的用户进入而Flask的注册接口只创建roleuser的普通账户。在密码安全上Django侧直接用make_password生成哈希Flask侧用标准库的hashlib加盐后存储。两边的校验方案不同但不影响业务层因为同一个用户不会同时走两套认证系统。# Flask 登录接口核心逻辑示意 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer def generate_token(user_id, expires604800): s Serializer(app.config[SECRET_KEY], expires_inexpires) token s.dumps({uid: user_id}).decode(utf-8) redis_client.setex(fuser:token:{user_id}, expires, token) return token注意Redis里存token的目的是踢人下线。如果用户在个人中心点击“退出所有设备”直接删除该用户的token键所有端都会失效。只靠JWT签名无法做到服务端主动失效。4.2 商品分页与动态筛选接口商品列表是Flask侧被调用最频繁的接口。我使用了“首屏Redis缓存商品ID列表动态查MySQL分页字段”的混合方案。第一次请求时从MySQL查出符合筛选条件的全部ID存入Redis的Set数据结构并返回第一页数据。后续翻页时直接从Redis的Set里按offset截取ID再批量查MySQL补全商品名称、价格、缩略图等展示字段。排序字段按销量、按价格、按上架时间通过不同的Sorted Set键分别存储避免SQL反复执行。# Flask 商品列表接口核心逻辑示意 def get_product_list(category_id, page, size, sort_key): cache_key fcategory:{category_id}:sort:{sort_key} if redis.exists(cache_key): start (page - 1) * size end start size - 1 ids redis.zrange(cache_key, start, end) else: ids, total query_ids_from_mysql(category_id, sort_key) redis.zadd(cache_key, {pid: idx for idx, pid in enumerate(ids)}) products batch_query_products_by_ids(ids) return paginate(products, page, size)这段代码实际开发时要注意如果分类下商品数极少缓存Key可以设短过期时间如果商品数上万Set的排序效率依旧很高Redis内部是有序结构ZRANGE的时间复杂度是O(log N M)。4.3 购物车的合并与选中状态计算购物车模块很多时候看起来简单实际上手写的细节极多。主要难点在“未登录加购”和“登录后合并”之间的数据搬移。我的实现策略是未登录时购物车数据存localStorage用户点击登录后前端把localStorage里的购物车数组通过一个merge_cart接口提交给Flask。[ {sku_id: 1001, quantity: 2}, {sku_id: 1008, quantity: 1} ]Flask后端做三件事逐项检查SKU有效性、将同SKU数量合并、剔除库存不足的项并返回提示。合并完成后清空本地缓存以服务端数据为准。购物车的“全选/单选”勾选状态我选择在服务端存字段checked因为结算页需要精确计算勾选商品的总额。如果只存在前端用户换设备后勾选状态就丢了。4.4 下单时的事务边界与锁设计下单接口是电商项目里最不能出错的地方。我画了一条非常明确的事务边界订单主表插入、订单明细插入、库存锁定、购物车清空删项这四件事必须在同一个事务里完成。数据库层面使用SELECT ... FOR UPDATE锁定SKU行防止两个用户同时下单导致超卖。但要注意锁的顺序项目中必须固定按“SKU ID升序”加锁否则两个订单各包含对方SKU时会产生死锁。# Django 订单创建事务示意 from django.db import transaction with transaction.atomic(): sku_ids [item[sku_id] for item in items] sku_objects SKU.objects.select_for_update().filter(id__insku_ids).order_by(id) for sku in sku_objects: if sku.stock - sku.locked_stock 所需数量: raise StockShortError(sku.id) order Order.objects.create(...) OrderItem.objects.bulk_create(order_items) # 更新 locked_stock事务提交后再异步发一条消息给消息队列或直接调用Redis删除该用户的购物车缓存。订单创建成功后前端跳转支付页后端开启一个延迟任务15分钟内未支付自动关闭。经验我在项目里给“关闭订单”单独建了一张定时任务表每分钟扫一次“已创建但超时未支付”的订单。数据量少时直接轮询数据量大了再换延迟队列。初学者不用一上来就上Celery先把主体流程跑通。5. 双框架协作中的通信与部署要点5.1 Django与Flask如何共享同一套数据这里有一个容易踩坑的认知Django的ORM模型和Flask的SQLAlchemy模型必须手动保持字段同步。我在项目中采取的方式是Django作为唯一的模型定义来源用python manage.py inspectdb倒推出Flask侧需要的Model定义模板再手动精简。每天的模型变更流程在Django中修改models.py生成并执行迁移。在Flask项目中手动同步对应的SQLAlchemy模型字段。如果有新增表Flask侧补充新的Blueprint路由。同步确实麻烦但换来的好处是两边不会因为ORM差异而性能下降。实测中Flask侧直接用SQLAlchemy Core模式table对象比用ORM映射快5%以上查询高频接口时尽量用Core方式。5.2 Nginx反代配置简述server { listen 80; server_name shop.example.com; location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /admin/ { proxy_pass http://127.0.0.1:3031; proxy_set_header Host $host; } location /static/ { alias /data/shop/static/; } }这套配置把静态资源请求彻底分流Django和Flask都不处理图片和CSS文件。商品图片统一走对象存储MySQL只存图片URL路径避免将二进制大字段放在业务库里拖慢备份和查询速度。5.3 部署后的环境变量管理生产环境我把敏感配置放进了.env文件并用python-dotenv加载。Django的settings.py和Flask的config.py都从环境变量取值例如DB_NAMEshop_db DB_USERshop_user DB_PASSWORDxxx REDIS_URLredis://127.0.0.1:6379/2 SECRET_KEYrandom-generated-key有一点务必养成习惯任何环境下不要提交.env到代码仓库。我见过太多项目因为.env外泄导致数据库被拖库的案例。.gitignore里必须写死.env。6. 常见问题与排查技巧实录6.1 订单重复提交导致库存为负症状并发压测时同一SKU的下单接口返回成功但库存最终变成负数。原因只靠前端按钮防重不可靠后端没有做幂等控制。解法在orders表中为每个用户每分钟的订单请求设置一个幂等KeyRedis中SETNX一个短期锁。# 幂等控制伪代码 lock_key forder_lock:{user_id}:{sku_id_group} if not redis.set(lock_key, 1, nxTrue, ex3): return jsonify({code: 429, msg: 操作太频繁})6.2 Django Admin修改商品后Flask接口数据不更新症状后台把商品价格从99改成89前端商品详情还是99。排查思路Flask侧用了Redis缓存商品详情Django Admin的save流程没有触发缓存删除。解决在Django Admin的ModelAdmin中重写save_model保存成功后主动删除对应缓存Key。缓存的Key命名规范在这个项目里是product:detail:{id}和category:{category_id}:products删除时用scan匹配前缀批量清理。6.3 Flask的SQLAlchemy连接池被耗尽症状压测半小时后Flask日志出现TimeoutError: QueuePool limit of size 10 overflow 10 reached。原因每个请求获取了数据库连接但没有归还或者事务未正确提交/回滚。解决全局注册teardown_appcontext钩子确保每次请求结束都db.session.remove()。另外把连接池大小从默认的10调到20pool_pre_pingTrue可以在连接失效时自动重连。from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() def register_db(app): app.config[SQLALCHEMY_ENGINE_OPTIONS] { pool_size: 20, pool_recycle: 3600, pool_pre_ping: True, } db.init_app(app) app.teardown_appcontext def shutdown_session(exceptionNone): db.session.remove()6.4 混合架构下session不一致症状用户从Flask API登录后访问Django管理后台仍要重新登录。原因两套框架的session存储机制不同Flask默认client-side sessionDjango默认服务端session。处理方案不追求单点登录。面向C端用户的操作全部走Flask API管理后台只允许roleadmin用户登录。两个体系的会话在逻辑上天然隔离反而减少了安全风险。如果在后续业务中要打通可以在Redis里统一存session并让两边共享同样的session key生成规则。7. 项目后续扩展方向这套双框架电商项目完成后后续扩展空间其实很大。我建议优先做三件事引入Celery异步任务队列订单超时关闭、支付回调处理、邮件通知、库存回补通知都可以切到异步队列里去Flask进程不再被耗时任务卡住。搜索能力升级当前商品搜索是MySQL的LIKE匹配数据量超过十万条就吃力。后续可以接入Elasticsearch或者轻量级的Manticore Search把商品名、标签、描述统一建成索引检索速度能提升一个量级。管理后台定制化Django Admin虽然上手快但交互形态离商业后台还有距离。可以逐步用Vue或者React做一个独立的管理前端通过DRF或者自定义API从Django侧取数。我个人在带这个项目的过程中最大的感受是电商项目没有一步到位的完美架构只有基于当前团队规模、业务体量不断调整的合适方案。双框架混合看起来增加了一些部署和沟通成本但得到的收益是每个环节都能用上最顺手的工具。它既是练手项目里最接近真实业务的一种也最容易在后续演进中逐步拆成微服务。如果你正卡在一个“框架全家桶到底该选谁”的纠结里不妨用这个思路把你的电商项目重新盘一遍大概率能理清思路。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表