☰
Python+Vue前后端分离实战:商城、商家与优惠券系统架构解析
2026/10/6 4:28:29 网站建设 项目流程

前后端分离项目做多了,你会发现真正难的不是写业务代码,而是"一开始就把架构想明白"。我最近刚把"万事屋智能服务平台"从原型到落地完整走了一遍,趁热打铁把这套基于Python + Vue的商城、商家、优惠券三合一系统拆开揉碎讲一讲。不管你是刚入门的Python学习者,还是已经能用Django写CRUD但没做过完整商业项目的开发者,这篇文章都值得你花十分钟读完。

先交代一下背景。这个平台叫"万事屋",思路其实很简单——把同城生活服务类商家(保洁、维修、跑腿、宠物照看等等)集中到一个平台上,用户可以逛商城下单,商家入驻后管理自己的店铺和订单,平台通过优惠券来做拉新和促活。我当时的开发环境是Windows + PyCharm Professional,后端主力用的是Django 4.x + Django REST Framework,前端用Vue 3 + Vite + Element Plus,另外还有一小块服务用Flask写的。整个项目从设计到上线前前后后大概两个月,中间踩了不少坑,也沉淀了一些值得记录的经验。

这个项目最大的价值不在于功能多复杂,而在于它把"电商平台"这条链路上的几个典型模块完整走了一遍。你把它看懂,"商城类项目怎么做"这个领域,基本就有了一个可以迁移的整体认知。

1. 项目概述与整体设计思路

1.1 "万事屋"到底是一个什么平台?

先把这个名字拆开。"万事屋"这三个字,参考的是那种"什么都接"的服务站概念。放到这个项目里,它的定位是同城生活服务撮合平台。

用户端能做什么?浏览商城里的服务商品(比如上门保洁一次、空调清洗一台、维修服务包),下单购买,在个人中心查看订单状态,用优惠券抵扣金额。商家端能做什么?申请入驻、提交资质、平台审核通过后,商家可以上架商品、管理库存、处理订单、查看营收数据。平台端能做什么?审核商家入驻申请、管理全平台的商品上下架、创建优惠券活动、查看整体交易流水。

这个三层角色结构,决定了系统从一开始就不能只是一堆CRUD堆在一起。商家、用户、平台管理员三个角色的权限边界要清晰,数据隔离要严谨,优惠券的核销逻辑要经得起并发考验。你可以把它想象成一个迷你版的美团加迷你版的有赞,虽然没有大厂那么庞大的体量,但该有的骨架一个不少。

目标用户画像很清楚:我是按"中小型本地生活服务商"来设计的。这些商家通常没有自建小程序或App的能力,需要一个现成的平台来展示和售卖服务。而用户端则需要一个"一个平台搞定多种家庭服务需求"的入口。平台方赚的是交易佣金和营销费用,优惠券就是一种典型的平台补贴工具。

1.2 为什么选前后端分离架构?

坦白讲,用Django的模板系统(DTL)直接渲染页面,开发速度会更快,尤其适合一个人全部搞定的项目。但我最终坚持用了前后端分离,原因有三。

第一,这个项目的角色类型多,页面形态差异大。用户端是商城列表、详情、结算这种偏C端交互的页面;商家端是数据表格、表单、状态流转这种偏B端的后台页面;管理端更是典型的dashboard格局。如果用服务端渲染,这三套东西全挤在Django模板里,工程会迅速腐烂。前后端分离之后,前端只是调API,后端只关心数据和规则,边界非常干净。

第二,Vue的组件化(组件化开发)让复用成本大幅下降。比如商家端和用户端都会用到商品卡片,虽然展示逻辑不同,但底层的图片懒加载、价格格式化、优惠券标签这些逻辑,写成Vue组件之后可以灵活复用。

第三,后期如果要扩展到小程序或者App,API已经摆在那里了,前端换一套客户端就行。后端基本不需要大改。这种扩展性,是模板渲染架构给不了的。

当然,前后端分离也引入了额外的问题,比如跨域(跨域资源共享)、Token鉴权、接口文档维护。这些坑我在第6章会专门展开说。你只需要知道,选择分离架构不是因为它"流行",而是因为这个项目本身的多端属性和角色复杂度,值得付出这些额外的成本。

2. 技术选型:Django、Flask还有PyCharm环境

2.1 主力后端为什么是Django?

