☰
Django实现客户关系管理系统:从数据库设计到功能实现全攻略
2026/9/26 5:07:48 网站建设 项目流程

客户关系管理系统这个题目,在计算机毕设里算是经久不衰的经典款。我当年带过的学生里,几乎每届都有人选这个方向,原因很简单:需求明确、技术栈常规、业务逻辑清晰,只要按部就班地做,不容易翻车。但反过来讲,正因为做的人多,想拿高分就得有点差异化设计。这篇就结合我实际带毕设的经验,把这个题目从选题逻辑、系统设计、数据库建模到核心功能实现完整拆一遍,给准备做这个题或者正在开题的同学一条清晰的路。

1. 系统整体设计与技术选型思路

1.1 为什么客户关系管理系统适合当毕设

毕设选题有个不成文的评判标准:既要有业务场景支撑,又要有技术含量可展示。纯增删改查的信息管理系统太单薄,答辩时容易被追问到无话可说;纯算法研究又容易脱离实际应用场景。客户关系管理系统恰好卡在中间——它属于典型的企业级业务系统,业务流程完整,能覆盖从客户录入、跟进记录到订单成交再到数据统计分析的全链路,天然适合用来展示软件工程能力。

而且客户关系管理系统对数据量级的要求很灵活。小到几十条测试数据能跑通流程,大到几万条数据做分页检索和统计都不算过分,这就给了你充足的空间去展示数据库设计、索引优化、缓存策略这些加分项。我当时给学生的建议是:把这个题目当成一个小型ERP的简化版来做,重点突出客户全生命周期管理这条主线。

1.2 技术栈选型的实际考量

Python生态里做Web系统,主流的选项无非是Django和Flask两种。我个人的建议是优先选Django,原因很实在:Django自带Admin后台,开发阶段可以直接拿来管理数据,省掉了不少造轮子的时间;自带的ORM和迁移机制让数据库变更非常顺手;认证授权系统开箱即用,不用自己实现登录、Session、CSRF防护这些基础功能。

Flask的优势是轻量灵活,适合那种想展示更多底层实现能力的学生。但代价是很多东西都要自己拼装,比如登录认证得用Flask-Login,ORM要自己接SQLAlchemy,表单验证要配Flask-WTF。如果是毕设,这些拼接工作会占用大量宝贵的开发时间。当然,如果你已经对Flask很熟练,用Flask也没问题,技术选型不是死规矩,但一定要考虑后期答辩时能否把每个技术点解释清楚。

前端方面,我见过不少学生会纠结要不要上Vue。说实话,对毕设来说没必要搞前后端分离那套。直接利用Django的模板系统,配合Bootstrap写响应式页面,开发效率高,演示时也不会出幺蛾子。如果实在想让界面好看一点,可以在模板里嵌入一些ECharts图表,效果完全不输单独部署的前端项目,维护成本却低得多。

1.3 功能模块划分与优先级

客户关系管理系统从业务角度拆,核心模块就四块:客户信息管理、跟进记录管理、订单管理、数据统计分析。这是骨架,必须做扎实。我建议在这个基础上,根据自己精力情况选择性加分项:

  • 客户信息管理:客户的增删改查、批量导入导出、客户分类(潜在客户、意向客户、成交客户、流失客户)、高级检索(多条件组合、模糊搜索)。
  • 跟进记录管理:销售人员的每一次客户沟通都要有迹可循,跟进时间、方式(电话、微信、拜访)、沟通内容、下次跟进提醒。
  • 订单管理:客户产生的下单行为记录,订单金额、日期、关联客户和销售人员。
  • 数据统计分析:客户来源分析、跟进转化率、销售业绩排行、按月/季度趋势图。这块是答辩时的视觉加分项。

进阶功能还可以做客户公海池(销售放弃的客户回到公共池供他人领取)、标签化管理、邮件营销记录等。但我的原则是:主线功能做到80分,加分项做到60分能演示即可,别本末倒置。

