☰
Django+微信小程序开发预约系统:时段冲突、订单状态与排班实战
2026/9/26 7:45:30 网站建设 项目流程

最近帮一个开化妆工作室的朋友倒腾了一套线上预约小程序,前后从需求梳理到上线跑了差不多三周。这个项目正好是典型的"Python后端 + 微信小程序"组合,技术栈涉及Django/Flask、小程序原生开发、MySQL数据库,做完之后我最大的感受是:预约类系统的核心难点根本不在"能不能做出来",而在"时间段怎么不冲突、订单状态怎么不混乱"。这篇文章就把整个项目的设计思路、核心代码、排坑过程全部摊开讲,适合正在做类似毕设、课设,或者想给自家小店做预约系统的朋友直接抄作业。

先交代一下背景:朋友的工作室有固定化妆师和兼职造型师,服务项目包括新娘妆、晚宴妆、写真妆、日常妆。之前客户全靠微信私聊约时间,经常出现"两个人撞同一个时段"或者"化妆师临时休息但客单没取消"的情况。所以这套系统的核心需求很明确:把化妆师的可预约时段可视化,让客户在小程序上自助选择时间段、下单、支付,工作室在后台统一管理订单和排班。

技术选型上我最终用了"Django + DRF + 微信小程序原生"的组合。标题里提到的Flask我也认真对比过,后文会专门聊怎么取舍。下面按项目推进顺序来写,从数据库设计到小程序页面,再到部署上线,思路尽量还原真实开发流程。

1. 项目整体设计与需求拆解

1.1 这个预约平台到底要解决什么问题

很多人一听到"预约系统",第一反应就是"不就是个表单提交吗",真做起来才发现坑全在细节里。拿化妆造服务来说,它有很强的时空独占性:一个化妆师在某个时间段只能服务一个客户,不像商品可以重复售卖。所以系统本质上是在解决"人(客户)和时间(可预约时段)的匹配问题"。

具体到业务层面,我梳理出三个核心痛点:

  • 时段冲突:化妆师上午10点到12点被约了,这个时段就不能再出现在其他客户的可选列表里。
  • 服务状态混乱:从"提交预约"到"已支付"再到"已完成",中间还有"改期""取消",每一步都要有明确的状态记录。
  • 排班灵活:化妆师会有请假、临时加班,后台必须能一键禁用某天的全部时段。

这三个点决定了系统的核心复杂度。后面的数据库设计、接口设计,全部是围绕它们展开的。

1.2 功能模块梳理

整个平台分成两个端:微信小程序端(客户用)和Web管理后台(工作室用)。

小程序端功能:

  • 微信授权登录,获取用户openid并建立本地用户体系
  • 首页展示化妆师列表,支持按服务项目筛选
  • 化妆师详情页:个人作品图、服务项目、价格、已开放的可预约日期
  • 选日期 -> 选时间段 -> 填备注 -> 生成订单 -> 模拟支付
  • 订单列表:查看待服务/已完成/已取消订单,已完成的可以评价
  • 个人中心:头像昵称、我的预约、联系客服

管理后台功能:

  • 管理员登录(Django Admin自带,安全可靠)
  • 化妆师管理:添加上下架化妆师,维护作品图
  • 服务项目管理:设置名称、价格、耗时、分类
  • 排班管理:为每个化妆师配置某天可预约的时段,支持批量禁用
  • 订单管理:查看所有订单、修改状态、取消订单、查看已支付金额
  • 简单统计:按日/周/月汇总订单量和营收

这些模块听起来多,但在Django的App结构里分得比较清楚,后面代码部分会细说。

1.3 为什么我选用Django而不是Flask

标题里面同时提到了Flask和Django,这俩确实是Python Web圈最常用的两个框架。我在项目开始时专门做了对比,这里直接说结论。