标题里Django和Flask都出现了,很多读者会好奇,一个项目为什么要同时用两个Python Web框架?这里我想把选择逻辑讲清楚。

主力后端我用了Django,核心原因是这个项目里有一个非常关键的领域概念——优惠券。优惠券的创建、分发、核销、过期,这几个环节之间有关联的状态流转,而且涉及金额计算,对数据一致性要求高。Django自带的ORM提供了完备的事务管理、字段约束和迁移机制,我在写模型的时候可以少操很多心。更重要的是Django自带的Admin后台,在开发阶段几乎是免费的福利,我搭建平台管理端的管理界面时省了大量时间。

另一个原因涉及到权限体系。商城系统里用户、商家、管理员三种角色的权限完全不同,Django自带的认证系统和第三方库django-guardian(对象级权限)结合起来,可以比较轻松地实现"商家只能操作自己店铺的数据"这种细粒度控制。如果用Flask,这部分要从零开始造轮子,开发周期至少多两周。

2.2 Flask在这个项目里承担了什么角色?

那Flask是不是白用了?也不是。我在项目里用Flask单独写了一个服务,负责优惠券的定时核销任务和过期提醒通知。为什么要单独拆出来?因为Django主服务的实例平时要扛商城的QPS(每秒请求数),我不希望一个后台定时批量任务去抢主服务的资源,导致用户侧接口变慢。Flask服务麻雀虽小,但配合APScheduler和Redis做了定时轮询,把过期的优惠券扫出来、把即将过期的提醒消息推出去,这个任务独立部署到另一台小机器上,互不干扰。

另外,Flask非常适合做微服务或辅助型服务的起步框架——它足够轻,启动快,依赖少,当辅助服务用体量刚好。你要记住一个原则:技术选型不是"哪个框架好"的问题,而是"哪个框架在哪个位置上更合适"的问题。Django是大包大揽的全家桶,Flask是轻装上阵的小工具,两者各有各的位置。

2.3 Python版本与PyCharm开发环境配置

开发环境这块,我踩过最痛的坑就是Python版本混用。项目一开始我本地装的是Python 3.9,后来迁移到服务器上发现是3.8,有个第三方库直接编译不过去。所以如果你是新手,请从第一天就固定Python版本。我在这个项目里用的是Python 3.10,Django 4.2 LTS版本,这两个版本到现在依然是很稳的组合。

PyCharm这里我要多说几句。很多人用了多年PyCharm,其实只用到了10%的功能。我在这个项目里的配置方案是这样的:

  • 虚拟环境用Virtualenv,不直接用PyCharm默认的Interpreter。因为默认的全局解释器容易造成不同项目的依赖污染,你Django装了个旧版本,另一项目立刻遭殃。Virtualenv之后,每个项目有自己的依赖隔离,出问题也好排查。
  • 在PyCharm里把Run Configuration设置成Django Server,这样F5直接跑起来,断点调试非常舒服。具体操作是:Run -> Edit Configurations -> 左上角加号 -> Django Server -> 填好Host(127.0.0.1)、Port(8000)和Python解释器(选你创建好的Virtualenv路径)。配置好后,你可以在代码行号旁边点一下,设置断点,跑到那一行就会停下来,可以查看所有变量的值。
  • 强烈建议安装一个PyCharm插件叫"Save Actions",加上"File Watchers",可以让保存文件时自动执行ruff格式化和排序import。这个小细节能让代码整洁度提升一个档次。

有读者问过PyCharm激活的问题,我不赘述,只是提醒一句:PyCharm专业版对于开源项目和学生都有免费授权,走正规渠道申请就行,效率其实比你想象的快很多。

3. 商城、商家、优惠券三大核心模块的实现逻辑

3.1 商城模块:商品模型与购物车设计

商城模块是用户最直接接触的部分。第一个核心是商品模型,体现"服务商品"的特征。与实物商品不同,服务商品没有库存数量但有时段限制,所以我在设计模型时增加了"可预约日期"和"服务时长"两个字段,而不是传统的stock字段。

商品模型的关键字段设计(部分):

class ServiceProduct(models.Model): name = models.CharField("商品名称", max_length=128) shop = models.ForeignKey(Shop, on_delete=models.CASCADE, verbose_name="所属店铺") category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) price = models.DecimalField("原价", max_digits=8, decimal_places=2) vip_price = models.DecimalField("优惠价", max_digits=8, decimal_places=2, default=0) duration = models.IntegerField("服务时长(分钟)", default=60) is_active = models.BooleanField("是否上架", default=True) created_at = models.DateTimeField(auto_now_add=True)

