
1. 项目定位与技术栈的真实组合方式第一次看到PythonDjangoSSM化妆品销售系统这个标题时我自己也愣了一下。Django是Python生态里的全栈Web框架SSM对应的却是Spring MVC、Spring、MyBatis这套Java技术栈这两组词放在一起严格来说是不成立的。但如果你在资源站、课设和毕设相关文章里搜过就会发现这种标题特别多发布方只是为了把热门框架关键词都挂上去让同一个仓库在搜索里同时命中两类用户。真正动手做项目时二选一即可要么纯PythonDjango要么纯JavaSSM两边都能把电商业务闭环做出来。这篇文章围绕的是PythonDjango实现的那一版。功能覆盖前台商品浏览、搜索筛选、SKU选择、购物车、结算下单、模拟支付、订单跟踪以及后台的会员、商品、库存、订单处理和基础数据统计。对选毕设题目、刚学完Django想做综合项目、或者想给垂直品类搭一套商城底子的同学来说参考价值都比较大。和商业系统相比这套项目最核心的价值不在于功能多而在于把电商业务链路完整跑通代码能讲清楚、能演示、能答得上原理。源码LW调试文档讲解这几个词也透露出项目的交付属性。LW通常是论文文档的简写配套调试文档和讲解说明说明这是个综合实践项目。这篇博文不展开论文怎么组稿重点放在系统设计和代码实现思路上尤其是化妆品这个垂直品类和通用电商平台的差异以及Django开发里容易翻车的细节。1.1 这套系统适合谁能覆盖哪些场景接触过的课设和毕设项目里绝大多数需求就一句话用户能注册登录、能看商品、能加购物车、能下单管理员能管商品、能管订单。化妆品销售系统单独拿出来做是因为它在商品属性和销售规则上有明显的垂直特征。第一点是SKU维度复杂。一瓶粉底液可能会有五个色号、三种规格、两种质地排列组合下来十几个SKU每个SKU的库存和价格还不一样。如果按普通商品表一张表存所有品名后面订单、库存、统计全都会乱套。第二点是运营后台需求重。化妆品涉及品牌、系列、功效、适用肤质、保质期、批次号等维度后台不仅要能增删改还要能快捷筛选、上下架、调库存。第三点是订单状态流转细。用户下单、付款、商家发货、确认收货、退款售后每一步都有时间点和记录要求系统里至少得有一个清晰的状态机。从使用场景来看这套系统适合三类人正在选毕设题目、需要一套能讲解的电商Demo的同学刚学完Django基础、想做一个综合项目的学习者小团队要做垂直品类商城、先做技术验证的开发者。把这套体系吃透以后换成图书、数码、鞋服底层逻辑完全通用业务层替换数据就行。1.2 为什么演示Demo要区分核心闭环和周边功能拿到需求先别急着建工程、写模型。我习惯先把功能分成两类核心业务闭环和周边功能。核心闭环指的是从商品展示到订单完成所必需的链路注册登录、浏览商品、查看详情、加入购物车、提交订单、模拟支付、订单状态更新、后台发货。这条链路必须完整且自洽任何一环断了演示效果都会大打折扣。周边功能包括评论、收藏、优惠券、充值、消息通知、数据报表大屏等。这批功能属于锦上添花一定要量力而行。如果时间只有两周先保证核心闭环稳定然后挑一两个周边功能做得漂亮一点比如首页轮播推荐和热销排行比做一堆半成品接口更能加分。我见过不少翻车案例代码里建了十几个app、几十张模型表看起来功能全覆盖结果订单状态改来改去对不上库存和销量也不一致答辩时一操作就露怯。先小后大、先通后全才是做这类综合项目的稳妥路径。真要把周边功能做全建议在核心闭环稳定之后再迭代否则越加越容易出问题。2. 化妆品SKU建模为什么不能照搬普通商品表很多同学的第一版数据表长这样商品表里直接放商品名、价格、库存、图片、描述所有信息平铺在一张表里。这个设计放在图书或通用小商品上勉强能跑放到化妆品上立刻出问题。举个具体例子。某品牌粉底液一共六个色号每个色号分30ml和50ml两种规格其中30ml版本又分正装和小样两种卖法。按照平铺表设计同一款商品要拆出二十几行数据。后台看到一堆名字相似但价格库存各异的行眼睛先就花了更麻烦的是用户在前台加购物车时对照色号规格的维度去定位查询条件漏一个加到购物车里的东西就不对了。正确做法是拆出SPU和SKU两层。SPU是商品的概念包含标题、主图、品牌、类目、详情描述等公共信息SKU才是实际可购买的最小库存单元价格、库存、规格属性都挂在SKU上。用户在前台看到某个SPU选择具体色号和规格后实际下单的对象是一个SKU。这个结构和主流电商平台的商品模型思路一致用在化妆品垂直商城上非常顺手。2.1 SPU与SKU的拆分顺带解决色号和规格的组合问题拆分之后规格值怎么存是第一个要决策的问题。常见方案有几种。第一种是给SKU表加固定字段颜色、规格、质地各加一列。优点是结构清晰但换一个品类字段就不够用改表很僵硬。第二种是用JSON字段存规格键值对比如{色号: 02, 规格: 30ml}。优点是灵活新增属性不用改表结构缺点是筛选时要用JSON查询写起来比普通字段稍绕。第三种是维护一张规格明细表把每个SKU的规格键值对记录成多行用外键关联。灵活度最高但联表变多对新手不算友好。在这个项目里我推荐用Django的JSONField方案。底层走数据库的JSON类型配合contains查询可以按规格值筛选性能和灵活度平衡得很好。查询类似SKU.objects.filter(spec_values__contains{色号: 02})对演示项目级别来说已经非常好用。JSONField还有一个好处前端在SKU选择面板上可以直接把选中的规格对象传回来后端存进去省去解析多个字段的麻烦。2.2 用Django模型把品牌、类目、商品、SKU串起来下面把项目核心模型贴出来。注意类目用了自关联实现层级品牌表独立商品和SKU分开建模# models/product.py from django.db import models class Category(models.Model): name models.CharField(max_length100) parent models.ForeignKey( self, nullTrue, blankTrue, on_deletemodels.CASCADE, related_namechildren ) sort_order models.IntegerField(default0) class Meta: ordering [sort_order] def __str__(self): return self.name class Brand(models.Model): name models.CharField(max_length120, uniqueTrue) logo models.ImageField(upload_tobrands/, blankTrue) description models.TextField(blankTrue) def __str__(self): return self.name class Product(models.Model): name models.CharField(max_length200) brand models.ForeignKey(Brand, on_deletemodels.SET_NULL, nullTrue, related_nameproducts) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue, related_nameproducts) main_image models.ImageField(upload_toproducts/) detail models.TextField(blankTrue) is_active models.BooleanField(defaultTrue) created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.name class SKU(models.Model): product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameskus) spec_values models.JSONField(defaultdict) price models.DecimalField(max_digits10, decimal_places2) stock models.PositiveIntegerField(default0) sales models.PositiveIntegerField(default0) is_active models.BooleanField(defaultTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (product, spec_values) def __str__(self): return f{self.product.name} {self.spec_values}这里有两个细节值得注意。第一个是unique_together约束。它保证同一个商品下不会出现两个完全相同的规格组合否则用户下单时系统根本分不清他选的是哪一个时间一长数据就乱了。要注意JSONField参与联合唯一约束在部分数据库版本下支持程度不一样开发阶段就要在目标数据库上验证。第二个是sales字段。销量是商品列表页排序的常用指标把销量冗余在SKU上订单完成后累加一次比每次去订单表做聚合快得多。这是刻意设计的冗余不是数据冗余的坏味道。2.3 购物车和订单模型的设计要点购物车和订单的模型同样要贴着业务走。购物车条目必须关联SKU不能关联Product因为同一个商品不同色号的价格和库存都不同加购物车时就必须把SKU定死。订单条目也要关联SKU同时快照商品名、规格描述和单价# models/order.py class Cart(models.Model): user models.ForeignKey(auth.User, on_deletemodels.CASCADE, related_namecarts) class CartItem(models.Model): cart models.ForeignKey(Cart, on_deletemodels.CASCADE, related_nameitems) sku models.ForeignKey(SKU, on_deletemodels.CASCADE) quantity models.PositiveIntegerField(default1) created_at models.DateTimeField(auto_now_addTrue) class Order(models.Model): STATUS_CHOICES [ (PENDING_PAYMENT, 待支付), (PAID, 已支付), (SHIPPED, 已发货), (COMPLETED, 已完成), (CANCELLED, 已取消), ] user models.ForeignKey(auth.User, on_deletemodels.PROTECT, related_nameorders) order_no models.CharField(max_length32, uniqueTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultPENDING_PAYMENT) total_amount models.DecimalField(max_digits10, decimal_places2) receiver_name models.CharField(max_length50) receiver_phone models.CharField(max_length20) receiver_address models.CharField(max_length200) remark models.TextField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) paid_at models.DateTimeField(nullTrue, blankTrue) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) sku models.ForeignKey(SKU, on_deletemodels.PROTECT) product_name models.CharField(max_length200) spec_values models.JSONField(defaultdict) unit_price models.DecimalField(max_digits10, decimal_places2) quantity models.PositiveIntegerField(default1)订单号推荐用时间戳加随机数生成格式类似20240601120000123456创建订单时查重有冲突就重新生成。收件人信息必须整体快照到订单里不能去读用户资料的实时值。否则用户改完收货地址历史订单的收货信息也跟着变后面发货、对账、售后全部会乱。3. 购物到履约的完整闭环代码实现模型定完之后接下来把整条业务链路串起来。我按注册登录—商品列表—加入购物车—结算下单—支付回调—后台发货—确认收货的顺序来讲每一步给出关键代码和背后的业务判断。3.1 登录、购物车和结算接口的核心逻辑登录直接用Django自带的authenticate和login就能搞定。关键做好两件事一是密码错误时的提示要友好二是登录后才能访问的接口统一加上权限控制用login_required装饰器即可未登录用户跳转到登录页。购物车用一张Cart主表和CartItem明细表。加入购物车的接口业务逻辑只有三步根据登录用户找到或创建购物车根据SKU id和数量创建或更新购物车条目返回购物车条目数和总金额。这里有个容易错的地方同一个SKU已经在购物车里时应当是数量加一而不是再插一条新记录。用get_or_create可以简化但要注意已存在记录的quantity累加逻辑# views/cart.py from django.shortcuts import get_object_or_404 from django.http import JsonResponse from .models import Cart, CartItem, SKU def add_to_cart(request): if request.method POST: sku_id request.POST.get(sku_id) quantity int(request.POST.get(quantity, 1)) sku get_object_or_404(SKU, idsku_id, is_activeTrue) cart, _ Cart.objects.get_or_create(userrequest.user) item, created CartItem.objects.get_or_create( cartcart, skusku, defaults{quantity: quantity} ) if not created: item.quantity quantity item.save() return JsonResponse({code: 0, msg: 加入成功})结算接口的逻辑稍多一点接收购物车条目ID列表遍历条目校验SKU是否在售、库存是否充足计算总金额创建订单和订单明细最后清空对应购物车条目。这几个步骤必须包在事务里任何一个SKU校验失败都整体回滚不能出现订单创建了一半、购物车却已清空的尴尬状态# views/order.py from django.db import transaction from django.shortcuts import get_object_or_404 from .models import Order, OrderItem, Cart, CartItem, SKU transaction.atomic def create_order(request): cart_item_ids request.POST.getlist(cart_item_ids) items CartItem.objects.filter( id__incart_item_ids, cart__userrequest.user ).select_related(sku__product) if not items: return JsonResponse({code: 1, msg: 没有选中商品}) total_amount 0 order_items [] for item in items: sku item.sku if not sku.is_active: return JsonResponse({code: 1, msg: f{sku.product.name} 已下架}) if sku.stock item.quantity: return JsonResponse({code: 1, msg: f{sku.product.name} 库存不足}) total_amount sku.price * item.quantity order_items.append(OrderItem( skusku, product_namesku.product.name, spec_valuessku.spec_values, unit_pricesku.price, quantityitem.quantity )) order Order.objects.create( userrequest.user, order_nogenerate_order_no(), statusPENDING_PAYMENT, total_amounttotal_amount, receiver_namerequest.POST.get(receiver_name), receiver_phonerequest.POST.get(receiver_phone), receiver_addressrequest.POST.get(receiver_address), remarkrequest.POST.get(remark, ) ) OrderItem.objects.bulk_create(order_items) items.delete() return JsonResponse({code: 0, order_id: order.id})生成订单号时可以把generate_order_no实现成时间戳加随机数的组合。事务内创建订单和图省事直接用items.delete()清空购物车前提是已经在函数最外层加transaction.atomic这样任何一步出错都会回滚。3.2 订单状态机从待支付到已完成的流转细节订单状态是整套系统的核心。我用的状态集合是待支付、已支付、已发货、已完成、已取消五个状态覆盖常规销售流程。设计上要规定好哪些状态能迁移到哪些状态待支付 - 已支付支付成功待支付 - 已取消用户主动取消或超时未支付已支付 - 已发货后台发货已发货 - 已完成用户确认收货已支付 - 已取消售后场景下管理员取消在代码层面我给Order模型加一个transition_status方法显式处理合法的状态迁移不让任意赋值拆掉状态机的约束# models/order.py class Order(models.Model): # ...字段省略 def transition_status(self, new_status): allowed { PENDING_PAYMENT: {PAID, CANCELLED}, PAID: {SHIPPED, CANCELLED}, SHIPPED: {COMPLETED}, } if new_status not in allowed.get(self.status, set()): raise ValueError(f非法状态变更{self.status} - {new_status}) self.status new_status self.save()状态迁移的触发点也要明确。支付回调里把待支付改成已支付并记录支付时间后台订单管理页面把已支付改成已发货用户确认收货把已发货改成已完成。每个动作对应一个明确的接口或后台按钮不要在页面里开放一个改状态的下拉框让操作者随意选。真需要灵活改状态也要在Service层走一遍校验逻辑最大程度保证数据可靠。3.3 库存扣减方案与防超卖处理电商系统最容易出事的环节就是库存。常见做法有两种下单减库存和支付减库存。下单减库存的优点是用户下单后库存即时被占用不会出现下单成功但一直没货的情况缺点是很多用户下单后不支付产生大量无效占用。支付减库存的优点是占用少但风险在于多人同时支付时扣到最后库存可能变成负数。对课设和毕设级别的项目我建议用下单时校验库存充足支付确认后扣减库存的折中方案。具体流程是创建订单时不扣库存但校验stock quantity支付回调成功后执行带条件的库存更新# services/stock.py from django.db.models import F def deduct_stock(sku_id, quantity): affected SKU.objects.filter( idsku_id, stock__gtequantity ).update( stockF(stock) - quantity ) if affected 0: raise ValueError(库存不足)update配合F表达式在数据库层完成条件扣减比先select再update更安全能避免并发时的超卖。答辩时虽然不太可能模拟高并发但这样写有得讲面试官问并发超卖怎么处理你可以从数据库条件更新这个角度给出有理有据的回答这就是区分度所在。订单完成后还要同步累加SKU的sales字段。同一次支付回调里做库存扣减和销量累加依然要包在事务里保证两个数字要么都更新要么都不动。4. 后台权限与商品管理admin体系的二次开发Django自带的Admin后台开箱即用但直接拿来当运营后台是不够的至少要做三件事自定义用户模型、划分角色权限、定制列表和表单页面。前期不做用户模型扩展后面再改会非常痛苦因为Django的AUTH_USER_MODEL在第一次迁移后就不能随意变更。4.1 自定义User模型与三方角色的权限划分一个化妆品销售系统里后台至少有三类角色超级管理员、运营人员、仓库发货人员。如果算上用户端其实是四方。在项目初期就使用AbstractUser扩展用户模型增加role字段比默认的User表合理得多。好处是权限判断时通过request.user.role就能分支后续想加角色也不用重构表# models/user.py from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES [ (ADMIN, 超级管理员), (OPERATOR, 运营人员), (WAREHOUSE, 仓库人员), (CUSTOMER, 普通用户), ] role models.CharField(max_length20, choicesROLE_CHOICES, defaultCUSTOMER)在settings.py里指定AUTH_USER_MODEL accounts.User注意这个配置必须在第一次迁移前设置好。角色权限的大致划分可以参考下面这张表角色可操作范围超级管理员全部权限包括用户管理、角色分配、数据统计运营人员商品上下架、SKU价格库存维护、活动与推荐位配置仓库人员查看待发货订单、执行发货、回填物流单号普通用户浏览商品、购物车、下单、确认收货、查看订单权限控制建议分两层做。接口层用自定义装饰器检查角色后台菜单层通过Admin的has_module_permission和has_change_permission控制不同角色能看到哪些模块。# decorators.py from functools import wraps from django.http import JsonResponse def require_role(*roles): def decorator(view_func): wraps(view_func) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({code: 401, msg: 未登录}) if request.user.role not in roles and not request.user.is_superuser: return JsonResponse({code: 403, msg: 无权限}) return view_func(request, *args, **kwargs) return wrapper return decorator仓库发货接口可以这样用require_role(WAREHOUSE, ADMIN)。运营账号调过来直接返回无权限把权限边界挡在视图层前面清晰又安全。4.2 让Django Admin真正能用的改造点直接注册模型后后台的商品列表通常每一行只显示一个SPU点进去才能看到SKU。这个交互对运营来说效率太低一个商品十来个SKU要层层点开才能看价格库存。比较推荐的改法是把SKU做成TabularInline在商品编辑页直接维护所有SKU# admin.py from django.contrib import admin from .models import Product, SKU, Brand, Category class SKUInline(admin.TabularInline): model SKU extra 0 fields [spec_values, price, stock, sales, is_active] admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [id, name, brand, category, is_active, created_at] list_filter [brand, category, is_active] search_fields [name] inlines [SKUInline]列表页加上品牌、类目筛选和名称搜索后台体验立刻不一样。订单页面也要定制默认列表要显示订单号、用户、金额、状态、下单时间。不建议直接使用list_editable去改状态因为绕过了transition_status校验。我的做法是列表页只做展示状态操作放到独立接口里后台页面通过按钮调用接口完成发货等动作。另一个容易被忽略的改造点是图片上传。ImageField上传后默认落在MEDIA_ROOT目录开发阶段直接可用。但MEDIA_URL和STATIC_URL必须区分开否则上线后图片和静态文件互相干扰。商品图片的命名建议加上时间戳或随机前缀防止同名文件互相覆盖后台维护时图片丢了很难排查是URL问题还是文件名问题。5. 联调、部署与常见坑位排查开发环境把功能跑通只是第一步。实际项目里从本地联调到部署上线会踩掉很多显性的坑。下面按问题类型梳理每一条都来自实际项目里的教训。5.1 图片上传和静态文件的坑Django项目里settings.py需要正确配置媒体和静态文件MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles本地调试时要让上传的图片能被访问还需要在urls.py里补一段MEDIA服务配置。很多同学漏掉这段后台明明显示上传成功前台图片却打不开排查半天发现只是URL路由没配# config/urls.py from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ...其他路由 ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)部署阶段如果用Nginx/media/和/static/这些静态请求通常由Nginx直接处理Django只负责动态逻辑。图片目录的写权限经常会出问题权限不够会导致上传失败而且后台报错往往不直观。我的做法是上传前先在前端校验图片大小后端再做扩展名白名单过滤单张图片限制在5MB以内既不担心磁盘膨胀也防止有人上传超大文件拖垮服务。5.2 支付接口模拟与回调处理真正接微信支付或支付宝需要商户资质和证书演示项目里不现实。常规做法是模拟支付前端下单后跳到模拟收银台页面点击确认支付系统生成支付记录把订单状态改成已支付。关键点在于回调处理不能只改状态。至少要建一张Payment表记录支付单号、订单号、支付方式、金额、支付时间把支付动作和状态变更分开记账后续对账和排错才有依据# models/payment.py class Payment(models.Model): payment_no models.CharField(max_length32, uniqueTrue) order models.ForeignKey(Order, on_deletemodels.PROTECT, related_namepayments) method models.CharField(max_length20, defaultmock) amount models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length20, defaultSUCCESS) paid_at models.DateTimeField(auto_now_addTrue)模拟支付回调中先用select_for_update锁住订单行再做状态校验和扣库存。锁行的目的是防止支付回调与用户手动取消订单并发执行时产生脏状态。演示项目虽然很少遇到并发但这个写法不复杂写上去就是专业细节。5.3 Windows/Linux两套环境的部署差异很多同学本地用Windows开发第一次往Linux服务器上部署就各种报错。最实用的建议是从第一天起就用虚拟环境隔离依赖不管是venv还是pipenv都行。依赖列表用requirements.txt固定版本pip freeze requirements.txt生成后换到服务器上执行pip install -r requirements.txt就能复现环境。Django部署到Linux时SECRET_KEY不要硬编码在代码里用环境变量或配置文件读取DEBUG必须设成False否则一行异常堆栈就会把服务器目录结构泄露给访问者。数据库方面演示项目用SQLite也能跑但如果你要展示并发处理和复杂查询建议直接用MySQL或PostgreSQLsettings.py把数据库配置改为环境变量读取本地和服务器各用一套.env文件。进程管理上Gunicorn加Nginx是Django最经典的组合。一个简化的启动方式是这样gunicorn config.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 3 \ --timeout 60Nginx配置里把/static/和/media/指向对应目录其余请求反代到8000端口。部署完成后要重点检查三项静态文件是否执行过collectstatic、媒体目录权限是否正确、数据库连接是否正常。这三个点都通过页面基本就能访问了。5.4 答辩演示时要准备的测试数据与演示脚本功能全都做完了答辩环节绝不能临时在界面上乱点。很多项目功能没毛病却在演示时因为数据太丑或者流程不连贯丢分。这里分享一套我常用的准备方案。测试数据准备两类。一类是海量示例数据20个品牌、50个SPU、每个SPU配3到5个SKU保证列表页有翻页效果、筛选有条件可选。另一类是剧本数据一个会员账号、一个运营账号、一个仓库账号每个账号对应一套预定的操作路径。数据可以通过管理后台手动录也可以写一个独立的初始化脚本一次性导入。演示脚本我按四步走第一步用普通用户账号演示逛店。从首页搜索口红筛选某个品牌点进详情页切换色号和规格加入购物车再买一件护肤品类商品顺便演示一下购物车里同一SKU数量叠加的效果。第二步结算下单填写收货地址走模拟支付支付成功后展示订单从待支付变成已支付。第三步切到仓库账号进后台看到待发货订单点发货并回填快递单号切回用户端刷新订单状态变成已发货。第四步确认收货订单变成已完成再回到管理后台看商品销量和库存变化顺便演示库存不足时下单被拦截的效果。每走一步对应讲解一下代码里这一块是怎么实现的。这样的演示节奏紧凑、逻辑闭环比临时在界面上乱点效果好太多。还有个细节演示前把浏览器缓存清理掉提前在无痕窗口里登录好三个账号避免演示时卡在登录环节。这个看似不起眼的小准备关键时刻能省下不少尴尬时间。最后说点我自己的体会。第一次搭这类系统时我也走过弯路。一开始照着大型电商平台做全功能数据表越设计越复杂写了两周连核心下单流程都没跑通。后来把范围砍到最小可闭环版本先让用户能完成一次完整购买再逐步加筛选、加后台优化、加统计功能项目反而快速推进下来了。多讲一个细节项目里的化妆品不是简单换几个商品名。SKU规格、肤质推荐、保质期这些维度的建模才是真正花心思的地方。把这套模型吃透下次换一个垂直品类只需要替换数据内容代码骨架可以直接复用。如果你准备做类似项目我的建议是三句话先画清楚业务闭环图再写模型层然后再补接口和页面。模型定错后面改起来成本极高模型一旦合理前后端就是顺水推舟的事。这篇内容的代码片段都是从项目中截取的完整项目里还包含前端页面、初始化脚本、配套文档和调试说明。照着上面的思路搭起来两周内出活很有把握。