2. 数据库设计与核心模型

2.1 数据表结构与关系设计

数据库设计是客户关系管理系统的地基,这块要是设计不合理,后面写代码会处处难受。我常用MySQL作为数据库,设计思路直接套用经典的范式模型。

用户表(auth_user)直接用Django自带的就行,扩展一个profile表存真实姓名、手机号、角色。角色就分两种:管理员和销售人员。管理员能看全公司的客户和统计报表,销售人员只能看自己和公海池的客户。

客户表几个关键字段:

class Customer(models.Model): name = models.CharField('客户名称', max_length=100) phone = models.CharField('联系电话', max_length=20) level = models.CharField('客户级别', max_length=10, choices=LEVEL_CHOICES, default='A') source = models.CharField('客户来源', max_length=50, choices=SOURCE_CHOICES) address = models.CharField('地址', max_length=200, blank=True) owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='customers') status = models.CharField('客户状态', max_length=20, choices=STATUS_CHOICES, default='potential') created_at = models.DateTimeField('创建时间', auto_now_add=True)

客户状态建议用字符串常量而不是数字,可读性高,排查数据问题时不用翻代码注释。客户级别(A/B/C/D)对应重要程度,A级为最高优先级客户,销售人员按级别决定跟进频率。

客户跟进记录表是整个系统的灵魂,因为它记录了销售过程中的所有动作:

class FollowUp(models.Model): customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='followups') user = models.ForeignKey(User, on_delete=models.CASCADE) way = models.CharField('跟进方式', max_length=20, choices=WAY_CHOICES) content = models.TextField('跟进内容') next_time = models.DateTimeField('下次跟进时间', null=True, blank=True) created_at = models.DateTimeField('跟进时间', auto_now_add=True)

订单表要关联客户和销售员,金额字段用DecimalField而不是FloatField,避免浮点精度问题:

class Order(models.Model): order_no = models.CharField('订单号', max_length=32, unique=True) customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name='orders') owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='orders') amount = models.DecimalField('订单金额', max_digits=10, decimal_places=2) status = models.CharField('订单状态', max_length=20, choices=ORDER_STATUS, default='pending') created_at = models.DateTimeField('下单时间', auto_now_add=True)

2.2 数据关系设计里的几个陷阱

有些细节没做过真实项目的人很难意识到。比如客户删除问题,现实中客户数据是核心资产,如果客户已经被关联了订单,删除客户会导致历史数据全部断链。我的方案是对客户做“软删除”——在模型上加一个is_delete字段,删除操作只是把这个字段置为True,查询时默认过滤掉,而不是真正DELETE。这样既保证业务数据完整,又能在需要的时候恢复误删的客户。

再比如客户归属问题,需要考虑客户被删除或销售离职时的处理。用on_delete=models.SET_NULL把owner置为空,同时可以配合一键转移功能把原先的客户重新分配给在职销售。学生的毕设虽然不会遇到真实的数据迁移需求,但把这两层逻辑做了,代码的健壮性就明显上一个档次。

2.3 数据库索引与查询优化

客户列表页会涉及多个条件的组合查询,没加索引之前,数据量到两万条的时候查询就开始卡了。一般情况下建议在phone、status、owner_id这几个高频筛选字段上建索引:

class Meta: indexes = [ models.Index(fields=['phone']), models.Index(fields=['status']), models.Index(fields=['owner', 'status']), ]

MySQL底层用的是B+树索引,加索引的本质是把原本全表扫描O(n)变成通过树结构检索O(log n)。但凡是都有两面,索引也是要占存储空间的,写操作也会因为维护索引变慢。好在客户关系管理系统的业务是典型读多写少,这种代价完全可以接受。

3. 核心功能模块的实现细节

3.1 客户信息管理模块

客户信息的增删改查是基本功,但想做得漂亮,需要在前端交互上花点心思。列表页建议实现分页、排序、多条件筛选三个功能,缺一个答辩时都可能被挑刺。