这里有几个设计上要留意的地方:

  • 价格一定要用DecimalField而不是FloatField。浮点数做金额计算会有精度问题,比如0.1 + 0.2在浮点数里不等于0.3,这在支付场景里是不可接受的。用DecimalField,虽然操作上稍微麻烦一点,但金额安全是第一位的。
  • is_active字段是软删除/上下架逻辑的核心。你前端下架一个商品,后端只是把is_active置为False,而不是物理删除。这样即使用户把它加入了购物车,商品下架了也不会出现订单里找不到商品的尴尬。

购物车实现方面,考虑到用户可能不登录就逛商城,我先实现了本地购物车(存入localStorage),登录之后再从接口同步。本地购物车的数据格式是一个数组,里面存商品ID、数量、规格ID,后端每次在获取购物车详情时,会一次性把商品的最新价格、上架状态拉出来返回前端。这样的好处是,购物车页面永远展示实时价格,不会出现结算时才发现价格已经变动的情况。

3.2 商家模块:入驻审核与店铺管理的权限隔离

商家模块是整个平台"管理侧"的核心。最考验设计功底的是入驻审核流程。我把它设计成了一个简单状态机,状态流转是:待审核 -> 审核通过(同时创建店铺) / 审核驳回(用户可以修改资料后重新提交)。

商家提交入驻申请时,需要填写营业执照信息、法人身份信息、服务类别、店铺简介和logo。平台管理员在Django Admin或者管理端页面审核。审核通过时,我不仅创建了店铺记录,还会给商家绑定一个商家角色分组(Group),这个分组拥有对店铺下商品的增删改查权限——这就呼应了前面说的django-guardian对象级权限。

为了防止商家看到别人的数据,我在所有涉及Shop查询的地方都强制加了shop_id过滤。这里有个实际教训:开发中后期,某一次我写商品删除接口时忘了过滤shop_id,结果出现了商家A能删除商家B商品的严重BUG。排查了半天才发现是queryset没用当前用户关联的店铺去过滤。从那以后我养成一个习惯,凡是涉及商家操作商品的接口,一律先从token里解析出商家ID,再去查对应店铺,最后在查询条件里带上shop_id。

商家端的店铺管理中,还有一个数据看板功能,展示今日订单数、营收、商品点击量等指标。实现上我用的是Django ORM的聚合查询(aggregate)加Redis做缓存——因为看板的数据不需要实时刷新,缓存个5分钟,既保留数据近实时性又不会打垮数据库。

3.3 优惠券模块:创建、分发、核销的完整生命周期

优惠券模块是平台营销的灵魂,也是代码里踩坑最多的地方。我把劣惠券拆成两层:第一个层面是模板(CouponTemplate),第二个层面是实例(CouponInstance)。模板定义规则——比如"满100减20""新客立减10",实例是真正发放到用户手里的券。

模板和实例分层的意义在于:创建活动时一次定义模板,然后批量生成几千张券实例,这些实例绑定不同用户之后,状态从"未发放"变成"已领取"。这种设计在数据统计上非常清晰——某个活动发出去多少、用了多少、过期多少,都是对实例做group by,效率高,逻辑也干净。

发券过程中有一个很经典的并发问题,我在第5章会专门展开。先看核销这个环节。用户在下单结算时,前端会把选择优惠券ID传给后端。后端接口需要做这样几件事:

  1. 校验优惠券是否属于当前用户
  2. 是否处于"已领取但未使用"状态
  3. 是否在有效期内
  4. 订单金额是否满足使用门槛
  5. 更新订单金额,计算实付金额
  6. 把优惠券状态改成"已使用"

我一开始把这些逻辑全部写在一个视图函数里,代码一团糟。后来重构时用了一个简单的方式——把校验抽成一个独立函数,返回(是否成功, 错误信息, 优惠券实例)三元组,视图函数里直接调用,逻辑清晰,测试也方便。

