简介:这是一套完整的微信小程序会议室预约系统源码,前端为微信小程序,后端基于Django框架,面向企业、机关单位或需要内部会议室管理的团队,帮助快速搭建线上预约、查询、审批等功能。系统包含管理员与普通用户视角,适合用作毕业设计、课程项目或企业二次开发基础。压缩包以zip格式提供,大小约750KB,共含106个文件,文件类型涵盖Python后台代码、JavaScript交互逻辑、JSON配置文件、WXML页面结构及WXSS样式表,也包含少量图片和说明文档,目录组织规范,便于按模块阅读。目前已有2154人参与下载学习。通过这份源码,读者可以了解小程序与Django服务端对接的完整流程,包括接口设计、数据模型搭建、页面渲染等关键环节,直接部署后即可运行演示。资源内附环境配置文件和基础页面素材,对希望快速上手小程序开发或Django后端开发的初学者同样具有参考价值。
1. 一套能直接改的会议室预约源码,后端调度逻辑比界面更值钱
做企业信息化相关工作的同行,大概率都遇到过会议室预约需求:看起来只是给前台加个排期表,真接需求后才发现,难的不是界面,而是同一个时间段的会议室不能重复预约,临时取消后时间片怎么释放。这套微信小程序会议室预约系统微信小程序+Django服务端后台源码,把小程序端和 Django 服务端后台源码都放进了一个包里:前端负责选会议室、挑日期时间、提交预约,后端负责房间维护、预约校验和管理员介入。想找一份能跑的微信小程序项目实例,可以直接把它当作可运行骨架来拆:开放预约、资产借用、活动室预定,都能在同样的调度模型上改。后端自带配置样例和源码,本地能起服务直接调试。
2. Django 服务端源码骨架:配置分层、数据模型与初始化
2.1 从 local_settings.py.default 看多环境配置
源码包里出现local_settings.py.default,说明项目采用“公共配置 + 本地覆盖”两层写法。settings.py保存所有开发环境一致的配置,比如 app 列表、中间件、模板路径;local_settings.py放机器相关的差异项,比如数据库连接串、SECRET_KEY、DEBUG是否开启。local_settings.py.default则是提交到仓库里的模板,记录本地配置需要哪些字段,团队新成员加入时复制改名即可。
cp local_settings.py.default local_settings.py python manage.py check --deploy复制命令之后紧跟check --deploy,是用来在启动业务前做一次静态巡检。它会输出当前配置中不安全的地方,例如DEBUG=True、ALLOWED_HOSTS缺失、SSL 配置不足。注意这只是一个参考检查,并不代表检查通过就是生产级安全;至少能帮你少踩DEBUG=True直接暴露堆栈信息的坑。
常见配置项的作用如下表,表格里的字段在复制模板后基本都要过一遍:
| 配置项 | 所在文件 | 作用 |
|---|---|---|
| SECRET_KEY | local_settings.py | 签名会话与密码重置令牌,不能公开 |
| DEBUG | local_settings.py | 调试模式,生产环境必须为 False |
| ALLOWED_HOSTS | settings.py | 允许访问的域名列表 |
| DATABASES | local_settings.py | 数据库类型、地址与账号 |
| TIME_ZONE | settings.py | 影响日期时间字段的存储与展示 |
这套拆分逻辑在稍大一点的 Django 项目里非常常见,换一台机器部署时只需要改local_settings.py,公共配置不会因为个人环境差异被污染,git 冲突也会少很多。
2.2 会议室与预约的 Django 数据模型
核心业务涉及两个模型,Room 和 Reservation。Room 描述资源本身,Reservation 描述人和会议室在某时间段的关系。“关系”这个词是关键:预约的本质不是一条简单记录,而是一个带时间边界的状态对象。
from django.db import models from django.contrib.auth.models import User class Room(models.Model): name = models.CharField(max_length=64, unique=True) capacity = models.IntegerField(default=10) location = models.CharField(max_length=128, blank=True) is_active = models.BooleanField(default=True) class Reservation(models.Model): room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name="reservations") user = models.ForeignKey(User, on_delete=models.CASCADE) date = models.DateField() start_time = models.TimeField() end_time = models.TimeField() title = models.CharField(max_length=128, blank=True) status = models.CharField(max_length=16, default="confirmed") created_at = models.DateTimeField(auto_now_add=True)room_id外键保持资源与预约的关联,删除会议室时on_delete=models.CASCADE会连带删除预约;如果业务要求保留历史,建议改成PROTECT,让管理员先处理历史记录再删会议室。date用DateField、时间用TimeField,模型层直接约束了格式,就不会出现“2025/01/18”和“2025-01-18”混存的脏数据。is_active用来下架房间而不是删除,比如装修中的会议室可以临时不展示。
status字段没有用choices写死虽然灵活,但后台录入容易出错;建议至少补上CHOICES常量约束。需要扩展业务模块时,用python manage.py startapp新建一个独立 app 放周边功能,保持这个主流程的模型表结构单一。
2.3 setup.cfg 与页面资源:工程规范说明
setup.cfg在源码包里的作用是统一开发约束,常见内容包括flake8的最大行宽、忽略规则,以及isort的导入排序方式。多人提交代码时,行宽和导入顺序不一致会产生大量无谓 diff;有了统一配置,代码评审的关注点才能落在业务逻辑上。.gitattributes则控制行尾符,Windows 上常见的 CRLF 不会在提交时污染整个文件。
源码包里的error.html是自定义错误页模板。Django 默认的 404/500 页面比较简陋,而且可能暴露调试信息;把error.html通过handler404、handler500绑定后,访客看到的是友好说明,线上环境也能少泄露堆栈细节。几张 jpg 图片,1.jpg、3.jpg、5.jpg是会议室轮播图或演示截图,room_demo.jpg更像某个会议室的示例图。Django 惯例是把这类静态资源放在static/目录,按static/rooms/路径分目录,比全部堆在根目录好维护。
2.4 初始化命令:把后端跑起来
拿到源码后,按下面的顺序把服务端启动。先建虚拟环境再装依赖,避免把系统 Python 环境弄乱。
cd django_server python -m venv venv source venv/bin/activate pip install -r requirements.txt cp local_settings.py.default local_settings.py python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000依赖清单一般是requirements.txt,如果包内换成了pyproject.toml,用pip install -e .安装即可。makemigrations根据当前模型生成迁移文件,migrate把迁移应用到数据库;改过模型字段之后这两条命令必须重新执行。createsuperuser创建 Django admin 的管理员账号,会议室和预约记录的维护都在 admin 里做。runserver 0.0.0.0:8000是为了让小程序开发者工具或同一局域网真机访问到后端;如果只在本机调试,直接python manage.py runserver更省事。
3. 预约冲突检测:时间段重叠判断与 Django ORM 查询写法
3.1 重叠区间的判定条件
同一个会议室能否被预约,唯一需要严格判断的就是时间区间是否重叠。一段时间由start_time和end_time两个端点确定,两个区间存在交集的条件是:已有预约的开始时间早于新预约的结束时间,并且已有预约的结束时间晚于新预约的开始时间。
existing.start_time < new.end_time existing.end_time > new.start_time以 10:00-12:00 的已有预约为例:新预约 09:30-11:00 在两个条件上都成立,确实冲突;新预约 12:00-13:00 不满足第一条(12:00 < 12:00 为假),说明只留下背靠背的衔接时段。大多数会议室系统都允许这种排列,因为两场会议在物理上并不重叠。真正要警惕的是跨天预约,如果模型里只有date字段、没有结束日期,跨天场景就需要额外设计,比如拆成两段子预约。
3.2 用 Django ORM 写重叠查询与状态过滤
冲突判断放在前端不可靠,前端禁用按钮不代表绕过请求就不能提交;必须放在 Django 视图或 service 层里做二次校验。
from .models import Reservation def is_conflict(room_id, date, start_time, end_time, exclude_id=None): qs = Reservation.objects.filter( room_id=room_id, date=date, status__in=["confirmed", "pending"], start_time__lt=end_time, end_time__gt=start_time, ) if exclude_id: qs = qs.exclude(id=exclude_id) return qs.exists()这段查询里,date=date把范围缩到同一天;status__in=["confirmed", "pending"]只考虑有效预约,cancelled和completed不参与占用;start_time__lt=end_time与end_time__gt=start_time对应上一节的两个不等式。注意参数顺序:新预约的end_time和start_time作为比较值,不要写成反方向,否则重叠判断会变成包含判断。
exclude_id用于修改预约时间:用户在已有一条预约的情况下改期,校验时要排除自己那条记录,否则永远与自身冲突。如果未来要支持多人同时抢同一个时段,还需要把查询放进transaction.atomic()并配合select_for_update()锁住已查到记录,避免两个请求同时通过校验;单机开发阶段可以暂不考虑。
3.3 取消、删除与过期处理
预约取消和记录删除是两件不同的事。用户取消后,后台依然需要看到这条历史,了解谁在什么时候约过又取消,所以用状态更新而不是删行。
# 用户取消:保留记录,释放时间片 Reservation.objects.filter(id=resv_id, user=request.user).update(status="cancelled") # 管理员清理:真正删除 Reservation.objects.filter(id=resv_id).delete()update()是 QuerySet 层面的批量更新,不走模型实例的save()方法,所以如果模型里重写了save()做审计日志,这里不会被触发;需要审计时改成先get()再修改实例字段后save()。.delete()会触发DELETESQL,记录一旦删除,后台筛选和统计都会少掉一条;非必要不调用。
过期预约的常见处理方式是状态流转为completed。要注意:过期只适用于“预约日期早于今天”或“预约日期是今天且end_time已过当前时间”的记录,不能简单把所有带start_time的记录都清掉,因为未来时段的预约仍然有效。这个判断写成管理命令后,用定时任务每天跑一次。
| status | 含义 | 是否占用时间片 |
|---|---|---|
| confirmed | 已确认 | 占用 |
| pending | 待审核 | 占用 |
| cancelled | 用户取消 | 不占用 |
| completed | 已结束 | 不占用 |
3.4 admin 后台配置与查询界面
默认 admin 列表也能看全部预约,但会议室一多就难用。定制list_display、list_filter和search_fields可以很快定位问题数据。比如某天哪几间会议室被谁占了,用date_hierarchy的日期层级下钻最快。
from django.contrib import admin from .models import Reservation @admin.register(Reservation) class ReservationAdmin(admin.ModelAdmin): list_display = ["room", "date", "start_time", "end_time", "user", "status"] list_filter = ["date", "room", "status"] search_fields = ["user__username", "title"] date_hierarchy = "date"list_display里的room和user是外键,Django 默认显示对象的__str__,如果没有定义友好的__str__,列表页会显示成Room object (1),非常难读。建议在模型里给Room.__str__返回self.name,给User用它的username。list_filter字段越多,页面加载越慢;date、room、status三个维度已经覆盖大多数排查场景。admin 界面美化优先级不高,先把字段和筛选逻辑做对,再考虑换主题皮肤。
4. 小程序端对接 Django API:登录态、请求封装与表单提交
4.1 小程序端目录结构与页面职责
原生微信小程序的页面按目录组织,每个页面目录下包含.wxml、.wxss、.js、.json四个文件。以这套系统为例,典型页面是会议室列表、会议室详情和我的预约;app.js里做全局登录初始化,app.json注册所有页面路由,这属于搭建微信小程序的流程中最基础的结构规范。页面间通过wx.navigateTo跳转,详情页从列表页接收room_id作为参数。
Django 端不使用模板渲染页面,只提供 JSON API。小程序端所有数据来自wx.request,所以这里天然是前后端分离架构。用 HBuilderX 改造同样适用:uni.request的调用方式与wx.request基本一致,保留utils/request.js这一层封装,原生页面迁到 uni-app 时只需要替换请求单体,业务逻辑不用重写。
4.2 封装 utils/request.js 并携带会话凭证
小程序没有浏览器的 Cookie 概念,身份信息一般放在请求头里。登录后,后端返回 token,前端存到本地缓存,之后每个请求都带上。
const BASE_URL = "https://your-domain.example/api"; function request(path, method = "GET", data = {}) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${path}`, method, 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) { wx.removeStorageSync("token"); reject(res); } else { reject(res); } }, fail: reject, }); }); } module.exports = { request };这段封装的要点在于:getStorageSync("token")从本地缓存读取会话凭证,没登录时为空字符串;后端返回 401 时主动清掉 token,避免后续请求反复带一个失效凭证。Promise包装让业务层可以用async/await写调用,比层层嵌套success回调好读很多。如果后端把所有业务结果都包在{ code, msg, data }里,再把res.statusCode === 200的条件改成res.data.code === 0,通用性更好。BASE_URL在开发期可以指向局域网 IP,比如http://192.168.x.x:8000/api,联调阶段不用每次改代码。
提示:
wx.login()拿到的code是一次性的,有效期很短。应当把它立即传给 Django 后端,由后端换取会话身份,而不是在小程序端缓存code。
4.3 提交预约的表单参数与日期格式化
会议室详情页通常会有日期选择和时间段选择。微信原生picker组件返回的值是字符串,不要把datetime混在一起传。后端DateField解析YYYY-MM-DD,TimeField解析HH:mm,这是最省事的配合。
const payload = { room_id: currentRoom.id, date: e.detail.value.date, // "2025-01-18" start_time: e.detail.value.time, // "14:30" end_time: calcEndTime("14:30", 60), // "15:30" }; request("/reservations", "POST", payload) .then(() => wx.showToast({ title: "预约成功", icon: "success" })) .catch(() => wx.showToast({ title: "该时段已被约", icon: "none" }));calcEndTime的时间计算建议统一用分钟数累加:把HH:mm转成总分钟数,加上时长后再转回字符串。直接用字符串拼接会遇到 14:30 + 60 分钟等于 15:30 这类进位问题,也有 23:30 跨到次日 00:30 的边界。如果业务不允许跨天,计算后要判断结果日期是否还是当天,否则直接报错提示用户重新选时段。传参后端的room_id必须来自详情页路由参数或接口返回,不要信任前端自行构造的数字,后端视图里要再用Room.objects.filter(id=room_id, is_active=True)校验会议室存在且有效。
4.4 与后端 API 对应的接口清单
| 接口路径 | 方法 | 参数 | 说明 |
|---|---|---|---|
| /api/auth/login | POST | code | 小程序 code 换取 token |
| /api/rooms | GET | 无 | 可用会议室列表 |
| /api/reservations | POST | room_id, date, start_time, end_time | 创建预约 |
| /api/reservations | GET | date 可选 | 查询我的预约 |
| /api/reservations/{id}/cancel | POST | 无 | 取消预约 |
/api/auth/login的流程是:小程序wx.login()拿到 code,传给 Django;Django 调用微信接口换取openid,再用openid找到或创建本地用户,最后签发一个业务 token 返回前端。这个 token 可以存成 Django 的Token模型,也可以用 JWT 自包含签名。源码包如果已实现其中一种,尽量保持原实现;如果登录还没实现,建议先做最简单的 token 表,不要一上来就上 JWT,调试成本更低。
5. 从源码跑通到上线:域名配置、mysqlclient 与数据落库排查
5.1 小程序请求 Django 服务端的域名配置
本地开发时,微信开发者工具可以打开“不校验合法域名”选项,让http://127.0.0.1:8000直接可用。真机预览和正式上线时,这个开关无效,必须到小程序管理后台的开发设置里登记服务器域名。域名需要是https,路径按/api/这样配置。
真机预览首次报request:fail时,优先检查这一步,而不是改代码。这个排查顺序能省下大量时间。
5.2 mysqlclient 在 Linux 上的安装
本地用 SQLite 跑通后,线上部署大多换 MySQL。Django 连 MySQL 时最常卡在mysqlclient编译失败,错误信息一般指向mysql_config找不到。
apt install default-libmysqlclient-dev build-essential pkg-config pip install mysqlclientdefault-libmysqlclient-dev提供 MySQL 客户端头文件,build-essential提供编译工具链,pkg-config让mysql_config能被正确找到。缺少这三个,pip install mysqlclient就会在编译阶段报错。装完依赖后再安装,再把DATABASES里的ENGINE从sqlite3改到django.db.backends.mysql,并填写生产数据库地址、账号和端口。
5.3 判断预约是否真的写进数据库
小程序端提示“预约成功”但后台列表看不到记录时,先确认是写库失败还是查看位置不对。用终端直接查库最直接:
sqlite3 db.sqlite3 "select id, room_id, date, start_time, end_time, status from appname_reservation;" python manage.py shell -c "from app.models import Reservation; print(Reservation.objects.count())"表名由 app 名加模型名拼接,比如 app 叫meeting,表就是meeting_reservation;不确定时先执行python manage.py showmigrations确认模型对应的表。线上 MySQL 换成SELECT id, room_id, date, start_time, end_time, status FROM meeting_reservation;在客户端里执行。计数条数比预期少,再翻 Django 日志看有没有IntegrityError,多半是外键或唯一约束被命中。
本文还有配套的精品资源,点击获取