对比维度DjangoFlask
自带Admin后台有,开箱即用没有,需要自己写
ORM强大, migrations管理表结构方便用SQLAlchemy,需要自己配
DRF(API框架)配合djangorestframework非常成熟需要Flask-RESTful,生态弱一些
适合场景中大型项目、后台管理需求多轻量API、原型验证、小型服务
学习曲线稍陡,但套路固定简单,自由度高

这个项目的实际情况是:工作室非常需要一个可视化后台来管理化妆师和订单。如果我用Flask,后台每个页面都要手写HTML表单、处理权限、封装分页,工作量直接翻倍。而Django Admin几乎零成本就解决了70%的后台需求,我只需要在admin.py里注册模型、配置list_display和filter就可以了。

另外,预约服务的订单状态流转很复杂,Django的ORM和事务机制对这类业务支撑更好。所以我最终选了Django。但这不代表Flask不行——如果你做的是纯API服务、前端完全自己写、后台也用Vue/React自己搭,那Flask确实更轻快。

2. 数据库设计与核心业务表

2.1 核心模型设计

数据库是整个系统的地基。我在设计models.py时重点考虑了"用户、化妆师、服务项目、预约订单、排班"这五个核心实体。下面是核心模型的简化代码,注意我加了不少业务字段,后面写逻辑会用到。

from django.db import models from django.contrib.auth.models import AbstractUser # 用户:继承Django自带用户,加上微信相关字段 class User(AbstractUser): openid = models.CharField('微信openid', max_length=128, unique=True) avatar = models.URLField('头像', blank=True) phone = models.CharField('手机号', max_length=20, blank=True) class Meta: db_table = 'user' # 化妆师 class Stylist(models.Model): name = models.CharField('名字', max_length=50) avatar = models.URLField('头像', blank=True) desc = models.TextField('简介', blank=True) level = models.CharField('级别', max_length=20, choices=( ('junior', '初级'), ('senior', '高级'), ('chief', '首席') ), default='junior') status = models.BooleanField('是否上架', default=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'stylist' # 服务项目 class ServiceItem(models.Model): name = models.CharField('服务名称', max_length=100) # 如新娘妆、晚宴妆 category = models.CharField('分类', max_length=20, choices=( ('bride', '新娘妆'), ('party', '晚宴妆'), ('photo', '写真妆'), ('daily', '日常妆') ), default='daily') price = models.DecimalField('价格', max_digits=8, decimal_places=2) duration = models.IntegerField('预计耗时(分钟)', default=60) cover = models.URLField('封面图', blank=True) class Meta: db_table = 'service_item' # 预约订单 class Booking(models.Model): STATUS_CHOICES = ( ('pending', '待支付'), ('paid', '已支付/待服务'), ('serving', '服务中'), ('finished', '已完成'), ('cancelled', '已取消'), ('refunding', '退款中'), ) user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='客户') stylist = models.ForeignKey(Stylist, on_delete=models.PROTECT, verbose_name='化妆师') service = models.ForeignKey(ServiceItem, on_delete=models.PROTECT, verbose_name='服务项目') booking_date = models.DateField('预约日期') start_time = models.TimeField('开始时间') end_time = models.TimeField('结束时间') status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='pending') remark = models.TextField('客户备注', blank=True) total_amount = models.DecimalField('订单金额', max_digits=8, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'booking' # 关键约束:同一化妆师同一时段不能有两条非取消订单 constraints = [ models.UniqueConstraint( fields=['stylist', 'booking_date', 'start_time'], name='unique_stylist_slot', condition=~models.Q(status='cancelled'), name='unique_active_booking' ) ] # 评价(订单完成后可写) class Review(models.Model): booking = models.OneToOneField(Booking, on_delete=models.CASCADE) rating = models.IntegerField('评分', default=5, choices=[(i, str(i)) for i in range(1, 6)]) content = models.TextField('评价内容', blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'review'

这里有个很多人一开始容易忽略的点:ForeignKey的on_delete参数。微信小程序端的用户记录和订单关系紧密,但化妆师、服务项目这些基础数据如果被删除,订单就没了参照,所以化妆师和服务项目我用的是PROTECT,禁止直接删除。用户我用的CASCADE,不过实际业务中一般也不会删用户。