分页我通常用Django自带的分页器,每页显示10条或20条。筛选条件用GET参数传递,forms表单接收后构造filter()条件。这里有个小坑:多个筛选条件同时为空时,filter()参数也会是空的,此时要避免把所有空条件拼进查询集。我的写法是先构建一个conditions字典,然后动态拼接:

conditions = {} if keyword: conditions['name__icontains'] = keyword if status: conditions['status'] = status if owner_id: conditions['owner_id'] = owner_id customers = Customer.objects.filter(**conditions, is_delete=False).select_related('owner')

批量导入导出也是个容易加分的功能点。导入用pandas读取Excel或CSV文件,遍历每一行做数据清洗后写入数据库;导出直接生成CSV,利用csv模块写文件流,前端用<a>标签触发下载。我在实际带学生时发现,pandas处理编码问题最常见——用encoding='utf-8-sig'能避免Excel打开CSV中文乱码的情况。

3.2 跟进记录与客户状态流转

跟进的交互逻辑建议做成“时间线”样式,每个客户详情页里按时间倒序展示所有的跟进记录,新记录直接往下追加。这样做的好处是动态展示客户激活的完整过程,销售一眼能看到这个客户已经聊过几次、上次说到哪个阶段、下一次计划什么时候联系。

客户状态的流转要遵循业务规则,不能随便跳:

潜在客户 → 意向客户 → 成交客户 ↘ ↘ ↘ 流失客户 ← 流失客户 ← 流失客户

用Django的表单验证或模型层的clean()方法加业务约束,避免状态异常跳转。这个点虽然小,但能体现你对业务逻辑的理解深度,答辩时可以说“我根据销售漏斗模型设计了状态机流转”。

3.3 数据看板与统计图表

统计图表是整个系统视觉上的门面。我建议做三个核心图表:客户来源分布饼图、销售业绩柱状图、近6个月客户转化趋势折线图。技术方案是用ECharts,数据用Django ORM的聚合查询生成JSON传给前端。

比如统计每个销售的订单总金额:

from django.db.models import Sum sales_data = (Order.objects.filter(created_at__year=current_year) .values('owner__username') .annotate(total_amount=Sum('amount')) .order_by('-total_amount'))

返回的QuerySet可以直接序列化成JSON列表,ECharts拿到数据后填充到柱状图里。需要注意日期处理,聚合时如果不做分组,统计的是全量的数据,要和前端联动筛选条件保持一致,建议通过URL参数把时间范围传给后端,后端按条件过滤后再聚合。

有个细节容易被忽略:折线图的横坐标如果按月统计,会出现缺少数据的月份(比如某个月全公司零订单),这时候必须在前端做数据补齐,否则图表会断一条线。一般用JS先初始化12个月的数组,value默认填0,再用后端返回的数据去覆盖对应下标。

3.4 权限控制与操作日志

这部分是系统成熟的标志,也是评委喜欢看的点。Django的权限框架里,Group和Permission是两件套。我的做法是创建两个组:销售组和管理员组。销售组的权限范围是自己创建的客户和跟进记录,管理员组拥有全部权限。视图层用@login_required保证必须登录,再用@user_passes_test过滤角色:

def is_admin(user): return user.is_superuser or user.groups.filter(name='管理员').exists()

操作日志模块要记录用户在系统里做了哪些关键操作,比如新增客户、修改客户归属、删除客户。实现上我在每个视图函数里插一行OperationLog.objects.create(),或者在模型层用Django Signal的post_save和post_delete统一拦截,后者代码更集中。日志记录的内容至少包括操作人、操作类型、操作对象、操作时间、IP地址。

4. 实操过程中常见的坑与排查技巧

4.1 Django环境配置那点事