def validate_coupon(user, coupon_id, order_amount): try: coupon = CouponInstance.objects.select_for_update().get( id=coupon_id, user=user ) except CouponInstance.DoesNotExist: return None, "优惠券不存在" if coupon.status != CouponStatus.UNUSED: return coupon, "优惠券已使用或已过期" if coupon.expire_time < timezone.now(): return coupon, "优惠券已过期" if order_amount < coupon.template.min_amount: return coupon, "订单金额未达到使用门槛" return coupon, None

这里用到了select_for_update(),它会对这条优惠券记录加上行级锁,避免两个人同时使用同一张券。这个细节极其重要,不加的话,高并发下可能出现同一张券被使用两次,最后订单金额少收了的BUG。现在你还觉得优惠券模块简单吗?很多上线后的资损事故,往往就出在这种"看起来不需要锁"的代码里。

另外,优惠券的发放方式我也做了几种:新人注册自动发券、活动页面手动领取、用户分享之后赠送。不同发放方式的优惠券我通过模板的issue_type字段区分,这样后续统计不同渠道的转化率也有数据支撑。

4. Vue前端搭建与后端API接口对接实录

4.1 Vue项目初始化和环境配置

前端的第一个坑就是环境安装。Node.js版本不要用太新的,我当时用的Node 16.20(Vite 4对16是友好兼容的),配上npm 8。安装Vue项目用官方脚手架:

npm create vue@latest

新版脚手架会询问路由、Pinia、ESLint等特性,按需选择。注意这里有个很多人忽略的细节:Vite默认开发服务器端口是5173,而Django跑在8000端口,跨域问题从这一刻就注定了。解决办法在后端配跨域,我用的插件是django-cors-headers,只需要在settings.py里加上CORS_ALLOWED_ORIGINS配置,把http://localhost:5173加进去,前端npm run dev,后端manage.py runserver,两边数据才能通。

但你要是以为配完跨域就万事大吉,那就太天真了。开发期这样没问题,部署上线时前后端不在同一个域名下,Cookie方案就不能用了,Token必须放在请求头里。这个我在6.1节还会讲。

4.2 axios请求封装与路由拦截

Vue这边,我对axios做了一个统一封装。所有的请求都走一个实例,自动携带Authorization请求头,加上统一的错误处理。这一层封装是所有Vue项目的标配,但很多新手是每写一个组件就import axios from 'axios',代码重复不说,后期改请求地址或加鉴权逻辑时,改到你怀疑人生。

我封装的axios实例核心逻辑:

const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { router.push('/login'); } return Promise.reject(error); } );

路由拦截也很重要。商城的一些页面要求用户登录(比如收银台),商家后台的页面要求必须是商家角色才能访问。我用了Vue Router的beforeEach全局守卫,每次路由跳转前检查meta里声明的requiresAuth和requiresRole,配合Pinia里存用户信息,做了一次干净的访问控制。这个守卫逻辑是整个前端权限的核心,模板项目里基本可以直接抄走。

4.3 前后端联调的那点事

联调阶段,我反复遇到的一个问题是接口返回结构不统一。后来我在后端DRF里统一了所有接口的返回格式:成功时返回{ code: 0, data: 实际数据, message: "ok" },失败时返回{ code: 非0, data: null, message: "具体错误" }。这样前端封装的响应拦截器只需要判断code,不用每个接口单独处理错误,代码量直接砍半。

还有一个容易被忽略的点:时间格式。Django的DateTimeField序列化后默认返回ISO格式的字符串,比如2024-06-01T12:30:00Z。如果前端直接用,它会当成UTC时间处理,跟北京时间相差8小时。我当时在Vue里写了一个统一的formatDateTime过滤器,把ISO字符串转成YYYY-MM-DD HH:mm的本地时间格式。这个细节你要是等到上线了才发现,用户投诉来得比你的修复还快。

对了,前端播放m3u8格式视频的需求(比如商家上传服务案例视频)也是项目中后期才加进来的。m3u8本质是一个索引文件,浏览器默认不支持直接播放,我在项目里引入了hls.js库,十几行代码就能搞定,比什么video标签直接放链接靠谱得多。核心代码:

import Hls from 'hls.js'; if (Hls.isSupported() && videoEl) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(videoEl); }

5. 数据库设计与高并发场景下的细节打磨

5.1 核心数据表结构与关联关系

前后端项目做到一定规模,数据库设计的合理与否直接决定了后期的开发效率。这个项目我总共设计了多少张表?大概二十几张吧。但核心的业务表就几张:用户表、商家表、店铺表、商品表、订单表、订单详情表、优惠券模板表、优惠券实例表、审核记录表。