2.2 预约时段与排班设计

光有订单表还不够,你得让客户知道"哪天能约、约哪个时辰"。这块我是额外建了一张排班表,让后台像日历一样给每个化妆师配置可约时段。

class StylistSchedule(models.Model): stylist = models.ForeignKey(Stylist, on_delete=models.CASCADE, verbose_name='化妆师') work_date = models.DateField('日期') start_time = models.TimeField('开始时间') end_time = models.TimeField('结束时间') is_available = models.BooleanField('是否可约', default=True) class Meta: db_table = 'stylist_schedule' unique_together = ('stylist', 'work_date', 'start_time')

排班的逻辑是:一个化妆师某天可能有多个时段(比如09:00-12:00、13:00-18:00),每个时段是一个独立记录,后台可以单独禁用。小程序端在展示时,先去查StylistSchedule中work_date >= 今天且is_available=True的记录,再排除已经被订单占用的时段,剩下的就是可预约时段。

2.3 订单状态机:生命周期要闭环

订单状态我设计成pending -> paid -> serving -> finished,这条主线之外还有cancelled和refunding。在每个状态转换点都要做业务校验。

  • pending可以cancel(用户未支付主动取消)
  • pending超时未支付,后台可把订单自动置为cancelled(我写了一个crontab脚本每天扫一遍)
  • paid状态要取消,走退款流程,先置为refunding,退款完成后置为cancelled
  • 化妆师开始服务,paid转为serving
  • 服务结束,serving转为finished,此时用户才能评价

实际开发中我建议不要允许"已支付订单任意跳转到已完成",每个状态转换都要在Service层封装一个方法,而不是随便改数据库字段。我踩过的坑就是最开始为了图方便直接在前端按钮里改状态,结果并发情况下状态乱套,最后还是老老实实把状态流转收敛到后端接口里。

3. 微信小程序端实现

3.1 小程序侧技术选型:原生还是uni-app

现在做一个微信小程序,首先得面临原生和跨端框架的选择。我之前用过uni-app,它确实能一套代码跑微信、支付宝、App。但这个项目我最终选了原生小程序开发,原因很实在:

  • 项目只需要微信端,不需要跨端
  • 原生小程序的组件和API最稳定,遇到问题查文档最方便
  • 预约流程不算复杂,原生写页面完全够用

如果你的工作室未来还想做抖音小程序、支付宝小程序,那可以考虑uni-app,但代价是框架本身的兼容性问题也会消耗不少时间。我认识的不少开发者用uni-app在微信上跑得好好的,一跑抖音就遇到各种API差异,调试起来更头疼。

3.2 小程序项目结构和请求封装

小程序端目录我习惯这样组织:

miniprogram/ ├── app.js ├── app.json ├── app.wxss ├── pages/ │ ├── index/ # 首页:化妆师列表 │ ├── stylist/ # 化妆师详情+服务项目 │ ├── booking/ # 预约下单页 │ ├── orders/ # 订单列表 │ ├── order-detail/ # 订单详情 │ └── mine/ # 个人中心 ├── utils/ │ ├── request.js # 封装wx.request │ └── date.js # 日期格式化工具

封装的request.js是最基本但最容易忽视的部分。我一开始在每个页面直接调wx.request,导致代码重复、错误处理混乱。后来统一封装:

const BASE_URL = 'https://api.yoursite.com/api/v1'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${path}`, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${wx.getStorageSync('token') || ''}` }, success: (res) => { if (res.statusCode === 200) { resolve(res.data); } else if (res.statusCode === 401) { // token失效,跳转登录 wx.navigateTo({ url: '/pages/login/login' }); reject(res); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res); } }, fail: (err) => { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { request };

这里有一个非常重要的点:小程序生产环境要求域名必须是HTTPS,而且要在小程序后台配置合法域名。开发阶段可以勾选"不校验合法域名",但上线前一定要去公众平台的"开发管理 -> 开发设置 -> 服务器域名"里把接口域名加进去,否则线上会请求失败。

3.3 预约页面的日期间段选择

小程序端最核心的交互就是预约下单。我的实现思路是三步:

  1. 进入预约页时,传一个stylistId,先调接口获取服务项目列表和未来7天可约日期。
  2. 用户点击某个日期后,再调接口获取这个日期的时段列表。
  3. 选中时段后填写备注,提交订单。

这里的日期处理有几个细节。小程序官方picker组件的mode="date"只能选到日,要限制可选日期区间需要设置start和end:

<picker mode="date" start="{{today}}" end="{{maxDate}}" bindchange="onDateChange"> <view class="date-text">{{selectedDate || '请选择日期'}}</view> </picker>

时段时间隔我设计成30分钟一个档位,但化妆服务一般一次1小时起,所以时段列表在接口里就直接过滤掉时长不足的组合。比如化妆师今天可约的原始排班是09:00-18:00,服务项目需60分钟,那18:00这个起点就不展示,因为约了做不完。这块逻辑放在后端做,前端只是接收"推荐可预约时段"数组。

另外要注意的是服务时长不等于占用的时段长度。化妆师给新娘妆可能要2小时,但中间可能有等待、收拾工具的时间,所以我在排班时预留了20%的缓冲,这个在计算结束时间时会体现。

3.4 微信登录与用户体系绑定

微信小程序获取用户身份的标准流程是:wx.login拿到临时code,发给后端,后端再用code换取openid(和session_key)。这步必须在后端做,不能在前端直接把code当身份标识。

Django端的简单实现:

import requests from rest_framework.views import APIView from rest_framework.response import Response from .models import User from rest_framework.authtoken.models import Token APP_ID = '你的appid' APP_SECRET = '你的appsecret' def code2session(code): url = ( f'https://api.weixin.qq.com/sns/jscode2session' f'?appid={APP_ID}&secret={APP_SECRET}&js_code={code}' f'&grant_type=authorization_code' ) resp = requests.get(url).json() return resp # 包含 openid 和 session_key class WxLoginView(APIView): def post(self, request): code = request.data.get('code') res = code2session(code) if 'openid' not in res: return Response({'msg': '登录失败'}, status=400) openid = res['openid'] user, created = User.objects.get_or_create( openid=openid, defaults={'username': f'wx_{openid[-8:]}'} ) token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key, 'nickname': user.username})

这里我是用了rest_framework.authtoken来做登录态。真实项目中更建议用JWT,但对小程序项目Token也完全够用,实现简单、没有过期时间管理上的麻烦。用户昵称头像在小程序端可以通过wx.getUserProfile获取,然后调用一个上传接口更新到User表。

4. 后端API与预约核心逻辑

4.1 接口设计清单

后端我用的Django REST Framework(DRF)。按业务模块拆接口,前后端各司其职,接口文档用drf-yasg或者手写Markdown都行。这里是我的接口清单:

模块接口方法说明
用户/api/v1/auth/loginPOST微信登录,传code拿token
用户/api/v1/user/profileGET/PUT获取/更新用户资料
化妆师/api/v1/stylistsGET列表,支持分类筛选
化妆师/api/v1/stylists/{id}GET详情+服务项目列表
排班/api/v1/stylists/{id}/schedulesGET获取某日期范围的可约日期
排班/api/v1/stylists/{id}/slots?date=2025-03-20GET获取某日的可约时段
订单/api/v1/bookingsPOST创建订单
订单/api/v1/bookingsGET我的订单列表
订单/api/v1/bookings/{id}/cancelPOST取消订单
订单/api/v1/bookings/{id}/payPOST模拟支付
评价/api/v1/bookings/{id}/reviewPOST提交评价

每个接口在DRF里对应一个ViewSet或者APIView。我习惯用ViewSet + ModelSerializer,代码量少,而且配合DRF的router自动生成URL,非常适合这种CRUD为主的项目。

4.2 预约冲突检测与并发处理

核心逻辑来了。前面数据库设计里我加了一个UniqueConstraint,不允许同一化妆师、同一日期、同一开始时间存在多条非取消订单。但光有数据库约束还不够,因为时段不是只有一个分钟点,用户选的可能是09:00开始、11:00结束,如果另一个用户选10:00开始,数据库的唯一约束是拦不住的(因为开始时间不同),但实际已经重叠了。

所以创建预约的接口必须做两件事:

  1. 查出所有已占用时段,做重叠判断
  2. 加锁或事务,避免并发下两个请求同时通过校验

我的实现是在Service层用一个事务包裹,先锁住化妆师当天记录再判断:

from django.db import transaction from django.db.models import Q from rest_framework.exceptions import APIException class BookingService: @transaction.atomic def create_booking(self, user, stylist_id, service_id, booking_date, start_time): # 先锁住化妆师当天的排班记录,防止并发 schedules = StylistSchedule.objects.select_for_update().filter( stylist_id=stylist_id, work_date=booking_date ) if not schedules.exists(): raise APIException('该日期没有可约排班') service = ServiceItem.objects.get(id=service_id) end_time = (datetime.combine(booking_date, start_time) + timedelta(minutes=service.duration + 10)).time() # 查这个时间段是否与已有订单重叠 conflict = Booking.objects.filter( stylist_id=stylist_id, booking_date=booking_date, status__in=['pending', 'paid', 'serving'] ).exclude( Q(end_time__lte=start_time) | Q(start_time__gte=end_time) ).exists() if conflict: raise APIException('该时段已经被预约,请选择其他时间') booking = Booking.objects.create( user=user, stylist_id=stylist_id, service_id=service_id, booking_date=booking_date, start_time=start_time, end_time=end_time, total_amount=service.price, status='pending' ) return booking

几点经验:

  • select_for_update()是行级锁,在事务里先锁定排班记录,其他事务修改同一排班会阻塞,这样能避免并发创建订单时都通过校验。MySQL的InnoDB支持行锁,SQLite不支持,所以生产环境一定要上MySQL。
  • 查询重叠订单用了一个反向排除的技巧:end_time <= 传入startORstart_time >= 传入end的是不重叠的,取反就是重叠。
  • 状态过滤里我把pending/paid/serving都算作占用,因为用户虽然没支付,但预留了这个时间段。下单后我会给一个15分钟支付倒计时,后端定时任务扫描超时未支付订单并释放时段。

4.3 支付与状态变化:模拟支付就够了

微信支付接入是需要商户号的,个人开发者和很多课设项目都没有。我在这里做了一个模拟支付:前端点击"支付"按钮,后端直接把订单状态从pending改为paid,同时记录支付时间。实际接微信支付的逻辑其实也差不多,就是调用wx.requestPayment发起支付,后端收到支付回调后再改状态。模拟支付的好处是不用商户号也能完整跑通业务流程。

关于预约提醒,我用了两种方式:一是小程序订阅消息,二是后端在用户下单成功后,通过站内消息记录提醒时间。WebSocket那套在小程序端做消息推送限制比较多,而且需要维护长连接,这个项目我用的是简单轮询:订单详情页每10秒拉一次最新状态。如果后面要做"化妆师确认订单"这种强实时功能,可以考虑用django-channels做 WebSocket,但要注意小程序不是所有页面都能长期保持WebSocket连接,最好只在订单详情页使用。

5. 管理后台与部署实战

5.1 Django Admin定制

Django Admin是选Django的一大红利。我只需要在admin.py里注册模型并配置展示字段:

from django.contrib import admin from .models import Stylist, ServiceItem, Booking, StylistSchedule, Review @admin.register(Booking) class BookingAdmin(admin.ModelAdmin): list_display = ('id', 'user', 'stylist', 'service', 'booking_date', 'start_time', 'status', 'total_amount') list_filter = ('status', 'booking_date', 'stylist') search_fields = ('user__nickname', 'stylist__name') date_hierarchy = 'booking_date' # 通过actions批量修改状态 actions = ['mark_finished'] def mark_finished(self, request, queryset): queryset.update(status='finished') mark_finished.short_description = '批量标记为已完成'

Django Admin实际使用时还有个技巧:在list_display里如果搭配@admin.display(description='...')可以自定义显示字段,比如把订单按钮"待服务"显示成中文标签,这个看需求,不展开。

5.2 本地开发环境怎么起

本地跑通整个项目是很多人容易卡住的一步。我建议按这个顺序操作:

  1. 创建虚拟环境:python -m venv venv,然后激活(Windows用venv\Scripts\activate,macOS/Linux用source venv/bin/activate)。
  2. 安装依赖:pip install django djangorestframework django-cors-headers pymysql mysqlclient。
  3. 创建Django项目:django-admin startproject config .,然后python manage.py startapp api。
  4. 配置settings.py:注册rest_framework、corsheaders、api三个app,配置MySQL连接(本地没MySQL就用SQLite先跑)。
  5. python manage.py makemigrations和python manage.py migrate建表。
  6. python manage.py createsuperuser创建管理员。
  7. python manage.py runserver启动Django。

这里要提醒的是:如果你先写好了models.py,再把django.contrib.auth里自定义User表替换掉,可能会遇到迁移冲突。最好的顺序是一开始就设置AUTH_USER_MODEL = 'api.User',再执行迁移,不然中途换用户模型非常痛苦。

5.3 生产部署要点

本地跑通后,部署到云服务器上才能被小程序正式调用。我的部署方案是经典 Nginx + Gunicorn + MySQL:

  • Nginx监听443端口,处理HTTPS和静态文件
  • Gunicorn作为Python WSGI服务器,跑Django应用
  • MySQL建库,配置好字符集为utf8mb4(emoji能存)
  • 微信小程序后台配置服务器域名

Gunicorn启动配置大概是:

gunicorn config.wsgi:application -w 4 -b 127.0.0.1:8000 --timeout 60

Nginx的核心配置片段:

server { listen 80; server_name api.yoursite.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name api.yoursite.com; ssl_certificate /etc/nginx/ssl/your.pem; ssl_certificate_key /etc/nginx/ssl/your.key; location /static/ { alias /var/www/yourproject/static/; } 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; } }

静态文件的处理有个细节:DjangoDEBUG=False时不再自动提供静态文件,需要先python manage.py collectstatic把静态文件收集到指定目录,再让Nginx直接引用。

5.4 上线前的检查清单

项目上线前,我整理了一份自查清单,每条都是实战中踩过的坑:

  • 小程序后台配置了 request 合法域名,且域名已备案并配好HTTPS证书
  • Django的ALLOWED_HOSTS加入了服务器域名
  • DEBUG = False,关闭了Django调试页面
  • MySQL数据库备份策略已配置,至少每天备份一次
  • 文件上传接口(如果做了作品图上传)要限制文件类型和大小,防止恶意上传
  • 订单超时未支付自动取消的定时任务已经部署(用crontab或者Django-Celery)

6. 常见问题与排查技巧实录

6.1 小程序request请求400/500

这个问题90%出现在域名和HTTPS配置上。开发时可以在开发者工具里勾选"不校验合法域名",但真机预览时如果还是不行,检查一下是否在微信公众平台里配置了合法域名。另外,DjangoDEBUG=True时的404页面在手机上也会被当成普通返回,所以看到很长的HTML返回别急着找接口问题,先看控制台打印的statusCode。

6.2 预约时间的时区巨坑

这个真的让我头疼过。小程序端用户在中国的时区是 UTC+8,但Django默认TIME_ZONE = 'UTC'。如果项目里混用了datetime.now()和timezone.now(),很容易出现"明明预约的是下午2点,数据库里却存成早上6点"的诡异问题。

我的解决方案是:所有时间字段统一用Django的DateTimeField(auto_now_add=True)来存,在用timezone.localtime输出;日期和时段的DateField/TimeField不涉及时区转换,原样存取。前端传过来的时间字符串,注意统一格式,我习惯用YYYY-MM-DD HH:mm,后端用parse_datetime做校验。

6.3 并发重复预约:唯一约束不够用

前面提到我加了唯一约束,但还是会有并发问题,比如两个用户同时看到09:00空着,同时点了提交,如果只靠先查后插,两个事务可能都通过了检查。解决办法是事务加select_for_update()锁行,这样第二个事务就会在排班记录上等待,第一个事务提交后,第二个会拿到最新状态再做检查。还有一个兜底方案是在MySQL层面再加一个唯一索引,包含stylist_id + booking_date + start_time + status(status里排除cancelled),双保险。

6.4 小程序端图片上传失败

工作室要上传化妆师作品图,小程序端用wx.uploadFile上传。这个地方有个常见误区:wx.uploadFile并不走wx.request的封装,它需要单独指定url和name参数。而且后端Django如果开启了CSRF校验(默认视图不需要,但DRF需要),在@api_view里上传接口用MultiPartParser才能解析到文件。

我最初在后端写了个普通APIView,没指定parser_classes,结果前端明明传了文件,后端request.FILES就是空的,排查了半天才发现是解析器没设。正确写法:

from rest_framework.parsers import MultiPartParser, FormParser class FileUploadView(APIView): parser_classes = [MultiPartParser, FormParser] def post(self, request): file = request.FILES.get('file') if not file: return Response({'msg': '请选择文件'}, status=400) # 保存文件,建议放到专门的静态/媒体目录 ...

图片存储这块,小项目直接存本地服务器就好,但要注意磁盘空间。如果图片量大,后面可以考虑换OSS对象存储,公众号里搜一下"Python上传图片到OSS"的教程很多,替换工作量不算大。

6.5 Django执行查询删除对象时的抄袭错误

有朋友会问我,后台管理里删掉一个已经有关联订单的化妆师时报错,怎么办。这就是我在models.py里用PROTECT的初衷,它会在有外键关联时禁止删除,避免产生孤儿数据。如果确实需要"删掉化妆师并保留历史订单",可以给Stylist加一个is_active字段做软删除,后台不展示即可,永远不要直接物理删除主表和从表有引用关系的数据。

7. 最后分享几个我自己迭代后的心得

项目做完之后,有几个感触比较深。

第一,预约系统的难点永远在状态流转和并发控制,不在页面多好看。建议在动手写代码之前,先花时间画清楚状态图,把"什么状态下能做什么操作"写出来,哪怕只是写在纸上,后面写代码就不会乱。

第二,Django的Admin是天然的运营后台,但生产环境直接给管理员用之前,一定要配置好权限。我给工作室建了两个管理员账号,一个是老板(看报表、管所有订单),一个是化妆师(只能看自己的排班和订单),避免"一个人手滑改错数据"的情况。

第三,小程序端的性能优化,图片别用原图,列表页用oss?x-oss-process=image/resize,w_400这类压缩参数,没上OSS就自己在后端生成缩略图。我家的小程序列表页一开始加载一堆高清作品图,首屏慢到让人崩溃,压缩后速度快了不止一倍。

第四,别忘了数据备份。这个项目因为是小体量,我直接写了个cron每天凌晨3点用mysqldump备份一次,备份文件保留7天。你若用云数据库,一般自带自动备份,但本地MySQL一定要自己配。

如果接下来想扩展,我觉得可以往这几个方向走:对接真实微信支付、架设 WebSocket 让化妆师接单实时提醒、加一个基于用户历史偏好的化妆师推荐。我这个项目目前的推荐逻辑很简单,就是按成交量排序,够用,但做深了会更有价值。这套系统从零到一跑通的完整思路和经验基本就是这些,希望能给正在做类似项目或者想开店做预约的朋友一些参照。遇到具体问题,欢迎按上面的排查思路先自查,基本上80%的坑都能在文章里找到答案。

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

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

立即咨询