Python环境问题排在毕设踩坑榜第一名。我见过太多学生倒在这一步:明明代码没问题,就是跑不起来。Django版本和Python版本的匹配关系一定要提前确认,Django 4.2支持Python 3.8到3.12,Django 5.0要求Python 3.10以上。装了版本不对应,pip install django之后django-admin命令直接报ModuleNotFoundError。

虚拟环境是必须用起来的,python -m venv venv创建,然后激活。网络上很多教程会推荐用Anaconda,用是能用,但环境隔离效果不如原生venv。我通常建议:项目依赖全部写在requirements.txt里,换机器部署时一条pip install -r requirements.txt搞定。Windows用户注意,MySQLdb这个旧包在Python3里装不上,要用pymysql并配置:

import pymysql pymysql.install_as_MySQLdb()

4.2 中文字符集和乱码问题

数据库层面的UTF-8问题非常隐蔽。创建MySQL数据库时,如果没显式指定字符集,可能沿用的是latin1,存进去的中文在网页上显示成问号。建库语句要写清楚:

CREATE DATABASE crm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

utf8mb4是utf8的超集,能存emoji,推荐直接用。另外Django的settings.py里也要把数据库连接的OPTIONS设置成charset='utf8mb4',不然通过ORM写入还是容易出乱码。

实操时经常出现的一种情况:本地MySQL里中文显示正常,部署到服务器后乱码。排查方法是先在数据库命令行里执行SHOW VARIABLES LIKE 'character%',看看character_set_server是哪一种,大概率是服务器MySQL配置文件没改。改完my.cnf重启MySQL服务,问题基本解决。

4.3 N+1查询导致的页面卡顿

这是Django开发里的经典顽疾。客户列表页如果直接查所有客户,然后循环里访问customer.owner.username,每条客户记录都会额外发一次用户表查询。数据量小的时候没感觉,几千条记录之后页面响应时间就会涨好几秒。

解决办法是提前用select_related把关联表join出来,Django会生成一条LEFT OUTER JOIN语句,把用户表的字段一次性取回来。同理反向关联用prefetch_related。代码层面一眼就能看出区别:

# 慢:循环中N次查询 customers = Customer.objects.filter(is_delete=False) for c in customers: print(c.owner.username) # 快:一次查询join出owner customers = Customer.objects.filter(is_delete=False).select_related('owner')

这个坑在数据库设计的那节虽然提过,但实际写代码时学生还是老忘记。答辩前做一轮性能自测,把客户表造几万条测试数据,翻看列表页响应时间,就知道该不该优化了。

4.4 表单提交时的CSRF验证错误

Django默认开启CSRF(跨站请求伪造)防护,所有POST表单模板里必须包含{% csrf_token %}。新手最容易在写Ajax请求时忘记带请求头,前端控制台报403错误。排查时先打开开发者工具的Network面板,查看POST请求的请求体里有没有csrftoken字段。

如果是Ajax方式提交,更稳妥的方案是在页面里用JavaScript读取cookie里的csrftoken,然后设置到请求头:

function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; }

别再一个人硬啃这个报错,记住CSRF的本质是防止跨站请求伪造,Django要求每次请求带上同一个session里的token,服务器比对过才放行。这个原理懂了,遇到类似问题就能举一反三。

4.5 静态文件404的排查方法

开发环境跑客户关系管理系统,页面样式和图表加载不出来,九成是静态文件配置问题。检查顺序:

  • settings.py里DEBUG = True时,STATIC_URL和STATICFILES_DIRS要正确指向项目里的static目录。
  • 模板里要用{% load static %}加载静态文件标签,然后<link rel="stylesheet" href="{% static 'css/style.css' %}">这种写法引入。
  • 如果用Django自带开发服务器python manage.py runserver,静态文件服务是自动开启的。

真要部署到生产环境,就要用collectstatic把所有应用的静态文件收集到指定的STATIC_ROOT目录,再交给Nginx托管。这个步骤容易漏,漏了之后页面CSS全部丢失,光秃秃只剩HTML。

