做开发这些年,接过不少课程设计和毕业设计项目,客户关系管理系统(CRM)算是我遇到频率最高的题目之一。今天聊的这套基于Python的客户关系管理系统,从题面上看是“设计与开发”,实际上项目包里包含了完整源码、精品论文、答辩PPT,摆明了是按毕业设计整套配置来的。它的核心业务并不复杂:围绕“销售或客服人员如何管理客户”这个场景,把潜在客户、跟进记录、联系人、商机状态这些数据统一收拢,再提供基础的统计看板,方便管理者看团队的客户分布和跟进情况。简单说,这就是一套带用户权限的Web业务系统。难点不在功能堆砌,而在表结构怎么设计、权限怎么隔离、CRUD怎么做得完整不冗余。这篇文章我就从需求整理、技术选型、数据库设计、核心代码实现到答辩资料准备,完整过一遍,顺带把我在写这类系统时遇到的坑都列出来。
1. 项目概述与设计思路
1.1 为什么选Python做客户关系管理系统
CRM这个概念在企业里早就不新鲜了,说白了就是把公司和客户打交道的过程数据化。对中小企业来说,客户资料往往散落在销售的Excel表、微信记录甚至纸质笔记本里,人员一变动客户资源就可能跟着流失,所以CRM系统的第一价值是把客户资产沉淀下来。放到高校的课程设计和毕业设计里,这个题目常年热门,原因是它有非常清晰的业务模型,老师评审时不用费劲理解,再加上功能边界稳定、技术栈自由度高,适合一个人在一个学期内完成。
从技术选型角度看,Python做这类管理系统相当顺手。语法接近自然语言,写业务逻辑时基本不会被语言本身卡住,Django或Flask又打包好了路由、ORM、模板渲染这些Web开发最常用的组件,一个人即可完成从前端页面到后端接口的所有工作。比起用Java全家桶做一套“大而全”的系统,Python的敏捷性更适合这种中量级项目,也更容易在答辩前把功能打磨完整。
我实际写下来,这套系统的合理定位应该是“内部销售协同管理平台”,而不是大厂那种营销自动化中台。核心角色就三类:普通员工也就是销售或客服,部门主管,以及系统管理员。权限设计上普通员工只能看自己的客户和数据,主管可以看团队数据并分配线索,管理员则维护账号和基础配置。把这三个角色理清,后面的表结构、视图函数和权限控制就都有了依据。
1.2 核心需求与功能边界
做这类项目最怕一上来就堆功能。我见过不少同学一开始就规划“客户管理加商机管理加工单管理加营销活动加数据分析加日程提醒”,最后每个模块都做得很浅,答辩时一问细节就露馅。更务实的做法是MVP思路,先做四个必选模块,其余时间用来打磨质量和准备论文。
- 用户与权限模块:登录、注册、角色区分、会话管理。
- 客户模块:客户名录、客户详情、联系人、客户分类。
- 跟进模块:新增跟进记录、关联客户与商机、下次跟进时间提醒。
- 统计模块:按客户状态统计数量、跟进次数排行、团队客户分布、转化漏斗概览。
这四个模块做完,系统的主线故事是完整的:销售登录后看到自己的客户列表,点进详情看到历史跟进,新增一条跟进时系统记录时间点和内容,回到看板页看到最近新增和转化情况。就这一条闭环,已经能撑起一篇像样的毕业论文。
需求阶段还有一件事值得做:把页面清单提前列出来。我通常在数据库设计前先列页面,比如登录页、注册页、客户列表页、客户详情页、跟进记录弹窗、统计看板页、用户管理页、个人资料页。每一页对应一个视图函数或类,开发时不容易漏页面,写论文时还能直接引用这套功能架构。
2. 技术选型与开发环境搭建
2.1 框架选择:Django和Flask怎么取舍
Python写Web项目,绕不开Django和Flask这两个框架。两个我都实际用过,在这套CRM上我更推荐Django,理由有三个。
Django自带Admin后台,这个对开发阶段帮助极大。CRM这类系统的数据管理后台本质上就是标准CRUD,Django Admin几乎不用专门写页面就能完成用户、客户、跟进记录的管理,用来调试数据非常方便。Django还内置了ORM和迁移机制,配合migrations命令,数据库表结构能以纯代码方式管理,这在写“数据库设计”章节时尤其好使,直接贴代码就能解释清楚。再一个,Django的认证系统开箱即用,登录、退出、会话、密码哈希都封装好了,比自己手动处理要靠谱很多。
当然,Flask做这个项目也没问题。如果前端打算用Vue写单页应用,Flask搭配Django REST Framework更像API后端。但单人开发课程设计,前端也要自己搞定,用Django模板系统直接渲染页面,开发效率反而最高。这里有个经验:不要为了技术炫耀选择更复杂的架构,这类项目的评分点在于逻辑自洽、工作量达标、系统能稳定跑起来。
2.2 Python环境与依赖管理
环境方面建议用Python 3.8以上版本,配合virtualenv或者conda创建独立环境。Python的版本坑不少,Django 4.2 LTS要求Python 3.8以上,Django 5.x则要求3.10以上,如果直接pip install最新版Django,老系统里装的是Python 3.6,安装就会直接失败。安装依赖时建议固定版本号,不要裸装。
我常用的一套组合可以稳定跑通:
Python 3.10 Django 4.2 LTS PyMySQL python-dotenv echarts / Chart.js有人会问为什么不用最新的Django 5.x。其实5.x也在维护期内,但不少第三方插件在4.2上的兼容验证更充分。课程设计项目追求稳定,没必要追新。如果数据库选择MySQL,建库时注意把字符集设为utf8mb4。这个坑我在真实项目里踩过,客户备注里只要出现一个emoji,写入就报“Incorrect string value”错误,排查半天才发现是字符集的问题。DataGrip里建数据库时我习惯直接带上DEFAULT CHARACTER SET utf8mb4,一笔账省去后面很多麻烦。
3. 数据库设计:围绕业务流的表结构
3.1 核心表有哪些
CRM系统的数据模型不复杂,但字段取舍很关键。按前面提到的四个模块,核心表主要有这些:
- User用户表:可以直接使用Django自带的auth_user,再扩展一张Profile表存真实姓名、岗位、部门。
- Department部门表:部门名称、负责人、创建时间,用来支撑团队维度的统计。
- Customer客户表:客户名称、电话、邮箱、状态、负责人、创建人、更新时间、备注。
- Contact联系人表:一个客户可能有多个联系人,联系人关联客户ID,字段包括姓名、职位、电话、微信。
- FollowUp跟进记录表:关联客户,记录跟进方式、跟进内容、下次跟进时间、创建人。
- BusinessOpportunity商机表:扩展项,记录商机名称、金额、预计成交时间、阶段。
表结构设计的核心原则是尽量不冗余。例如“客户状态”直接在Customer表存一个字符串或整型字段,不要为了省事把“已成交客户”单独拆成一张表,否则后续统计还要做表关联,白白增加复杂度。状态用choices常量定义好,前端展示时做映射,后端过滤时直接用,比关联字典表要轻量得多。
3.2 Django模型实现与迁移
以Customer为例,我会把核心代码写成下面这样,为便于展示删减了一部分装饰器和校验逻辑:
from django.db import models from django.contrib.auth.models import User class Customer(models.Model): STATUS_CHOICES = ( ('lead', '潜在客户'), ('intention', '意向客户'), ('deal', '成交客户'), ('lost', '流失客户'), ) name = models.CharField('客户名称', max_length=120) phone = models.CharField('联系电话', max_length=20, blank=True) email = models.EmailField('邮箱', blank=True) status = models.CharField( '客户状态', max_length=20, choices=STATUS_CHOICES, default='lead' ) owner = models.ForeignKey( User, verbose_name='负责人', on_delete=models.PROTECT, related_name='customers' ) remark = models.TextField('备注', blank=True) created_at = models.DateTimeField('创建时间', auto_now_add=True) updated_at = models.DateTimeField('更新时间', auto_now=True) def __str__(self): return self.name这里有三个设计细节值得说明。owner字段指向User且on_delete设为PROTECT,意思是如果某个销售账号下面还有客户,就不能直接删掉这个账号,从数据层面防止客户资料被连带删除。phone字段没有使用专门的PhoneNumberField,因为Django不自带,得依赖第三方库phonenumbers,课程设计里没必要为这个功能引入额外依赖,用CharField配合正则校验就够了。updated_at用auto_now,每次调用save时会自动更新为当前时间,省去手动维护。
模型写完后执行迁移:
python manage.py makemigrations python manage.py migrate迁移文件会记录在app的migrations目录下,这也是论文“数据模型版本管理”部分的素材。要导出表结构关系图,可以用django-extensions的graph_models命令生成ER图,不过中文表名显示需要额外配置,我个人更推荐直接截图models.py核心代码放进论文,清晰且不容易出错。
4. 核心功能模块实现
4.1 登录认证与权限控制
登录模块看着简单,实际上CRM系统里最容易被问倒的是权限控制。Django自带login和logout视图,但真实项目中通常要扩展几个点:登录后跳转目标、角色区分、页面级访问限制。
我习惯直接手写登录视图,逻辑很直白:
from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect('dashboard') return render(request, 'login.html', {'error': '用户名或密码错误'}) return render(request, 'login.html')权限控制方面,我在Profile模型上增加一个role字段来区分员工与主管,再配合Django自带@login_required保证业务页面必须登录。主管页面的二次过滤可以用@user_passes_test,判断当前用户role是否为manager。
这里特别注意一点:判断角色不要用user.is_staff。is_staff在Django里表示“能否进入Admin后台”,调试时如果把普通测试账号勾选了is_staff,业务权限就会跟着错乱。我的做法是角色字段独立管理,和Django自带布尔字段完全分开。权限逻辑保持清晰,答辩时也更好向老师解释。
4.2 客户信息管理的增删改查
客户模块的核心是列表页加详情页。列表页最关键的是数据过滤,普通员工只能看到自己名下的客户,主管可以看到整个团队的。
from django.views.generic import ListView from .models import Customer class CustomerListView(ListView): model = Customer template_name = 'customer/list.html' paginate_by = 10 def get_queryset(self): qs = Customer.objects.all() # 普通员工只看自己的客户,主管看整个团队 if self.request.user.profile.role == 'staff': qs = qs.filter(owner=self.request.user) # 状态、关键字筛选 status = self.request.GET.get('status') if status: qs = qs.filter(status=status) keyword = self.request.GET.get('keyword') if keyword: qs = qs.filter(name__icontains=keyword) return qs.order_by('-created_at')这段代码是最典型的数据隔离落地点,写论文时值得重点展开。很多人用Django做系统时都忽略了这种行级权限控制,实际做出来反而是加分项。
新增和编辑客户,我会用ModelForm完成。Django表单最大的好处是自动渲染HTML、自动校验字段、自动回显错误,几行代码就能得到一个可靠的表单:
from django import forms from .models import Customer class CustomerForm(forms.ModelForm): class Meta: model = Customer fields = ['name', 'phone', 'email', 'status', 'remark'] widgets = { 'remark': forms.Textarea(attrs={'rows': 3}), }为什么课程设计不追求前后端分离,到这一步就很直观。Django自己负责表单渲染、提交、校验,开发量比Vue加axios写一遍再加Django API写一遍要少一半。单人项目优先考虑交付效率。
我还会建议给客户列表增加“导出Excel”功能。如果系统里只有增删改查,工作量看起来会单薄。加一个导出功能,既实用又带技术含量。用openpyxl库遍历queryset写入Excel,大概几十行代码就能完成。
4.3 跟进记录与统计看板
跟进记录模块的数据结构不复杂,但业务逻辑上有一个关键点:每次新增跟进后,最好把客户状态一并更新。例如销售把客户从“潜在客户”推进到“意向客户”,在跟进表单里设置一个状态下拉框,保存跟进记录的同时更新Customer.status。这样做的好处是统计看板直接按status分组就能画出漏斗图,不用额外写复杂的关联查询。
统计看板是整个项目中最出效果的部分,推荐用echarts来做。Django视图只需要给前端传JSON数据,前端再用图表组件渲染。视图示例:
from django.db.models import Count from django.http import JsonResponse def dashboard_data(request): user = request.user qs = Customer.objects.all() if user.profile.role == 'staff': qs = qs.filter(owner=user) status_data = qs.values('status').annotate(cnt=Count('id')) return JsonResponse({'status_data': list(status_data)})前端用echarts饼图渲染,核心配置大致是这个样子:
var chart = echarts.init(document.getElementById('chart')); chart.setOption({ tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: ['40%', '70%'], data: statusData }] });需要注意,如果演示环境在教室或答辩现场,网络可能访问不了外部CDN。我这里用的是本地static目录里存放的echarts.min.js文件,确保局域网环境也能正常出图。
5. 测试、部署与答辩资料准备
5.1 功能测试和常见Bug
课程设计的测试不需要覆盖全套单元测试,但至少要把核心流程冒烟跑一遍。我的建议是先走通注册、登录、新增客户、新增跟进、查看统计看板这条主路径,再验证权限隔离是否真实生效。方法很简单:用一个普通员工账号登录,尝试访问另一个员工名下的客户详情页,看是否被拒绝。
实际开发中我见过最多的Bug有三类,这里列成速查表:
| 典型问题 | 原因 | 解决办法 |
|---|---|---|
| 页面时间比本地时间晚8小时 | 时区设置是UTC | settings.py里设置TIME_ZONE为Asia/Shanghai,并按需配置USE_TZ |
| 关掉DEBUG后页面没有样式 | Django默认不提供静态文件服务 | 本地演示保持DEBUG=True,或用whitenoise/nginx托管静态目录 |
| 写入中文或emoji乱码 | 数据库字符集不是utf8mb4 | 建库时指定utf8mb4,连接串带charset=utf8mb4 |
第一条时区问题最隐蔽。如果settings.py里没有配置好TIME_ZONE,新增客户时页面会显示UTC时间,和本地时间相差8个小时,答辩演示时很容易暴露。第二种静态文件问题也很头疼,本地调试时用runserver一切正常,关掉Debug就崩,本质上是静态文件服务机制变了。课程设计答辩阶段,我更推荐使用本地runserver并保持DEBUG=True,或者用whitenoise中间件兜底,这两种模式都能稳定跑起来。
5.2 论文结构与答辩PPT准备要点
题目里带了“源码+精品论文+答辩PPT”,说明这套资料是配套好的。论文结构按常规毕业设计目录来组织即可:绪论写背景和国内外现状,技术章节写Python、Django、MySQL和前端技术栈,系统分析写可行性和用例图,系统设计写架构和数据表,系统实现放页面截图和关键代码分析,最后系统测试用测试用例表收尾。这样的结构覆盖了学校要求的“分析、设计、实现、测试”四大环节,逻辑也完整。
答辩PPT一般按“背景与意义、技术与工具、需求分析、总体设计、功能展示、测试结果、总结展望”来做,控制在12到15页。一个强烈建议是:演示环节提前录制一段操作视频放在PPT最后一页。现场答辩的电脑大概率没有Python环境,打开终端跑Django服务这个动作本身就容易出问题,比如依赖没装、数据库没初始化、端口被占用。我见过太多次“源码明明在本地能跑,现场就是起不来”的尴尬场景,视频兜底能有效降低风险。
5.3 从课程设计到真实系统的扩展方向
如果学有余力,这套系统后续有几个不错的扩展方向。第一,引入Redis缓存首页统计看板的查询结果,减少每次打开看板时的数据库压力。第二,加一个简单的定时任务,在跟进时间到期时给销售发送提醒邮件,可以选用django-celery-beat。第三,把前端升级为Vue3配合Element Plus的单页应用,后端通过Django REST Framework提供接口,这也是往真实企业级项目演进的方向。这些扩展写到论文的“展望”章节里,答辩时说出来会显得思考有深度。
最后分享一个很小的经验:项目根目录一定要有README.md,把运行环境、数据库配置、启动步骤、默认账号密码都写清楚。这不是为了给老师交差,更多是给自己留后路。答辩前一周你可能还记得启动命令,答辩前一天的凌晨大概率已经忘了。把步骤固化下来,能少折腾很多。做这类毕业设计项目,重要的不是技术多炫,而是能不能在有限时间里把系统跑通、把逻辑讲圆、把资料备齐。这三点做到了,答辩基本就稳了。