基于Django的一站式家装服务管理系统设计与实现
花了大半个学期帮师弟改同一类毕设题目就是“基于Python的一站式家装服务管理系统”。第一次翻代码时发现不少同学只是把“装修公司”换成了“商城”预约、报价、施工进度全部硬塞进订单表状态字段直接用字符串if判断越往后写越乱。这个题要想做出彩关键不是能跑几个CRUD页面而是一开始就把“一站式家装”的业务链条理清楚再围绕链条做状态流和权限设计。这篇文章把我实际做这套系统的完整思路写出来从需求拆解、技术选型、数据库设计到核心代码、测试部署、答辩演示照着走能省下大量返工时间。这套系统适合什么场景呢业主装修时线上看案例、约量房、看方案、比报价、签单、盯进度、验收付款全程信息留在系统里装修公司一边接线索、分配设计师一边管理合同、材料、施工记录平台运营方则要有后台可以查看所有订单状态和数据统计。简单说系统要同时服务客户、设计师、公司管理员、项目经理这几类角色。正在做毕设或者想练手Python Web开发的同学这篇可以作为一份落地参考。1. 系统定位与需求拆解清楚“一站式”到底指什么1.1 家装业务链路里系统要管住哪些节点传统家装流程极其依赖电话和Excel客户信息往往分散在销售、设计师、项目经理的聊天记录里。业主从接触装修公司到最终入住中间经历大致是这样浏览案例、提交预约、量房、出平面方案、报价谈判、签合同、拆改水电、泥瓦木工油漆、安装、验收、付尾款、评价售后。每个节点都会产生角色转换比如预约之后变成客户签合同之后变成业主施工中又要靠项目经理维护工地。“一站式”并不是一句营销话术而是要把上面这一整条链路的业务对象建模到系统里。需要管理的对象至少包括用户与角色、装修公司与设计师、预约单、设计方案、报价单、订单/合同、施工项目、施工进度记录、材料库存、评价信息。如果哪张表缺失后面演示的时候就会卡壳。我见过不少同学只做了“用户、商品、订单”三张表结果预约流程完全没法闭环老师随便问一个“设计师怎么接单”就答不上来。1.2 三类核心角色的真实痛点和功能清单可以把这套系统当成一个多角色协作平台而不是单一的后台管理软件。不同角色登录之后看到的内容和操作权限完全不同。角色核心动作常用功能线下痛点业主/客户找公司、约量房、看方案、签约、盯进度浏览案例、预约、确认方案、查看进度、评价电话沟通容易忘、没有流程记录设计师接预约、传方案、做报价查看分配给我的预约、上传方案图、填写报价报价记录零散改口说不清公司管理员分配客户、审核方案、转施工单管理设计师账号、审核预约、生成订单、采购材料客户线索分散在个人微信里项目经理/运营推进工地、上传照片、更新状态给施工阶段打勾、传进度照片、发起验收工地情况靠拍照发群难汇总整理成表之后你会发现系统的核心不是界面做得多好看而是每个操作背后有权限规则和数据状态。例如业主不能直接把预约状态改成“施工中”设计师不能绕过方案报价直接生成订单项目经理只能更新自己负责的项目。这些约束要在后端做校验而不是前端按钮隐藏一下就算完成。1.3 为什么选Python而不是Java或PHP毕设选型时Python Django 的组合足够稳也足够有解释空间。我的判断依据有三点第一Django自带Admin后台。家装系统的运营端需要录入公司资质、设计师简介、材料清单如果用纯前端后台这部分工程量会非常大。Django Admin可以替你省掉一个小程序的开发量让运营人员直接在后台维护数据。第二ORM和用户认证开箱即用。Python生态里Django的ORM非常成熟外键关系、数据迁移、查询过滤都写得很顺手。系统需要多角色登录和权限控制Django提供AbstractUser、LoginRequired、Group这些基础能力能减少安全方面的坏味道。第三周边工具丰富。做演示数据可以用Faker做接口可以用Django REST Framework做报表想加分可以用pandas清洗数据做爬虫抓案例素材也有requests/Scrapy。这些都是Java和PHP生态里相对费劲的地方。当然也要诚实说一句如果导师的项目是面向几十万用户的高并发平台Python并不占优势。但作为毕业设计我们要做的是“业务闭环完整、代码结构清晰”这一点Python是最容易达到的。2. 技术选型与项目结构设计2.1 框架选型对比Django还是Flask选型时很多同学会纠结用Django还是Flask。我建议直接用Django除非题目明确要求轻量级架构。两者的区别可以看下面这张表。对比项DjangoFlask自带ORM有迁移工具完善没有需要另外装SQLAlchemyAdmin后台自带非常省事需第三方扩展用户认证完整认证体系需要手动实现适合场景多模块、业务复杂单接口、原型演示学习成本框架约定多但规范灵活但容易写乱一套家装系统至少有用户、预约、方案、订单、施工、材料几个模块如果用Flask你得自己拼装一堆库最后项目结构很可能是“一个大文件跑到底”后期改不动。Django的app拆分方式反而更适合做这种多业务系统。建议版本Python 3.10及以上Django 4.2 LTS。生产部署时可以切换到LTS版本不会踩到新版本功能不稳定的坑。安装时直接创建虚拟环境python -m venv venv venv\Scripts\activate # Windows下激活虚拟环境 pip install Django4.2.* mysqlclient pillow这里顺手把pillow装好因为方案图片上传、缩略图处理都离不开它。数据库开发阶段用Django默认的SQLite部署或答辩前再切到MySQL后面会讲到切换方法。2.2 前后端方案模板渲染还是接口分离家装系统这种以表单、列表、状态流转为主的毕设我推荐先用Django模板 Bootstrap 5完成。原因是省掉跨域、Token、Node构建这些额外复杂度把精力放在核心业务上。如果题目要求微服务、前后端分离就走Django REST Framework Vue的方案。但我需要提醒一句分离架构会让答辩重点从“业务逻辑”变成“前后端联调”老师更关注的是你设计系统时的思考模板渲染同样能把问题讲清楚。页面层可以规划成这样首页装修案例展示、公司列表、设计师展示预约页选择一个公司/设计师填写房屋信息和需求描述个人中心我发起的预约、我的订单、我的评价订单详情方案预览、报价明细、施工进度时间轴后台公司资料维护、预约分配、方案审核、物料管理。前端使用Bootstrap栅格做响应式量房预约页单独做一栏手机端样式这个细节在答辩演示时很加分能体现出你考虑了真实用户场景。2.3 项目目录按业务模块拆app项目名建议叫home_services避免用“system”这种过于泛的词。目录结构可以这么拆home_services/ ├── manage.py ├── requirements.txt ├── config/ │ ├── settings/ │ │ ├── base.py │ │ ├── dev.py │ │ └── prod.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ │ │ ├── models.py │ │ ├── views.py │ │ └── urls.py │ ├── companies/ │ ├── designers/ │ ├── appointments/ │ ├── schemes/ │ ├── orders/ │ ├── projects/ │ └── materials/ ├── static/ ├── media/ ├── templates/ └── tests/这种结构的核心思路是“一个业务域一个app”而不是把User、Company、Order全部堆在同一个models.py里。Django创建app很轻量多拆几个不会有人嘲笑反而显得有工程意识。settings拆成base/dev/prod三份是为了后续部署方便开发时用dev.py部署时用prod.py。URL也用include方式按模块挂在config/urls.py下不要一条路由写完几十行。3. 数据库设计与核心功能实现3.1 核心表设计让预约、方案、订单、施工形成闭环数据库设计是这套系统最值得花时间的地方。我推荐用一张主表连接业务流但每一段业务都要有独立的状态和记录。核心表大致如下。表名核心字段说明users_userusername, password, role, phone使用Django AbstractUser扩展role区分角色companies_companyname, license, manager, intro装修公司主体信息designers_designeruser, company, specialty, years设计师关联公司和账号appointments_appointmentcustomer, company, designer, address, demand, status预约单status控制预约流程schemes_schemeappointment, designer, title, cover, style, budget设计方案可包含多张图片orders_ordercustomer, appointment, scheme, total_amount, status订单/合同金额用DecimalFieldprojects_constructionstageorder, stage_name, start_date, status施工阶段表projects_progressrecordstage, description, photo, update_user施工进度记录materials_materialname, spec, unit_price, stock材料库存reviews_revieworder, rating, content, is_show完工评价用户表的第一条规则项目一开始就自定义User模型继承AbstractUser并加上role字段。如果你先migrate了默认用户表再改Django会给你一个很难受的提示“Can’t alter table”届时只能重启数据库重新同步。自定义User的代码很简单from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES [ (1, customer), (2, company), (3, designer), (4, admin), ] role models.SmallIntegerField(choicesROLE_CHOICES, default1) phone models.CharField(max_length11, blankTrue)同时要在settings里写一行AUTH_USER_MODEL users.User。写完这行再做第一次migrate后面就顺了。金额字段一定要用DecimalField(max_digits10, decimal_places2)不要用FloatField。我之前见过同学用float存报价客户看到的金额出现0.30000000000000004报价单直接翻车。这里不是“小细节”是会让演示当场尴尬的问题。外键的on_delete规则也要仔细想。例如订单里的appointment如果预约单被删除订单应该保留历史快照外键应该设置on_deletemodels.SET_NULL, nullTrue。如果直接CASCADE删一条预约会把关联订单、施工记录全带走这是灾难性的设计。3.2 状态流设计预约、订单、施工阶段怎么流转状态设计直接影响代码复杂度。我建议用整数字段加choices不要用字符串。字符串看起来直观但写错了没提醒数据库里会出现“施工中”、“施工中 ”、“施工中”三个不同值后期报表完全没法统计。预约状态可以这样定义APPOINTMENT_STATUS ( (0, 待联系), (1, 已联系), (2, 已量房), (3, 方案沟通中), (4, 已签约), (5, 已取消), )订单状态可以这样定义ORDER_STATUS ( (0, 待支付定金), (1, 设计进行中), (2, 方案待客户确认), (3, 待签订合同), (4, 施工中), (5, 待验收), (6, 已完成), (7, 已取消), )更关键的是不要在每个视图里随意赋状态值。我的做法是写一个独立的状态流转函数专门判断当前状态能不能跳到下一个状态。比如“方案待客户确认”这个状态设计师可以发起客户可以确认但不能直接从“待支付定金”跳到“施工中”。把规则收敛到一个地方后面排查问题会非常爽。3.3 用户登录与角色权限控制Django自带的login和logout视图可以省不少事我们只需要在模板里放表单使用django.contrib.auth.views.LoginView即可。重点是权限控制。自定义一个简单的装饰器比引入复杂的权限库更实用。from functools import wraps from django.http import HttpResponseForbidden def require_role(roles): def decorator(view_func): wraps(view_func) def wrapped(request, *args, **kwargs): if request.user.is_authenticated and request.user.role in roles: return view_func(request, *args, **kwargs) return HttpResponseForbidden(当前角色没有权限执行此操作) return wrapped return decorator使用方式很直观require_role([1, 4]) # 只允许业主和管理员访问 def customer_appointments(request): ...这种自定义装饰器评委看起来不复杂又能说明白权限思路。如果每个视图都用if request.user.is_authenticated判断代码会非常啰嗦而且容易漏掉某个入口。4. 核心代码流程与踩坑记录4.1 预约业务事务、重复提交和通知保持一致预约是系统里最核心的业务入口。这里有一个容易踩的坑客户连续点击两次提交按钮会生成两条预约记录。处理方式是在视图里先检查是否存在进行中的预约再创建新纪录整个过程包在事务里。这样即使通知写入失败预约也不会带着残缺数据落库。from django.db import transaction from django.http import JsonResponse from .models import Appointment, Notification require_role([1, 4]) transaction.atomic def create_appointment(request): if request.method POST: company_id request.POST.get(company_id) address request.POST.get(address) demand request.POST.get(demand) expected_time request.POST.get(expected_time) existed Appointment.objects.filter( customerrequest.user, company_idcompany_id, status__in[0, 1, 2, 3] ).exists() if existed: return JsonResponse({message: 你还有进行中的预约请先完成或取消再预约}, status400) appointment Appointment.objects.create( customerrequest.user, company_idcompany_id, addressaddress, demanddemand, expected_timeexpected_time, status0 ) Notification.objects.create( userappointment.company.manager, contentf客户 {request.user.username} 发起了预约请及时跟进 ) return JsonResponse({appointment_id: appointment.id})事务在这里起的作用是保证“预约单写入”和“通知写入”同时成功或同时失败。如果预约成功但通知失败公司管理员完全不知道有人来了业务链直接断掉。排查时会发现很多看似玄学的问题都出在业务操作写到一半抛异常。这里还想说明一点有人会用Django signal在post_save里自动创建通知代码表面更“优雅”但会让控制流变得隐性。在毕设阶段我建议用显式的service函数或视图内调用讲代码时也好说清楚后续再造一个service层来封装复杂的业务。signal留在真正需要解耦的地方再用。4.2 方案图片上传MEDIA配置和文件名处理家装系统离不开效果图。在settings里要配置好MEDIA_ROOT和MEDIA_URL否则上传的图片会跑到奇怪的位置。配置如下from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media在根URL配置里加上开发环境的静态服务from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)模型里使用ImageField时最好重写upload_to。我遇到过重名图片互相覆盖的问题所以会用一个带时间戳的目录def scheme_image_path(instance, filename): ext filename.split(.)[-1] return fschemes/{instance.appointment_id}/{timezone.now().strftime(%Y%m%d%H%M%S)}.{ext}这个upload_to的好处是让每一条预约的方案图放在独立目录里不仅不会覆盖后台管理人员一眼就能知道哪个文件属于哪个订单。上传时前端还要限制文件类型和大小我用的是acceptimage/*加后端校验文件后缀只允许jpg、png、webp大小限制在5MB。毕设阶段这些简单校验已经足够比只在前端隐藏按钮靠谱多了。4.3 状态变更时如何给用户发通知家装系统里用户最关心的是“我的方案有没有出来”“工地进展怎么样”。所以状态变更时同步发送站内信是刚需。我设计了一个Notification表字段包括接收用户、内容、是否已读、创建时间。在状态流转函数中统一发通知。订单状态从“方案待客户确认”变成“施工中”时要给客户发一条站内信同时给项目经理生成一个任务记录。这里的关键是不要在订单列表或详情视图里手动改order.status而是调用一个统一的update_order_status(order, new_status, operator)函数。函数里先做权限和状态校验再更新状态再创建通知。这段代码逻辑虽然不难但做出来后整个项目看起来是有设计的。老师问“你们怎么保证状态不跳位”的时候你可以直接指着这个函数说所有入口都走同一套校验不允许直接赋值。5. 测试、部署与答辩演示要点5.1 核心流程写单元测试不要只靠手工点击毕设阶段写满覆盖率的测试不是必须但至少要为核心业务流程写几个TestCase。Django的测试框架很友好可以直接模拟登录和POST请求。我为这套系统写过一组测试覆盖的顺序是客户注册登录、创建预约、公司管理员推进预约状态、设计师上传方案、客户确认方案生成订单、项目经理上传施工进度、客户验收并评价。这组测试跑通基本代表主流程没大问题。from django.test import TestCase, Client from apps.users.models import User class AppointmentFlowTest(TestCase): def setUp(self): self.client Client() self.customer User.objects.create_user( usernamecustomer1, passwordtest123, role1 ) # 准备一个测试公司这里用create方便快速构造 def test_create_appointment_without_login(self): resp self.client.post(/appointments/create/, {}) self.assertEqual(resp.status_code, 302) # 未登录会跳转 def test_duplicate_appointment_blocked(self): self.client.login(usernamecustomer1, passwordtest123) # 先提交一次再提交第二次第二次应返回400测试的好处还有一个答辩前整理数据时如果改了数据库结构导致某个页面报错跑一遍测试立刻能定位到哪一环断了不用一个个页面点过去。配合一份演示用的fixture数据可以大大降低翻车概率。5.2 从开发到部署切换MySQL、配置Gunicorn和Nginx毕设系统一般只需要单个云服务器或者PythonAnywhere。如果本地一直用SQLite部署前切换MySQL时要注意几点第一安装驱动pip install mysqlclientWindows下如果编译困难可以换成pymysql并安装cryptography再配合django.db.backends.mysql。第二settings里的DATABASES改成DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: home_services, USER: home_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }第三部署环境里把DEBUG设为FalseALLOWED_HOSTS写上域名或IP。运行python manage.py collectstatic把静态文件收集到指定目录再用Nginx托管。启动命令可以写成gunicorn config.wsgi:application -b 127.0.0.1:8000 --workers3Nginx里主要配置两个locationlocation /static/ { alias /path/to/staticfiles/; } location /media/ { alias /path/to/media/; }如果不想装服务器用PythonAnywhere的免费实例也够演示它可以直接指定WSGI文件和虚拟环境几分钟就能上线。总之生产方案不需要多惊艳稳定、跑通、能解释就行。5.3 答辩演示脚本提前跑通三个关键场景答辩现场最怕的不是答不上来而是演示时临时输数据、找按钮。我的建议是把演示流程固定成三条主链路数据预先填好场景一业主登录浏览首页案例找一个装修公司发起预约填写房屋需求。此时后台预约列表出现一条新记录。场景二管理员登录后台把这条预约分配给设计师设计师上传方案图并填写预算报价。然后切回客户账号在订单页确认方案并生成合同。场景三管理员把订单状态切到施工中项目经理上传工地照片和进度描述完成后客户确认验收提交评价。这三条链路串完基本覆盖了系统所有关键功能。演示时要故意展示一次权限拦截比如用客户账号去访问设计师后台页面提示“当前角色没有权限”这点比功能正常展示更能体现系统设计能力。6. 常见问题与排查技巧实录6.1 家装系统开发中的典型报错速查表这些坑都是我做这套系统时真实遇到过的按报错原因和解决方案整理成一张速查表。现象根本原因解决方法运行python manage.py runserver提示No such table还没执行迁移运行python manage.py makemigrations python manage.py migrate提示App contains migrations with pending changesmodel字段改了没生成新迁移重新makemigrations不要手改迁移文件登录后点页面报CSRF token missing模板Form里少了csrf_token在form中加{% csrf_token %}或Fetch请求加X-CSRFToken头图片上传后页面显示404MEDIA_URL没有配置或未添加static规则检查settings和根urls生产环境检查nginx alias中文数据写入数据库显示乱码数据库字符集不是utf8mb4建库用CREATE DATABASE ... CHARACTER SET utf8mb4用SQLite时并发稍高报database is lockedSQLite写锁限制开发环境才用SQLite演示转MySQL或开启WAL模式外键循环依赖卡在migration两个app互相引用了对方Model在其中一个外键使用字符串引用apps.xxx.Model并谨慎设计依赖方向模板找不到某个页面应用未加到INSTALLED_APPS检查setting INSTALLED_APPS别忘了也要有templates路径6.2 从这些坑里总结出的实操原则第一个原则用户模型要最先自定义。第一次migrate前就设置AUTH_USER_MODEL后面所有外键引用都依赖它。如果项目已经做了一半才想起加role字段尽量迁移到新库别在原库上反复折磨。第二个原则状态字段用IntegerField加choices并封装状态流转函数。字符串状态在演示时看不出问题但到了统计报表环节会非常痛苦。在一个地方统一管理状态跳转比每个视图各写一套if要可靠得多。第三个原则外键删除策略不要无脑CASCADE。订单、施工进度这类历史数据一定要用SET_NULL或PROTECT保护起来。真实系统里一条订单被级联删除是事故级的错误毕设阶段养成好习惯答辩时也能讲出取舍依据。第四个原则演示数据用脚本生成别手工造。写一个python manage.py seed_demo_data用Faker生成20个客户、5家公司、50条预约、若干条进度记录。每次答辩前清空数据库重新seed保证现场数据是“干净、统一、好看”的。第五个原则静态资源和媒体文件路径从第一天就规范化。开着DEBUG时runserver会自动处理但关了DEBUG之后404会立刻找上门。尽早配好Nginx的static/media后面心情会好很多。做这套系统最大的体会是技术难点其实不深最难的是把家装业务的每个状态和权限约束想明白。画好状态机写代码只是翻译的过程。如果再有人问我这个题目怎么做我还是会建议先花两天时间把表关系、状态流、角色权限画清楚再动手写第一行Python代码这比急着安装Django重要得多。最后补一个小技巧答辩演示时把后台管理操作和前端操作分开前台只走一遍业务闭环后台用Admin快速展示数据变动整个节奏会非常稳。