4.6 多人协同开发产生迁移冲突

如果这个毕设是团队做的,数据库模型的迁移文件经常打架。解决办法是约定好规则:每次pull代码后先检查migrations目录,看有没有新增迁移文件,然后python manage.py makemigrations生成自己改动的迁移,再python manage.py migrate同步数据库。

万一还是冲突了,最快的方法是删掉有争议的迁移文件,重新生成一份。不过前提是数据库还没执行过这个迁移。如果已经执行了,就需要用python manage.py migrate app_name zero回滚到初始状态,再重新迁移。这个操作像手术,要谨慎。

5. 系统测试与答辩准备

5.1 功能测试用例设计

毕设答辩时老师最常问的就是“你怎么保证系统是对的”。提前准备好测试用例很重要。别跟我说你手动点了几个链接就觉得万事大吉,那是自欺欺人。

建议至少覆盖下面这些场景:

  • 登录失败的情况,密码错误、用户不存在、被锁定。
  • 客户新增时手机号格式校验、字段必填校验。
  • 客户查询组合条件是否都能正确匹配。
  • 订单金额为负数或零时是否能被拦截。
  • 非管理员访问管理页面能否被重定向。
  • 大量并发登录时Session机制是否正常。

用Django的unittest框架写接口测试,数据库用测试环境自带的SQLite或者MySQL测试库,跑一遍需要的时间也不长。但效果非常明显,能提前发现不少粗心导致的低级bug。

5.2 答辩演示的节奏控制

答辩演示时最忌讳的是手忙脚乱地去操作。我一般建议按照故事线来演示:登录系统 → 新增一个客户 → 给这个客户添加跟进记录 → 把状态从潜在客户流转到意向客户 → 录入一笔关联订单 → 到统计页面看图表变化。

这条线走下来,评委能完整看到业务流程的闭环,比东点一下西点一下的演示效果好得多。演示数据尽量用测试账号预置好,别现场录入大量信息,万一键盘操作失误很尴尬。

还有一个容易被忽视的准备:把自己代码里用到的每个技术点能解释清楚。评委通常会挑一两个技术细节深挖,比如问“select_related和prefetch_related有什么区别”“为什么订单金额用Decimal而不用Float”。这些基础如果答不上来,前面的代码写得再好也会打折扣。

5.3 代码提交与文档整理

毕设最终交付物通常包括:项目源代码、数据库设计文档、系统说明书、答辩PPT。源代码建议传到Git仓库管理,即使团队只有自己一个人也要用Git,答辩时可以展示提交记录,体现工程化管理意识。

数据库设计文档里最好配上ER图,标注每个表的主外键关系和字段注释。我习惯用Django自带的django-extensions库导出ER图,命令是python manage.py graph_models -o er.png,快速省事。

系统说明书别写得太飘,重点描述每个功能模块的操作流程和对应截图,加上部署步骤。这份文档评委未必细看,但导师和评审老师一定会翻,写得规范能留下一个认真负责的印象。

写在后面的一些经验

我带过几届做客户关系管理系统的学生,最大的感触是:这个题目就像一套基本功的组合拳,不难,但想打漂亮需要真正理解业务的流转逻辑。不要把它当成CRUD堆砌,而是当成一个销售团队每天都在用的效率工具来设计。多想想“用户为什么需要这个功能”“这个数据能辅助什么决策”,代码的层次感就出来了。

如果时间允许,建议在主线功能之外再挑一两个自己感兴趣的点做深,比如客户画像标签体系、销售漏斗转化率分析、基于RFM模型的客户价值分层。这些虽然不是系统的必需功能,但会显著提升答辩时的上限。动手写代码之前先花两天时间把数据库设计好,把每个模块的接口理清楚,后面写起来会顺手很多。毕设是一个从零搭建完整系统的训练过程,认真做完这一套,你的工程能力会有实打实的提升。

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

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

立即咨询