1. 项目概述与整体设计思路
做这个项目其实是去年年末的事了。某学院的一位朋友要做一套综合性的软件系统作为结业作品,选题范围可以覆盖电商、智能客服和进销存三个方向。当时我手头正好有几年 Django 的项目经验,就建议他别做那种“大而全但各模块都只有半个功能”的演示品,而是把三个方向塞进一个项目里,做成一条完整业务链:用户在前台逛商城买东西,遇到疑惑的时候有智能问答顶上来回答,下单之后仓库那边自动减库存、生成出库单。这个思路最后落地成了一个基于 Python + Django 的自行车网上商城系统,附带 AI 问答模块和仓库管理模块,代码和文档加起来大概小两万行,前后折腾了快两个月。今天把整个项目拆开聊聊,从选型到建模到踩坑,能帮准备做类似综合项目或者想了解 Django 整合 AI 能力的朋友省不少试错时间。
先说这套系统到底干了什么。它不是一个单页面的“伪商城”,而是包含三条业务线:
- 对外商城:商品展示、搜索、购物车、下单结算、订单查询,完整走完 C 端购物流程。
- AI 问答:以知识库检索为主,意图识别为辅,用户问“这辆车适合多高的人骑”或者“怎么查我的订单物流”,系统能给出准确应答,而不是简单返回固定话术。
- 仓库管理:商品 SKU 维度的库存台账、入库单、出库单、库存流水、安全库存预警,并和商城订单自动联动。
三个模块用同一个 Django 工程承载,共享用户体系和数据库,但通过 App 做了清晰隔离。这个项目适合正在做 Java/Python 课程设计的人参考,也适合想了解“电商 + 简单 AI + 进销存”如何在一个系统中协同工作的开发者。如果你是刚接触 Django 不久、想看看一个相对完整的项目是怎么组织的,这篇也能当一份案例笔记看。
2. 系统架构与核心技术选型
2.1 为什么整套系统选择 Django 而不是 Flask 或 Spring Boot
说实话,如果只是做商城,Flask 配合一些扩展也完全能跑,但我最终选了 Django,核心原因是这套系统的需求不是“纯接口”,而是“业务后台 + 管理后台 + 前台页面”三者都有。Django 自带的东西恰好能覆盖这些。
第一是 ORM。商城要管商品、SKU、库存、订单,仓库要管入库单出库单,问答要管知识库条目,这些模型的关联和事务操作非常频繁。Django ORM 的 migration 机制在模型迭代时尤其省事,改完模型字段跑一条命令就能同步数据库结构,不用像某些框架那样手写一堆建表脚本。
第二是 Admin 后台。仓库管理模块天然就是一个“内部人员操作系统”,我们一开始就用 Django Admin 做了仓库管理的骨架,之后再逐步替换成自定义页面。Admin 免费提供登录、权限、列表筛选、分页这些能力,开发期能省下大量时间。
第三是认证与权限体系。商城需要区分普通用户、客服和仓库管理员,Django 自带的 User 模型加上 Permission 机制,直接给用户组分配权限就能实现基本的角色隔离。
第四是模板系统。前台商品列表页、详情页、订单页都用 Django Template 渲染,不需要额外搭前后端分离架构。这种单体模式对一个人开发、两个人协作的小项目来说,维护成本最低。如果强行拆成 Vue + DRF 前后端分离,工作量至少翻倍,但收益在这个规模下并不明显。
当然 Django 也有缺点,比如异步支持相对偏弱,WebSocket 实现要额外配置。但这个项目里 AI 问答是请求-响应模型,不需要长连接推送,所以完全够用。
2.2 三条业务线的模块划分与协作关系
整个工程按 Django App 拆成五个模块:users、shop、cart、qa、warehouse。前两个分别负责用户体系和商城主流程,cart单独拆出来是为了购物车逻辑复用,qa是智能问答,warehouse管库存和出入库。
模块之间的协作关系是这样的:商城商品表shop.Product和仓库库存表warehouse.Stock通过product_id建立一对一关联;用户下单支付成功后,订单状态变化会触发一个信号,自动在warehouse模块生成出库单,并扣除对应 SKU 的可用库存;AI 问答模块的知识库中有相当一部分条目是关于订单查询和商品参数的,用户在商城页面发起提问时,qa模块可能会反向查询订单表来生成回答。
这样设计的好处是职责清晰,每个 App 都可以独立测试和迭代,同时在 Django 工程内部共享同一套数据库和缓存,不需要做服务间通信。对于这种规模的系统,微服务反而会让事情变复杂,用一个工程加清晰分层就够了。
值得一提的是权限设计。users模块里预置了三种角色:普通注册用户、仓库管理员、超级管理员。普通用户只能访问商城的商品和订单接口;仓库管理员额外拥有warehouse下所有模型的新增和修改权限;超级管理员除了全部权限之外,还能管理问答知识库。权限分配是在 Django Admin 里通过用户组一次性配置好的,避免在代码里到处写死的权限判断。
2.3 数据模型设计的几个关键决策
数据模型是整个系统的基础,我花了很多时间在设计这十几张表上。以下是核心模型和它们之间的关系:
User:复用 Django 内置用户模型,只扩展了手机号和收货地址字段。Product:自行车商品主体,包含品牌、型号、分类、描述、图片。SKU:同一商品的规格维度,比如车架尺寸、颜色、轮径。库存和价格都挂在 SKU 级别。CartItem:购物车条目,关联用户和 SKU,记录数量。Order和OrderItem:订单主表和订单明细。主表记录总价、状态、收货信息;明细关联 SKU,记录快照价格。QuestionEntry:问答知识库条目,存用户可能问的问题和对应答案。Stock:库存台账,关联 SKU,记录在库总量、可用量、锁定量。InboundOrder/OutboundOrder:入库单和出库单,仓库流转记录的“票据”。StockRecord:库存流水,每一次库存变动都会写一条流水,用于追踪问题。
设计上最需要注意的地方是库存为什么放在 SKU 而不是 Product 上。自行车和普通商品不一样,同一款车可能同时有 700C 和 650B 两种轮径规格,还分不同的车架尺寸和颜色。如果把库存绑在商品上,就会出现“车有货但你要的尺寸没货”的尴尬。挂在 SKU 上才能精确管理每一档规格的库存。
另外订单明细里存的是“快照价格”而不是实时查 SKU 表的价格,是因为商品价格后期可能调整,如果订单总价跟着商品表走,历史订单的数据就全错了。这种“以订单数据为准”的思路在做电商系统时一定要养成习惯。
3. 商城模块的实现要点
3.1 商品模型与 SKU 的拆分逻辑
自行车商城的商品模型是整个商城的核心,这部分我踩过一个小坑,差点把模型设计成“一张表存所有规格”。后来参考了一些成熟的电商系统设计,才意识到必须拆成 Product 和 SKU 两张表。
Product表存的是商品公共属性,比如标题、所属分类、品牌、详情描述、主图。而具体到可以下单购买的“最小库存单元”,则用SKU表来表示。举个例子,某款公路车在页面上是一个商品,但实际购买时有 4 个尺码、2 种颜色,这就是 8 个 SKU。每个 SKU 有自己的条码、价格、划线价和库存。
在代码里大概是这样:
class Product(models.Model): name = models.CharField(max_length=200, verbose_name='商品名称') category = models.ForeignKey('Category', on_delete=models.PROTECT, verbose_name='分类') brand = models.CharField(max_length=100, verbose_name='品牌') description = models.TextField(verbose_name='详情描述', blank=True) main_image = models.ImageField(upload_to='products/', verbose_name='主图') class SKU(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='skus', verbose_name='所属商品') spec_1 = models.CharField(max_length=50, verbose_name='规格1(车架尺寸)') spec_2 = models.CharField(max_length=50, verbose_name='规格2(颜色)') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='销售价') original_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='划线价') code = models.CharField(max_length=64, unique=True, verbose_name='SKU编码')这种设计在页面上展示“规格选择”的时候非常有用。前端拿到商品下所有 SKU 的规格组合和库存状态,就可以判断哪一档规格缺货,直接置灰不可选,避免用户下单后才发现没货。
3.2 购物车与订单流程的设计细节
购物车模块我用的是 Redis 存储,而不是数据库表。原因很简单:购物车是一个临时性很强的数据载体,用户加购后可能很久不下单,也可能频繁增删改,如果每次操作都写数据库,既浪费资源又要处理并发问题。放在 Redis 里用 Hash 结构存储,key 是用户 ID,field 是 SKU ID,value 是数量,操作效率非常高。只有用户点击去结算的那一刻,才把购物车数据从 Redis 取出写入订单表。
订单流程我设计为以下几个状态:
- 待付款:用户提交订单,此时系统会锁定库存(可用量减一,锁定量加一)。
- 已付款:支付回调成功后更新状态,同时通知仓库模块生成出库单。
- 已发货:仓库操作人员对出库单确认后自动标记。
- 已完成:用户确认收货后结束。
- 已取消:超过支付时限或用户主动取消,此时需要回滚库存。
这里最核心的操作是“下单锁库存”。如果不做锁定,就会出现两个人同时买最后一件库存的商品,两个订单都创建成功,但仓库实际只有一件货可发。锁库存的代码我放在装饰器里用事务处理:
@transaction.atomic def create_order(user, cart_items): order = Order.objects.create(user=user, status='pending_payment') total_amount = 0 for item in cart_items: sku = SKU.objects.select_for_update().get(id=item['sku_id']) if sku.stock.available_count < item['count']: raise ValueError(f'{sku} 库存不足') sku.stock.available_count -= item['count'] sku.stock.locked_count += item['count'] sku.stock.save() OrderItem.objects.create(order=order, sku=sku, price=sku.price, count=item['count']) total_amount += sku.price * item['count'] order.total_amount = total_amount order.save() return orderselect_for_update()是这里的关键,它在事务里对 SKU 的行记录加锁,防止并发请求同时读到同一个库存数值。这行代码我建议在做电商类项目时重点记住,很多并发扣减库存的问题都能靠它解决。
支付环节没有接真实的支付渠道,用的是“模拟支付”回调,在开发环境里点一个按钮就模拟第三方支付成功,然后订单状态流转。如果要在真实环境部署,把回调接口换成对应支付平台的验签和通知处理即可。
4. 理解 AI 问答模块的定位与实现
4.1 别动不动就上大模型,先分清楚业务问答的类型
这个项目里 AI 问答模块是最容易“浮夸化”的部分。很多人一听说要加 AI,第一反应是接一个通用大模型接口,然后把用户问题丢过去,让它自由发挥。这种做法在这个业务场景里并不合适,原因有三个:
第一,通用模型不了解你的商品数据和订单数据。它可能回答了自行车的通用保养知识,但用户问“我的订单 TX20250101 到哪里了”,模型没有能力去查你的数据库。
第二,大模型接口的响应延迟和成本在小项目中不可控。商城客服场景里用户问题通常都比较固定,不需要每次都动用生成式模型。
第三,作为课程设计或者个人项目,你如果只是调别人的接口,代码本身没有太多可展示的技术点,答辩的时候很难讲清楚你自己做了什么。
所以我把问答模块设计成了分层结构:最底层是知识库检索,中间层是意图识别,最上层是简单对话管理。只有当知识库无法匹配、或者用户明显在闲聊时,才可选的转发到通用大模型 API 兜底,这是可插拔的。
知识库检索这部分,用的是 jieba 分词加 TF-IDF 向量化再算余弦相似度的方法。虽然没有神经网络那么“智能”,但它完全离线运行、速度快,而且对中文短文本的效果足够可靠。以下是一个极端简化但能跑的示例:
import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 假设这是从 QuestionEntry 表里加载的问题列表 questions = ["这辆车适合身高多少的人骑", "能不能帮忙查一下快递物流", "退换货政策是什么"] answers = ["建议身高165cm以上选择M码车架", "请提供订单号,我可以帮您查询", "支持7天无理由退换货"] vectorizer = TfidfVectorizer(tokenizer=lambda text: jieba.lcut(text)) vectors = vectorizer.fit_transform(questions + [user_question]) sims = cosine_similarity(vectors[-1], vectors[:-1]) best_idx = sims.argmax() if sims[0][best_idx] > 0.3: return answers[best_idx] else: return "这个问题我还不太清楚,建议您联系人工客服"上面的代码核心在vectorizer.fit_transform这一步,它用 TF-IDF 把每个问题转成向量。用户在页面上提问时,系统把问题也用同样的向量器转换,然后再和知识库里每个问题的向量做余弦相似度匹配,取相似度最高的那个返回答案。0.3的阈值是反复试出来的,太高容易查不到答案,太低会答非所问。
4.2 意图识别与订单查询能力的结合
如果你的 AI 问答只做基础 FAQ,那它还是太浅了。真正的价值在“问答系统能直接查业务数据”。比如用户问“我买的公路车发货了吗”,系统要能识别出这是一个查订单的意图,然后去订单表查数据。
订单查询的核心在于从用户的话里提取“订单号”信息。实际处理中我用了两种方式:规则提取加简单正则。用户在聊天中通常会说“订单号是 TX20250101”或“单号是 TX20250101”,这时用正则抓出TX\d+模式的字符串即可。但如果用户只说“我的车发货了没”,没有订单号,那就需要依靠用户登录信息取最近一笔订单来查。
意图识别我用的是“关键词集合 + 优先级匹配”的方案,效果不比训练一个分类模型差。比如:
- 问题里含“订单、物流、发货、到哪、快递”等词,归为订单查询意图。
- 问题里含“身高、适合、尺码、多大”等词,归为商品尺码咨询。
- 问题里含“退、换、售后、保修”等词,归为售后意图。
意图识别之后再决定由哪个子模块来处理问题。如果是尺码咨询,直接走知识库检索;如果是订单查询,就调用订单服务获取数据并组装答复;如果都没有命中,就进入兜底的生成式模型接口。
这里有一个教训:问答模块不能只做“检索返回答案”,那样看起来就像是文档查询工具,而不是 AI。把业务查询和知识库回答结合到一起,系统才真正有了“智能感”。
4.3 知识库内容的准备与冷启动
知识库是 AI 问答的地基。刚开始我们只有二十多条问答,效果非常差,用户换个问法就查不到。后来我总结出一个规律:知识库的填充最好直接参考电商客服的聊天记录。如果没有现成的记录,就按五类问题来准备——价格优惠类、尺码规格类、物流售后类、使用保养类、支付类。每类至少准备十个问题,但很多问题本质上是同一答案的不同表述,所以在表设计里我加了一个question_synonyms字段,存同义问法,这样可以避免完全重复录入。
另外要注意分词对准确率的影响。自行车领域的专业名词很多,比如“碟刹”“轮径”“尺码”。默认的 jieba 词典会把“碟刹”切成“碟”和“刹”,导致向量化之后查找不准。解决办法是自定义用户词典,把领域词汇加进去:
碟刹 10 n 轮径 10 n 公路车 10 n 山地车 10 n 碳纤维 10 n把这个字典文件的路径传给 jieba:
jieba.load_userdict('dicts/bike_dict.txt')这样分词的结果就正确了,后续相似度计算的准确率提升非常明显。
5. 仓库管理系统的设计思路
5.1 从“一件商品几个库存”到完整的出入库单据体系
仓库管理系统是很多人做电商项目时最容易忽略的部分,通常就是一张库存表加加减减完事。但真实业务场景里,库存管理必须要有“单据”的概念。库存表只能记录一个结果,而单据记录的是原因和操作留痕:这批货是哪张采购单进来的,那批货是哪个订单出库的,出了问题才能追责。
入库单InboundOrder至少包含单号、供应商、入库时间、经办人、明细列表。出库单OutboundOrder则关联商城订单,包含出库类型(销售出库、退货出库、盘亏出库)、收货方、操作时间。每一张单据审核生效后,才允许变动库存台账Stock的字段,同时必须写一条StockRecord流水。
在 Django 里的库存扣减写在一个统一的原子操作接口里,任何想改动库存的代码都必须调用它:
@transaction.atomic def change_stock(sku, delta, reason, record_type): stock = Stock.objects.select_for_update().get(sku=sku) stock.quantity += delta stock.save() StockRecord.objects.create( sku=sku, change=delta, reason=reason, record_type=record_type, created_by=request.user )有了这个统一入口,后续所有业务逻辑就不用各自去写quantity += 1这种散乱的代码了,库存流水可以完整追溯。
5.2 安全库存预警与采购建议
仓库管理模块还加了一个简单但非常实用的功能:安全库存预警。每一种 SKU 可以设置一个安全库存值,当台账库存低于这个值时自动触发预警。我之前在写这个功能时直接在查询库存表时判断,后来项目上了定时任务之后才发现,应该把预警逻辑独立出来做一次全量扫描。
预警功能配合 Django 的Celery定时任务,每天早上跑一次,扫描所有 SKU:
def check_stock_warning(): low_stock_skus = [] for s in Stock.objects.select_related('sku').all(): if s.quantity < s.safe_stock: low_stock_skus.append({'sku': s.sku.code, 'current': s.quantity, 'safe': s.safe_stock}) if low_stock_skus: send_warning_email('bike-shop@example.com', low_stock_skus)这个扫描任务还顺带生成了一份采购建议单。采购建议的逻辑很简单:目标库存等于安全库存的两倍,当前库存不足部分就是建议采购量。它是一个半成品采购单,仓库管理员确认后可以一键生成正式入库单。这些功能虽然不复杂,但它让“仓库管理”这个模块真正完整了,而不只是“库存增删改查”。
5.3 与商城订单的联动处理方式
商城和仓库的数据联动是这个项目里最关键的整合点,也是我觉得整个系统最有价值的部分。用户在前台支付一个订单之后,后台要自动完成一系列动作:扣减 SKU 可用库存、解除锁定库存、生成销售出库单、把出库单分配到仓库待办列表。
具体实现方式是通过 Django 的信号机制完成解耦。在订单状态变更时发送一个信号,仓库模块里的接收函数负责处理库存和出库单。
from django.db.models.signals import post_save from django.dispatch import receiver @receiver(post_save, sender=Order) def on_order_status_changed(sender, instance, **kwargs): if instance.status == 'paid': outbound = OutboundOrder.objects.create(order=instance, type='sale') for item in instance.items.all(): outbound.items.create(sku=item.sku, count=item.count) change_stock(item.sku, -item.count, f'订单{instance.order_no}销售出库', 'outbound')使用信号的好处是订单模块不需要知道仓库模块的存在,未来如果要新增一个财务模块,只需要再写一个接收器监听同一个订单状态改变即可,符合开放封闭原则。
这里也要提醒一点:信号虽然是好用的解耦方式,但不要在信号里面做耗时太长的操作。如果订单状态改变后有一连串的耗时任务,应该发一个任务给 Celery 异步执行,而不是在信号里同步等待。项目里我初期就是这么干的,导致支付回调接口的响应时间变得很慢,后来把邮件通知和出库单生成都改为异步才解决。
6. 前后台交互与权限控制的实现细节
6.1 前台商城与后台管理的页面组织
商城前台用的是 Django 模板加 Bootstrap。这个选型很务实,项目里没有专职前端,用 Django 模板可以直接复用后端数据,不需要额外联调接口。商品列表页挂在/shop/,详情页挂在/product/{sku_id}/,购物车页面和结算页面各自独立。
后台仓库管理页面一开始直接用的 Django Admin,后来发现 Admin 的默认交互不太适合仓库工作人员的日常节奏。比如仓库管理员希望在一个界面里看到所有待处理的出库单,点击按钮就能完成出库,而不是点进每一个单据再修改状态。所以后来我用 Django 原生的视图自己写了一个简易控制台:左侧菜单是待入库、待出库、库存预警、商品管理,右侧是数据列表和操作按钮。
这种“混合模式”是我负责的项目里比较常用的做法——Admin 用于程序员和管理员维护基础数据,而日常业务系统用定制页面提升操作效率。如果整个后台全靠重写会很花时间,但完全不定制又很难用,找好这个平衡点非常重要。
6.2 基于角色的权限控制如何落地
权限控制在 Django 里属于自带的成熟能力,但用好它还需要一些设计。我定义了三个用户组:普通用户组、仓库组、管理员组。每个组分配不同的模型权限,比如仓库组的用户拥有对OutboundOrder的处理权限,但没有修改商品价格的能力。
前台的用户权限主要靠视图里的login_required和user_passes_test来控制:
from django.contrib.auth.decorators import login_required, user_passes_test @login_required def order_list(request): orders = Order.objects.filter(user=request.user) return render(request, 'shop/order_list.html', {'orders': orders}) @login_required @user_passes_test(lambda u: u.groups.filter(name='仓库组').exists()) def warehouse_dashboard(request): pending_outbound = OutboundOrder.objects.filter(status='pending') return render(request, 'warehouse/dashboard.html', {'orders': pending_outbound})权限控制有一个容易忽略的细节:Ajax 请求也一样要做权限校验。我遇到过前端页面正常渲染,但里面的接口因为权限不足返回 403 的情况。后来统一封装了一个登录检查装饰器给所有 Ajax 视图,才彻底解决。
6.3 缓存与性能优化的几个有效手段
项目里的性能优化没有做得很深,但有几个手段特别立竿见影。
商品列表页的数据库查询做了select_related和prefetch_related。Django 的 ORM 在没优化的情况下很容易造成 N+1 查询问题。比如商品列表页要显示 20 个商品和它们的 SKU,如果代码里写Product.objects.all(),然后在模板里循环访问每个商品的skus属性,那就会产生 1 次查询取商品、20 次查询取 SKU,总共 21 次数据库查询。改成Product.objects.prefetch_related('skus')之后,就变成了 2 次查询。这个改动对页面响应速度的影响是肉眼可见的。
AI 问答模块的检索结果也做了缓存。知识库本身不经常变动,但每次用户提问都要实时把问题向量化、再和全部知识库条目计算相似度,有点浪费。我把知识库的 TF-IDF 向量矩阵在系统启动时加载一次并缓存到 Redis,用户提问时只需要转换用户问题这一个向量,然后在内存里做矩阵运算。如果知识库更新了,就主动删除缓存键,让矩阵在下次加载时重建。
Redis 在做缓存的同时也承担了购物车存储的职责。把 Redis 的 key 设计为cart:{user_id},用 Hash 结构存储 SKU 和数量,这样既方便又高效。
7. 部署上线与整体联调的经验总结
7.1 从开发环境到生产环境的部署清单
很多项目在本地跑得挺好,一部署到服务器就各种问题。这个项目部署时我也踩了一些常见的坑,这里列一个比较完整的部署步骤可以作为参考。
服务器环境是 Linux 加 Nginx 加 Gunicorn,数据库用的 MySQL,缓存用的 Redis。部署的第一步是准备 Python 环境,用virtualenv创建独立环境,然后用pip install -r requirements.txt安装依赖。依赖列表里要特别注意固定版本号,不能只写包名不写版本,否则一段时间后同一套代码可能因为某个依赖升级而跑不起来。
然后是配置文件的区分。开发环境和生产环境的配置差异很大,一定不能混用。我用了settings_dev.py和settings_prod.py两个文件,公共部分放到settings_base.py里继承。生产环境的配置至少包含:DEBUG=False、ALLOWED_HOSTS设置为实际域名、数据库改为 MySQL 的远端连接、STATIC_ROOT设置好之后执行collectstatic、media 文件路径用 Nginx 直接映射。
Gunicorn 的启动命令有几个参数要特别注意。--workers通常设为 CPU 核数加 1,--bind绑定到本地127.0.0.1:8000,不要直接绑定公网 IP,因为前面还有 Nginx 做反向代理。Nginx 配置里把/static/和/media/两个路径直接交给文件系统处理,其余请求反向代理到 Gunicorn,这个配置可以用下面这个模板:
server { listen 80; server_name example.com; location /static/ { alias /path/to/project/static/; } location /media/ { alias /path/to/project/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }数据库迁移也是一个需要小心的环节。如果生产环境已经有真实数据,python manage.py migrate之前一定要先备份数据库。虽然 Django 的迁移机制理论上可以自动处理表结构变更,但在加了非空字段而没有默认值的场景下,迁移工具会在执行时报错,这时候需要你在迁移文件里手动设置默认值或者做数据回填。
7.2 全链路联调的关键场景
联调是这个项目里最有意思的阶段,因为三条业务线要一起跑通,才能体现“综合系统”的价值。我当时设计了三个核心联调场景,每个都覆盖了多个模块的交互。
第一个场景是“从加购到发货”。用户注册登录后搜索自行车,选中某款公路车的特定尺码加入购物车,提交订单,模拟支付,仓库后台出现一张新的出库单,操作出库后订单状态更新为已发货,用户可以查到物流信息。这个流程覆盖了users、shop、cart、warehouse四个模块,是最重要的一条链路。
第二个场景是“库存不足拦截”。把某 SKU 的库存设置为 1,然后同时从两个浏览器发起下单请求,其中一个能成功,另一个必须提示库存不足。这个场景验证select_for_update()是否真正起了作用,相关代码在联调阶段改过一次,最终确认事务隔离级别没问题才通过。
第三个场景是“智能问答查订单”。用户向 AI 问答模块发消息说“我的订单发货了吗”,系统提取当前用户最近一笔订单并查询状态,回答“您的订单 TX20250101 当前状态为已发货,承运商为某物流公司”。这个场景把qa模块和shop模块链接在一起,是这个项目区别于其他单功能系统的一个亮点。
7.3 上线后真实运行中的问题修复记录
项目部署上线之后还出现过几个问题,这些问题在纯开发环境中不容易暴露,但在这里很值得记录。
第一个问题是时区。Django 开发环境下用USE_TZ=True和默认时区感觉不到差异,但部署到真实服务器后,所有订单创建时间都比本地时间少了 8 小时。原因很简单,服务器是 UTC 时区,但用户在中国。解决方法是把TIME_ZONE设置为'Asia/Shanghai',同时确认数据库连接字符串里的时区参数一致。
第二个问题是图片无法显示。商城商品图片用ImageField存储在本地目录,开发环境能正常显示,但部署后 Nginx 没有正确映射/media/路径,导致所有图片 404。这个问题排查了很久才定位到,因为本地跑 Django 开发服务器会自动处理 media 文件,但生产环境必须交给 Nginx。后来在 Nginx 配置里加上 media 路径的 alias 就好了。
第三个问题是问答知识库的热更新。因为知识库向量矩阵做了缓存,管理员在后台新增一条问答后,前台用户去提问还是拿到旧答案。后来我加了一个信号接收器,在QuestionEntry保存的时候自动删除缓存键,问题就解决了。这也说明缓存更新策略一定要纳入功能设计,不能等出了问题才补齐。
8. 代码贡献与开发过程回顾
这个项目目前已经能在本地完整跑通,小而全的三个子系统合在一起,整体完成度比较高。代码仓库里的结构大概是这样:
bike-shop/ manage.py config/ # 项目配置,包含 dev/prod 不同配置 apps/ users/ # 用户体系与权限 shop/ # 商品、订单 cart/ # 购物车(Redis) qa/ # AI问答、知识库 warehouse/ # 仓库管理、库存流水 static/ media/ templates/ requirements.txt整个开发周期大约八个星期,前两周做需求分析和数据库设计,中间三周完成商城和仓库的核心功能,再花两周做 AI 问答模块,剩余时间全部用于联调和部署。如果是一个有经验的 Django 开发者来做,时间应该能压缩到五周左右,但考虑到项目中还涉及自定义的问答检索和信号联动,这已经算比较紧凑的节奏了。
回到标题里那个关键词“AI 问答系统”,我的理解是这个项目如果想在答辩或面试时展示亮点,就应该重点讲清楚问答系统如何与传统业务系统协同工作。它既不是孤立的聊天机器人,也不是给商城锦上添花的小工具,而是真正参与了订单查询和售后服务的业务流程。技术本身没有特别高深的东西,但“检索加业务查询混合”的架构思路是可以迁移到很多实际场景里去的。
我个人在实际开发中最有体会的一点是,Django 这种全家桶式的框架对于综合性业务系统来说确实效率很高。它把 Web 开发里那些最常用、最琐碎的东西都准备好了,让你可以专注于业务逻辑本身。如果你也在规划一个类似的综合项目,建议不要纠结于“一定要用什么最新技术栈”,而是把核心业务链路如何跑通想清楚,这比什么都重要。