Django+微信小程序开发预约系统:时段冲突、订单状态与排班实战
发布时间:2026/9/26 7:45:32来源:尧图网络
最近帮一个开化妆工作室的朋友倒腾了一套线上预约小程序前后从需求梳理到上线跑了差不多三周。这个项目正好是典型的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需要自己配DRFAPI框架配合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_length128, uniqueTrue) avatar models.URLField(头像, blankTrue) phone models.CharField(手机号, max_length20, blankTrue) class Meta: db_table user # 化妆师 class Stylist(models.Model): name models.CharField(名字, max_length50) avatar models.URLField(头像, blankTrue) desc models.TextField(简介, blankTrue) level models.CharField(级别, max_length20, choices( (junior, 初级), (senior, 高级), (chief, 首席) ), defaultjunior) status models.BooleanField(是否上架, defaultTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table stylist # 服务项目 class ServiceItem(models.Model): name models.CharField(服务名称, max_length100) # 如新娘妆、晚宴妆 category models.CharField(分类, max_length20, choices( (bride, 新娘妆), (party, 晚宴妆), (photo, 写真妆), (daily, 日常妆) ), defaultdaily) price models.DecimalField(价格, max_digits8, decimal_places2) duration models.IntegerField(预计耗时分钟, default60) cover models.URLField(封面图, blankTrue) class Meta: db_table service_item # 预约订单 class Booking(models.Model): STATUS_CHOICES ( (pending, 待支付), (paid, 已支付/待服务), (serving, 服务中), (finished, 已完成), (cancelled, 已取消), (refunding, 退款中), ) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name客户) stylist models.ForeignKey(Stylist, on_deletemodels.PROTECT, verbose_name化妆师) service models.ForeignKey(ServiceItem, on_deletemodels.PROTECT, verbose_name服务项目) booking_date models.DateField(预约日期) start_time models.TimeField(开始时间) end_time models.TimeField(结束时间) status models.CharField(状态, max_length20, choicesSTATUS_CHOICES, defaultpending) remark models.TextField(客户备注, blankTrue) total_amount models.DecimalField(订单金额, max_digits8, decimal_places2) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table booking # 关键约束同一化妆师同一时段不能有两条非取消订单 constraints [ models.UniqueConstraint( fields[stylist, booking_date, start_time], nameunique_stylist_slot, condition~models.Q(statuscancelled), nameunique_active_booking ) ] # 评价订单完成后可写 class Review(models.Model): booking models.OneToOneField(Booking, on_deletemodels.CASCADE) rating models.IntegerField(评分, default5, choices[(i, str(i)) for i in range(1, 6)]) content models.TextField(评价内容, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table review这里有个很多人一开始容易忽略的点ForeignKey的on_delete参数。微信小程序端的用户记录和订单关系紧密但化妆师、服务项目这些基础数据如果被删除订单就没了参照所以化妆师和服务项目我用的是PROTECT禁止直接删除。用户我用的CASCADE不过实际业务中一般也不会删用户。2.2 预约时段与排班设计光有订单表还不够你得让客户知道哪天能约、约哪个时辰。这块我是额外建了一张排班表让后台像日历一样给每个化妆师配置可约时段。class StylistSchedule(models.Model): stylist models.ForeignKey(Stylist, on_deletemodels.CASCADE, verbose_name化妆师) work_date models.DateField(日期) start_time models.TimeField(开始时间) end_time models.TimeField(结束时间) is_available models.BooleanField(是否可约, defaultTrue) 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_availableTrue的记录再排除已经被订单占用的时段剩下的就是可预约时段。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 预约页面的日期间段选择小程序端最核心的交互就是预约下单。我的实现思路是三步进入预约页时传一个stylistId先调接口获取服务项目列表和未来7天可约日期。用户点击某个日期后再调接口获取这个日期的时段列表。选中时段后填写备注提交订单。这里的日期处理有几个细节。小程序官方picker组件的modedate只能选到日要限制可选日期区间需要设置start和endpicker modedate start{{today}} end{{maxDate}} bindchangeonDateChange view classdate-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 ( fhttps://api.weixin.qq.com/sns/jscode2session f?appid{APP_ID}secret{APP_SECRET}js_code{code} fgrant_typeauthorization_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: 登录失败}, status400) openid res[openid] user, created User.objects.get_or_create( openidopenid, defaults{username: fwx_{openid[-8:]}} ) token, _ Token.objects.get_or_create(useruser) return Response({token: token.key, nickname: user.username})这里我是用了rest_framework.authtoken来做登录态。真实项目中更建议用JWT但对小程序项目Token也完全够用实现简单、没有过期时间管理上的麻烦。用户昵称头像在小程序端可以通过wx.getUserProfile获取然后调用一个上传接口更新到User表。4. 后端API与预约核心逻辑4.1 接口设计清单后端我用的Django REST FrameworkDRF。按业务模块拆接口前后端各司其职接口文档用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?date2025-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开始数据库的唯一约束是拦不住的因为开始时间不同但实际已经重叠了。所以创建预约的接口必须做两件事查出所有已占用时段做重叠判断加锁或事务避免并发下两个请求同时通过校验我的实现是在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_idstylist_id, work_datebooking_date ) if not schedules.exists(): raise APIException(该日期没有可约排班) service ServiceItem.objects.get(idservice_id) end_time (datetime.combine(booking_date, start_time) timedelta(minutesservice.duration 10)).time() # 查这个时间段是否与已有订单重叠 conflict Booking.objects.filter( stylist_idstylist_id, booking_datebooking_date, status__in[pending, paid, serving] ).exclude( Q(end_time__ltestart_time) | Q(start_time__gteend_time) ).exists() if conflict: raise APIException(该时段已经被预约请选择其他时间) booking Booking.objects.create( useruser, stylist_idstylist_id, service_idservice_id, booking_datebooking_date, start_timestart_time, end_timeend_time, total_amountservice.price, statuspending ) 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(statusfinished) mark_finished.short_description 批量标记为已完成Django Admin实际使用时还有个技巧在list_display里如果搭配admin.display(description...)可以自定义显示字段比如把订单按钮待服务显示成中文标签这个看需求不展开。5.2 本地开发环境怎么起本地跑通整个项目是很多人容易卡住的一步。我建议按这个顺序操作创建虚拟环境python -m venv venv然后激活Windows用venv\Scripts\activatemacOS/Linux用source venv/bin/activate。安装依赖pip install django djangorestframework django-cors-headers pymysql mysqlclient。创建Django项目django-admin startproject config .然后python manage.py startapp api。配置settings.py注册rest_framework、corsheaders、api三个app配置MySQL连接本地没MySQL就用SQLite先跑。python manage.py makemigrations和python manage.py migrate建表。python manage.py createsuperuser创建管理员。python manage.py runserver启动Django。这里要提醒的是如果你先写好了models.py再把django.contrib.auth里自定义User表替换掉可能会遇到迁移冲突。最好的顺序是一开始就设置AUTH_USER_MODEL api.User再执行迁移不然中途换用户模型非常痛苦。5.3 生产部署要点本地跑通后部署到云服务器上才能被小程序正式调用。我的部署方案是经典 Nginx Gunicorn MySQLNginx监听443端口处理HTTPS和静态文件Gunicorn作为Python WSGI服务器跑Django应用MySQL建库配置好字符集为utf8mb4emoji能存微信小程序后台配置服务器域名Gunicorn启动配置大概是gunicorn config.wsgi:application -w 4 -b 127.0.0.1:8000 --timeout 60Nginx的核心配置片段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; } }静态文件的处理有个细节DjangoDEBUGFalse时不再自动提供静态文件需要先python manage.py collectstatic把静态文件收集到指定目录再让Nginx直接引用。5.4 上线前的检查清单项目上线前我整理了一份自查清单每条都是实战中踩过的坑小程序后台配置了 request 合法域名且域名已备案并配好HTTPS证书Django的ALLOWED_HOSTS加入了服务器域名DEBUG False关闭了Django调试页面MySQL数据库备份策略已配置至少每天备份一次文件上传接口如果做了作品图上传要限制文件类型和大小防止恶意上传订单超时未支付自动取消的定时任务已经部署用crontab或者Django-Celery6. 常见问题与排查技巧实录6.1 小程序request请求400/500这个问题90%出现在域名和HTTPS配置上。开发时可以在开发者工具里勾选不校验合法域名但真机预览时如果还是不行检查一下是否在微信公众平台里配置了合法域名。另外DjangoDEBUGTrue时的404页面在手机上也会被当成普通返回所以看到很长的HTML返回别急着找接口问题先看控制台打印的statusCode。6.2 预约时间的时区巨坑这个真的让我头疼过。小程序端用户在中国的时区是 UTC8但Django默认TIME_ZONE UTC。如果项目里混用了datetime.now()和timezone.now()很容易出现明明预约的是下午2点数据库里却存成早上6点的诡异问题。我的解决方案是所有时间字段统一用Django的DateTimeField(auto_now_addTrue)来存在用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 statusstatus里排除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: 请选择文件}, status400) # 保存文件建议放到专门的静态/媒体目录 ...图片存储这块小项目直接存本地服务器就好但要注意磁盘空间。如果图片量大后面可以考虑换OSS对象存储公众号里搜一下Python上传图片到OSS的教程很多替换工作量不算大。6.5 Django执行查询删除对象时的抄袭错误有朋友会问我后台管理里删掉一个已经有关联订单的化妆师时报错怎么办。这就是我在models.py里用PROTECT的初衷它会在有外键关联时禁止删除避免产生孤儿数据。如果确实需要删掉化妆师并保留历史订单可以给Stylist加一个is_active字段做软删除后台不展示即可永远不要直接物理删除主表和从表有引用关系的数据。7. 最后分享几个我自己迭代后的心得项目做完之后有几个感触比较深。第一预约系统的难点永远在状态流转和并发控制不在页面多好看。建议在动手写代码之前先花时间画清楚状态图把什么状态下能做什么操作写出来哪怕只是写在纸上后面写代码就不会乱。第二Django的Admin是天然的运营后台但生产环境直接给管理员用之前一定要配置好权限。我给工作室建了两个管理员账号一个是老板看报表、管所有订单一个是化妆师只能看自己的排班和订单避免一个人手滑改错数据的情况。第三小程序端的性能优化图片别用原图列表页用oss?x-oss-processimage/resize,w_400这类压缩参数没上OSS就自己在后端生成缩略图。我家的小程序列表页一开始加载一堆高清作品图首屏慢到让人崩溃压缩后速度快了不止一倍。第四别忘了数据备份。这个项目因为是小体量我直接写了个cron每天凌晨3点用mysqldump备份一次备份文件保留7天。你若用云数据库一般自带自动备份但本地MySQL一定要自己配。如果接下来想扩展我觉得可以往这几个方向走对接真实微信支付、架设 WebSocket 让化妆师接单实时提醒、加一个基于用户历史偏好的化妆师推荐。我这个项目目前的推荐逻辑很简单就是按成交量排序够用但做深了会更有价值。这套系统从零到一跑通的完整思路和经验基本就是这些希望能给正在做类似项目或者想开店做预约的朋友一些参照。遇到具体问题欢迎按上面的排查思路先自查基本上80%的坑都能在文章里找到答案。
网站建设高端定制企业官网