几个关键的关联关系你要特别注意:

  • 用户和商家的关系。一个用户不能既是普通用户又是商家?其实在真实场景里是可以的——用户A可以是保洁服务的消费者,同时自己也经营一家维修店。所以商户信息没有直接加到用户表上,而是通过一个一对一关系的商家表(Merchant)来挂接。这样用户表保持简洁,商家信息集中管理。
  • 订单和商家、商品的关系。订单表里冗余了一个shop_id字段。虽然通过订单详情可以反查到商品再反查到店铺,但如果每次都要多表查询才能知道订单属于哪个商家,性能上惨不忍睹。冗余字段在这个场景里是值得的。
  • 订单和订单详情是一对多关系。一个订单可以包含多个服务商品,每个服务商品可能是不同商家的。这里有一个现实问题:用户一次下单买了三个不同商家的服务,订单怎么拆?我的方案是一个订单统一归到第一个商品的商家名下,同时为每个商品单独生成一个子订单,用于商家端独立接单。这个方案的好处是,用户端看到一个总订单、支付一次;商家端各自处理各自的子订单。虽然一开始建表时多费了点心思,但后期功能迭代起来非常顺。

5.2 优惠券并发超发的经典解决方案

优惠券的并发问题,是很多做营销系统的开发者的噩梦。想象一个场景:活动上线,1000张券瞬间被秒杀,同时有2000个请求打过来。如果代码是"先查一下还有没有库存,有就发",那在极端情况下,会有多个请求同时看到"还有最后一张券",然后全部发放成功——超发就此发生。

解决思路并不复杂,关键在一条SQL和一把锁。

第一种方案是乐观锁。在数据表里加一个version字段,更新时用UPDATE ... WHERE id=? AND version=旧版本,如果更新影响行数为0,说明版本对不上,就重试或失败。

第二种方案是数据库行锁,也是我实际使用的方案。在发券时直接对优惠券模板记录加锁:

with transaction.atomic(): template = CouponTemplate.objects.select_for_update().get(id=template_id) if template.remaining_count <= 0: return "优惠券已被抢完" template.remaining_count -= 1 template.save() CouponInstance.objects.create(user=user, template=template)

select_for_update会把这个模板的记录锁住,其他请求的同类操作只能排队等待,有效避免了超发。但是这个方案有一个隐藏代价:每秒能发放的优惠券数量受到了数据库行锁的限制,在超大流量下吞吐量会不足。对于我这个体量的项目,完全够用;如果是大厂双十一级别的抢券,那就要上Redis + Lua脚本做原子扣减了,那是另一个话题。

5.3 商城订单支付状态与退款流程

虽然这个项目的支付我接的是沙箱环境,但订单状态的设计我还是花了不少时间。订单状态我用了整型枚举:待支付(1)、已支付(2)、商家已接单(3)、服务完成(4)、已取消(5)、退款中(6)、已退款(7)。

这个状态机的核心规则是:不能任意跳转。比如"待支付"只能去"已支付"或"已取消";"已支付"只能去"商家已接单"或"退款中";"服务完成"只能去"已退款"。我在代码里把所有允许的状态转换写成了一个字典,每次更新订单状态前先校验合法性。这样做的意义是,防止程序bug或者恶意请求把订单状态搞乱。

退款流程我设计成"先冻结优惠券补偿,再退钱"。用户用优惠券下单后来退款,优惠券要不要归还?我的策略是:如果订单在优惠券有效期内退款,则优惠券归还;如果退款时优惠券已过期,则折现为平台余额返还用户。这块规则虽然简单,但涉及的钱都是真金白银,建议你开发这类系统时把退款规则写成配置,方便运营随时调整。

6. 项目实战中的常见问题与排查技巧

6.1 跨域问题:开发环境与生产环境的双重解决方案

跨域问题是前后端分离项目里的头号拦路虎。开发阶段我用django-cors-headers解决,配置如下:

INSTALLED_APPS = [ # ... 'corsheaders', ] MIDDLEWARE = [ # ... 'corsheaders.middleware.CorsMiddleware', ] CORS_ALLOWED_ORIGINS = [ 'http://localhost:5173', 'http://127.0.0.1:5173', ]

注意:CorsMiddleware最好放在CommonMiddleware之前,否则某些情况下(比如使用APPEND_SLASH重定向时)跨域头会丢失。

生产环境我建议换一种解法——Nginx反向代理,让前端和后端同域或者用Nginx直接转发接口。简单说,Nginx把所有/api/前缀的请求转发到Django进程,其余请求去拿Vue打包后的静态文件。这样浏览器视角只有一个域名,根本不存在跨域,比改响应头更干净。

Nginx关键配置:

server { listen 80; server_name your-domain.com; location /api/ { proxy_pass http://127.0.0.1:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /srv/www/万事屋前台/dist; try_files $uri $uri/ /index.html; } }

try_files那一行很重要,它是Vue Router的history模式能正常刷新而不404的钥匙。如果你用的是hash模式,可以跳过;但只要用了history模式,这行配置是必须的。

6.2 DRF序列化性能优化:别让N+1查询拖垮接口

写DRF接口时,新手最容易犯的错误就是N+1查询。什么是N+1?最典型的场景是:查询商品列表,你用ServiceProduct.objects.all()拿到50个商品,然后序列化时,每个商品都需要查一次它的店铺名称——结果一次列表接口产生了51条SQL查询。数据量小的时候没感觉,数据一涨接口立刻变慢。

解决办法是select_related和prefetch_related。在这个例子里,商品查询改成:

ServiceProduct.objects.select_related('shop').all()

select_related会把外键关系也用JOIN一次性查出来,50条商品一条SQL就能带出店铺信息。对于多对多关系或者反向外键,用prefetch_related则在内存里做关联。

我在做优化时,把所有涉及列表页的接口都检查了一遍,只改了两行代码,接口响应时间就从700ms降到了90ms左右。对,你没有看错,就两行。所以性能优化不是玄学,大部分时候就是看你的查询有没有写对。

我还配合用了一个工具叫django-silk,它会在页面下面记录每个请求执行的SQL数量和时间。排查慢接口时先打开它看SQL列表,定位问题再动手,不要凭感觉瞎优化。

6.3 调试阶段的实用小技巧与经验总结

最后分享几个实战过程中沉淀下来的小技巧。

第一,Django的开发环境debug日志一定要分级别配置。我在settings.py里把SQL日志打开(logging模块配一个django.db.backends的handler),这样每个接口执行的SQL语句全部打印在控制台。排查ORM问题时,这个日志比任何debugger都直观。但生产环境一定要关掉,否则大流量会打爆你的磁盘。

第二,Vue前端开发时,Network面板的Disable cache选项记得勾上。否则改完代码刷新,浏览器还给你旧的JS文件,你会误以为是后端没改。这个小问题浪费了我一个下午。

第三,代码版本管理一定要用Git,而且commit信息按功能写清楚。"fix coupon bug"这种一句话还行,比"update"强太多。我习惯格式是feat(module): description,比如feat(coupon): 新增满减优惠券活动类型。回滚的时候,这种日志能帮你快速定位到具体改动。

第四,数据库迁移文件不要乱删。python manage.py makemigrations生成的迁移文件记录了表结构的演变史,它是Django判断表结构是否同步的依据。删了之后,在别人的电脑上运行migrate就可能会报错,说某个迁移文件找不到。正确做法是所有迁移文件进Git、跟随项目走。

第五,在整个项目里,我养成了"接口层永远不写复杂业务逻辑"的习惯。复杂逻辑放到Service层(你可以在Django里建一个services.py模块),视图函数只做参数校验、调用服务和序列化返回。作用是,测试的时候,我可以直接对Service层的函数做单元测试,不用启动HTTP服务、不用造HTTP请求数据,测试速度快一个量级。尤其是优惠券核销这种涉及金额的逻辑,没有单元测试,我是不敢上线改动的。

我个人在这个项目里获得的体会是:一个平台型项目,技术难点往往不那么惊天动地,真正的门槛在于边界感——商家和用户的边界、订单和优惠券的边界、开发期和生产环境的边界。把边界理清了,代码自然不会烂到哪去。我这套代码里有很多地方现在看来还可以继续优化,但作为一个从零到一跑通全流程的项目,它的价值已经达到了我最初的预期。如果后续继续迭代,我会考虑把商家端的营收统计做成更丰富的可视化报表,再把优惠券的核销逻辑抽成一个独立的微服务。不过那都是下一步的事了,先把当前这套结构吃透,你在类似项目里就能少走很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询