ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

自建轻量CRM系统实战:Flask+MySQL实现客户管理与权限控制

自建轻量CRM系统实战:Flask+MySQL实现客户管理与权限控制 1. 项目起源与核心需求拆解1.1 为什么放弃现成CRM选择自建一套先说背景。我所在的团队大概七八个人一直靠共享Excel表格和微信群里翻聊天记录来管客户客户信息散落在各人电脑里谁跟进到哪一步全靠问。后来客户到了四五十个明显感觉撑不住了想上CRM但看了一圈市面上的产品要么按坐席收费一年下来成本不低要么一些所谓“永久免费”的版本在人数、数据量、自定义字段上设了一堆暗门槛。于是动了自建一个内部CRM的念头项目代号就叫DeskcommCRM。DeskcommCRM要解决的痛点很明确把客户资料、跟进记录、待办任务、销售漏斗统一到一个可以随时随地访问的Web系统里让每个人登录后台就能看到自己名下客户的全貌同时管理层能看整体数据。这不是一个要从零发明轮子的项目而是要用最省事的方式把一个稳定可靠的CRM跑起来。考虑到团队不是专业开发人员我自己也只是懂一些Python和服务器运维的偏门经验所以技术选型上刻意避开了重框架、重架构的路线。这里想清楚了一个核心原则CRM的灵魂是数据结构和服务可用性不是技术栈多么炫酷。哪怕界面朴素一点只要客户资料不丢、字段能自定义、多人能同时用就已经赢过了市面上大部分“试用期结束就冻结数据”的免费SaaS。在这个阶段我做了两类产品的详细对比也给跟我一样在纠结“到底是买现成还是自己搞”的朋友一个参考对比维度付费SaaS CRM免费/开源CRM自建轻量CRM本项目初期成本按年付费中小团队也有压力部分免费但高级功能收费仅服务器成本数据归属在厂商服务器上在厂商服务器上完全自有自定义能力受产品限制改字段常要加钱受版本限制完全可控维护成本不用管不用管需要自己维护2-4小时/月永久在线依赖厂商策略免费版可能有访问限制服务器不关就一直在线1.2 核心用户画像与功能边界真正动手之前先画了用户画像。DeskcommCRM最核心的使用者是两类人第一类是销售/客户经理他们每天要查询客户资料、记录跟进电话或拜访内容、新增客户、领取待办任务。他们的诉求是“快”打开系统到找到客户信息最好三秒以内完成记录跟进像发微博一样简单。第二类是团队负责人要看到每个销售的客户数量、跟进频率、商机金额和最近动态判断哪些客户可能流失、哪些跟进该提醒了。明确了使用者之后功能边界就非常清晰了客户档案、跟进记录、任务提醒、销售漏斗统计、团队成员与权限管理。一开始没有做复杂的工单系统或营销自动化因为那会让自己陷入开发泥潭。项目目标是两周内上线可用版本一个月内通过实际使用反馈迭代出符合团队习惯的形态。这也回应了一个很多人问我的问题免费CRM和私人网站/私人部署的区别到底在哪区别在于“你拥有什么”。免费CRM的厂商随时可以调整免费策略你的数据迁移成本极高而私人部署的CRM它的数据、接口、字段、权限都完完全全掌握在自己手里。付出的代价就是服务器费用和维护精力但换来的是一套真正属于团队的“永久在线”系统。2. 技术选型与整体架构设计2.1 技术栈选择的底层逻辑DeskcommCRM的技术栈非常朴素但每一样都是经过测试的稳定组合后端Python Flask轻量、上手快、插件生态够用数据库MySQL 8.x也可以替换成PostgreSQL二选一即可前端Bootstrap 5 Jinja2模板不搞前后端分离减少开发和部署复杂度部署Nginx Gunicorn systemd保证进程守护和开机自启缓存/会话Redis用于session共享后续如果扩展多节点也方便选Flask而不是Django的原因很简单。Django自带Admin后台、ORM、迁移工具功能很全但对一个小团队内部系统来说有些笨重Flask可以把代码组织得更加轻快路由直观模型层用SQLAlchemy也一样顺手。还有一个考量是团队里有同事想参与二开Flask的学习曲线比Django平缓不少。有人可能会问为什么不用现成的开源CRM比如SuiteCRM、EspoCRM、Odoo这些确实是成熟方案安装即用。但我需要的是一个完全贴合销售流程的系统开源产品往往预设了一堆用不上的模块自定义字段和表单逻辑的学习成本反而比从零写一个更高。而且从维护角度说自己写的系统遇到问题能直接改代码不需要去翻第三方文档和社区求助。2.2 数据模型设计实战整个系统的核心是数据表结构这一步决定了后续所有功能的开发效率。我花了一整天画表和关系踩过的坑包括客户和联系人的关系不够清晰、跟进记录和任务的字段设计得过于冗余等。最终稳定下来的核心表如下customers客户表id主键name客户名称/公司名industry所属行业source客户来源展会、转介绍、线上广告等level客户等级A/B/C/Downer_id负责人IDphone、email、address等联系方式remark备注created_at、updated_at时间戳contacts联系人表id、customer_id外键关联客户name、position、phone、wechatis_primary是否主要联系人follow_ups跟进记录表id、customer_id、contact_id可选content跟进内容next_plan下一步计划follow_type跟进方式电话/拜访/邮件等creator_id、created_attasks任务表id、customer_id、title、descriptiondue_date截止时间status待办/已完成/已取消assignee_id执行人creator_id、created_atusers用户表id、username、password_hash、real_nameroleadmin/manager/memberrole在权限判断里非常关键is_active、last_login_at字段设计上有一个很重要的经验客户等级和合作状态这类枚举字段一定要用代码维护一个config表或Python枚举类不要直接在数据库里散落各种值。否则半年后你会发现数据里出现了“A级”“A”“a”三种写法统计口径彻底乱掉。2.3 永久在线的部署架构“永久在线”是DeskcommCRM被问到最多的一个点。这其实是个部署架构问题而不只是代码问题。我的部署方案直接采用云服务器 systemd守护进程 域名HTTPS。云服务器选择上2核2G内存的入门机型就够用了操作系统用Ubuntu 22.04 LTS。这个规格承载一个每天几十人访问的内部系统绰绰有余。价格方面国内主流云厂商新用户活动价一年大概几百块比付费CRM便宜太多。进程守护是“永久在线”的关键。用systemd管理Gunicorn进程设置Restartalways这样即使代码bug导致进程崩溃systemd也会在三秒内自动拉起服务。同时用logrotate做日志轮转防止日志文件把磁盘塞满。对于外网访问如果有公网IP直接解析域名到服务器再用certbot申请Lets Encrypt证书启用HTTPS即可。如果没有公网IP也可以用内网穿透工具——但那是另一个话题我觉得既然要做一个正式的内部管理系统还是建议直接上云服务器稳定性和安全性都在可控范围内。3. 核心功能模块与实现细节3.1 客户管理模块的实现重点客户管理模块是DeskcommCRM最核心的部分它要解决的是“客户档案从分散到统一”的问题。实现上做了三件事第一客户列表的分页和筛选。列表页支持按负责人、客户等级、来源、关键字搜索全部通过SQLAlchemy的动态查询实现。这里一定要在owner_id和created_at上加索引否则客户数据过千之后列表查询会肉眼可见地变慢。我实际测试过不加索引时两千条数据查询要三四秒加上复合索引之后直接降到几十毫秒。第二客户详情页的时间线设计。点进一个客户能看到这个客户从建立档案开始的所有跟进记录、历史任务、资料修改日志。这个设计让新人接手客户时能快速了解来龙去脉不用再翻聊天记录。跟进记录的富文本框我用的是Contenteditableplain text保存没有上富文本编辑器因为销售们写跟进记录就是要快格式反而不重要。第三客户去重。这个容易被忽略。Excel时代常见的问题是同一个客户被录入两次导致统计口径混乱。我在新建客户时做了重名校验如果名称相似度超过85%就提示是否合并。实现上用了一个简单的方式先查完全同名再查去掉空格和特殊符号后的同名校验没有上模糊匹配算法效果也已经够用了。3.2 跟进记录与任务提醒的联动逻辑跟进记录和任务不是孤立的功能模块它们之间要有联动。我在设计时定了一条规则写跟进记录时可以顺便创建下一步任务任务到时自动提醒。比如销售给客户打完电话在跟进内容里写“对方对报价单有兴趣下周三前给出优惠方案”这时候系统会提取并让用户确认是否创建一个“给客户发送优惠方案”的任务截止时间默认是下周三。这个设计极大提升了销售的使用意愿。他们不用单独跑去任务模块新建任务在记录跟进的场景里就顺手把后续动作安排好了实际用下来任务创建率提升了将近一倍。任务的提醒方式我做了站内消息和邮件提醒两种。站内消息是一登录就能看到的待办清单邮件提醒则是每天上午九点把当天到期和逾期的任务汇总发到用户邮箱。服务器上配了一个crontab定时任务调Flask的CLI命令发送邮件提醒没有引入Celery这样的重型队列框架。3.3 员工邀请与权限管理的实操方案热词里提到的“飞鱼CRM怎么邀请员工”本质就是多用户系统的团队管理功能。这一点DeskcommCRM实现得比较细致我把完整的员工邀请和权限管理流程分享出来。邀请员工流程第一步管理员在后台点击“添加成员”输入对方邮箱或手机号。第二步系统生成一条带token的邀请链接有效期为24小时同时向邮箱发送邮件或在手机端显示邀请码。第三步被邀请人打开链接设置自己的登录密码即完成注册并自动关联到团队。第四步管理员在成员列表里为其分配角色admin/manager/member和默认数据权限范围。权限管理的粒度admin拥有系统全部权限包括成员管理、参数配置、数据删除。manager可以查看团队内所有客户数据能分配和转移客户不能修改系统设置和删除成员。member只能看到自己名下及被共享给自己的客户不能查看他人客户列表。代码层面用装饰器实现权限控制在Flask里写了一个login_required和role_required装饰器。每个路由上标注允许的角色比如role_required(admin, manager)未授权访问直接返回403页面。这个方案在代码维护上非常直观新加一个功能时只需在路由上加装饰器就能完成权限控制。实际操作中遇到的坑邀请链接的token一定要设置过期时间并做单次使用校验否则拿链接的人可能反复用它注册出多个僵尸账号。还建议记录邀请人的ID团队审计时能追溯是谁拉进来的成员。3.4 销售漏斗与统计看板光记录数据不产出洞察系统就没有灵魂。统计看板模块做了三个核心视图第一个是销售漏斗图按客户等级和商机阶段统计金额。我在customers表里加了一个deal_amount字段和一个stage字段stage表示当前商机阶段初步接触/方案确认/商务谈判/已成交。漏斗图用纯前端绘制基于ECharts的funnel类型数据由后端API返回JSON格式。这里注意不要在ORM查询后直接传给前端要转换成前端友好的结构比如{stage: 方案确认, count: 12, amount: 300000}。第二个是个人绩效看板。每个销售登录后第一眼看到的就是自己的本周跟进次数、新增客户数、待办任务数、本月成交金额。这些数据用简单的SQL聚合查询即可关键在于日期范围的判断。我用的是Python的datetime模块计算本周起始日周一和截止日SQLAlchemy查询时加created_at week_start即可避免时区问题。第三个是客户动态预警。如果某客户的跟进记录超过15天没有新增系统就自动标记为“沉寂客户”提醒负责人主动回访。这个逻辑用定时任务扫描导入一个函数update_inactive_flags()每天凌晨执行一次。将预警结果写入customers表的一个is_inactive字段并在列表页用红色高亮展示。这样管理层不用每天翻数据系统主动告诉你要关注谁。4. 实操部署全流程记录4.1 服务器初始化与环境准备部署部分我给出一版从零开始的完整流程跟着做就能跑起来。假设你有一台Ubuntu 22.04的云服务器SSH能连上。# 更新系统 sudo apt update sudo apt upgrade -y # 安装Python和数据库 sudo apt install -y python3-pip python3-venv mysql-server nginx sudo mysql_secure_installation # 创建数据库和专用用户 sudo mysql CREATE DATABASE deskcomm_crm DEFAULT CHARACTER SET utf8mb4; CREATE USER crm_userlocalhost IDENTIFIED BY 你的强密码; GRANT ALL PRIVILEGES ON deskcomm_crm.* TO crm_userlocalhost; FLUSH PRIVILEGES; EXIT;这里为什么用utf8mb4而不是utf8因为MySQL的utf8最多只支持3字节字符像emoji和生僻字会被截断或报错。既然系统里客户备注和人名都有可能出现特殊字符直接用utf8mb4一劳永逸。数据库用户名和密码一定要用强密码这个数据库里有你的客户资料泄露了后果很严重。4.2 应用部署与Nginx反向代理配置项目代码上传到服务器后使用虚拟环境安装依赖cd /opt/deskcomm-crm python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # 初始化数据库 flask db upgrade flask init-data # 创建初始管理员账号Gunicorn作为应用服务器直接命令行启动测试一下能否正常运行gunicorn -w 3 -b 127.0.0.1:8000 app:create_app()能跑通之后把它写成systemd服务保证持久化运行[Unit] DescriptionDeskcommCRM Gunicorn Service Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/opt/deskcomm-crm EnvironmentPATH/opt/deskcomm-crm/venv/bin ExecStart/opt/deskcomm-crm/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:create_app() Restartalways RestartSec3 [Install] WantedBymulti-user.target注意Restartalways是为什么要强调因为你永远不知道代码什么时候会写了一个隐藏bug进程说崩就崩。有了systemd守护即使半夜两点崩溃了它也能在无人值守的情况下自动恢复这是“永久在线”的第一道保险。Nginx配置反向代理加HTTPSserver { listen 80; server_name crm.example.com; 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; proxy_set_header X-Forwarded-Proto $scheme; } }然后用certbot一键签发续期HTTPS证书sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d crm.example.com这里解决的就是免费CRM与私人网站的核心差异之一私人部署后只要服务器在线、SSL证书在有效期内、进程有守护你的CRM就是永久在线的任何时间登录都能用不会有免费版SaaS的“试用到期”“连接超时”“等待审核”这类问题。4.3 定时备份与灾难恢复方案客户数据是CRM里最值钱的部分备份的问题一定不能省。我写了一个备份脚本放在/etc/cron.daily/backup_crm.sh每天凌晨3点执行#!/bin/bash BACKUP_DIR/data/backups/crm DATE$(date %Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR mysqldump -u crm_user -p密码 deskcomm_crm | gzip $BACKUP_DIR/crm_$DATE.sql.gz # 保留最近30天的备份 find $BACKUP_DIR -name *.sql.gz -mtime 30 -delete脚本内容很简单但价值极高。我建议除了本机备份之外定期把备份文件同步到另一台服务器或对象存储防止服务器本身硬件故障把备份一起带走。这个可以用rclone配合对象存储或者网盘实现Linux自带的rsync也可以。迁移恢复时只需要在新服务器上建好数据库然后执行gunzip crm_20250101_000000.sql.gz | mysql -u crm_user -p deskcomm_crm整个过程十几分钟就能完成数据一条都不会少。5. 常见问题与排查技巧实录5.1 登录会话丢失与Redis配置问题部署完成后团队刚开始使用反馈最多的一个问题登录每隔一段时间就被踢出来操作到一半提示未登录。排查后发现是session存储方式的问题。Flask默认的session是签名的cookie大小只有4KB我往session里塞了用户ID、角色、权限列表之后每次请求来回传输的数据量过大而且浏览器端的cookie过期策略和服务器端不一致导致频繁掉线。解决方案是改用Redis存储session。这个改动在Flask里很简单引入Flask-Session库配置SESSION_TYPEredis指定Redis连接地址即可。Redis里存储session还有一个好处就是未来如果系统要扩展成多个应用实例session是集中存储的任何一台实例都能验证用户身份天然支持水平扩展。注意如果Redis设置了密码一定要在配置里正确填写。还有给Redis设置合适的内存淘汰策略用allkeys-lru就可以防止session数据越来越多把内存占满。5.2 并发写入导致的数据覆盖问题销售团队最常做的一个动作是在客户详情页记录跟进的同时另一个同事可能正在给这个客户修改等级和负责人。MySQL默认的事务隔离级别是REPEATABLE READ如果两个请求同时读取同一行再分别更新后提交的会把先提交的覆盖掉但这里覆盖的是不同的字段表现形式就是这次跟进记录的负责人字段突然变成了空。这个问题定位了很久才找到根因。后来在更新操作中做了行级锁和字段级别校验。简单说就是执行UPDATE前先SELECT ... FOR UPDATE锁住这行然后比较当前数据和提交时数据的版本号不一致就提示“该客户已被其他同事修改刷新后重试”。但实际使用中这种锁机制会给用户增加操作成本。更优雅的做法是改成字段级的“部分更新模式”也就是在编辑表单时只提交发生变化的字段而不是把整个对象的全部字段一起提交。配合更新时间戳的last_login_at逻辑判断基本能杜绝覆盖问题。5.3 备份恢复后出现的中文乱码有次在测试环境做备份恢复演练恢复完成后发现中文名称全部变成了问号。排查后确认是字符集问题mysqldump导出时的默认字符集和导入目标库的字符集不一致。解决办法是导出时强制指定字符集mysqldump -u crm_user -p --default-character-setutf8mb4 deskcomm_crm | gzip backup.sql.gz同时恢复前确认目标库的默认字符集是utf8mb4ALTER DATABASE deskcomm_crm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这个坑不在于代码而在于数据库运维的细节。建议备份脚本里显式加上--default-character-setutf8mb4不要依赖MySQL的默认配置否则换个版本或环境就会踩坑。5.4 Nginx 502 Bad Gateway的快速定位系统上线后有过一次502。当时Nginx返回502但服务器负载不高也没有重启记录。排查步骤是这样的第一步看Nginx错误日志/var/log/nginx/error.log发现是connect() failed (111: Connection refused) while connecting to upstream。第二步确认Gunicorn进程状态systemctl status deskcomm-crm发现进程状态是active (running)这就矛盾了。第三步看Gunicorn的日志发现worker超时被kill。原因是有一个统计报表的SQL查询特别慢超过了Gunicorn默认的30秒超时时间worker被杀后master自动拉起新worker但那个请求已经断开Nginx就报了502。解决措施是在Gunicorn启动参数里加--timeout 120同时优化那条慢SQL加索引和查询缓存两条腿走路才彻底解决问题。这也提醒我复杂报表查询至少要给数据库加索引不然用户一多并发一上来就会接连踩中超时的坑。5.5 快速排查速查表症状可能原因排查命令/位置解决方案无法访问网站Nginx未启动/防火墙拦截systemctl status nginxsudo ufw status启动Nginx放行80/443端口页面能开但登录失败数据库连接失败journalctl -u deskcomm-crm重启MySQL核对数据库账号密码登录后立刻掉线Session配置问题检查Flask配置启用Redis存session上传文件失败磁盘空间不足df -h清理日志或扩容磁盘定时任务不执行crontab环境变量问题crontab -e/var/log/syslog脚本中写全绝对路径6. 运行一段时间后的经验沉淀6.1 给“免费CRM”与“私人搭建”之争画个句号DeskcommCRM跑了大半年我对免费CRM和私人网站也就是自部署系统的区别有了更实际的认识。免费CRM适合个人或极小团队试试水没有技术维护能力又想快速看效果那就先用免费版但如果你的客户数据是有积累价值的永远不要把自己的核心资产寄托在别人的免费策略上。私人部署这条路线付出的是服务器钱和维护时间换来的是数据主权、功能完全自定义、和“永久在线”的确定性。所谓“永久在线”不是一个抽象概念。对销售来说就是凌晨想起客户要求打开浏览器登录就能填跟进记录对管理层来说就是早上九点的提醒邮件准点出现在邮箱里对老板来说就是无论出差到哪儿手机上打开网页就能看到实时销售数据。这些体验只要服务器稳定运行就一直在不会有厂商调整策略、免费额度用尽、数据被锁这种不确定性。6.2 后续扩展与优化方向目前DeskcommCRM的下一步计划有两条。第一条是签约数据的深度分析比如把成交周期、客户来源渠道转化率、流失原因做一个多维交叉报表让管理决策有数据支撑。第二条是做一个客户公海池的概念销售超过两周未跟进的有效客户自动流转回公海池供其他成员领取激活潜在价值。功能上还想加一个自定义字段功能让管理员自己在界面上新增客户字段类型不需要改代码。这个功能的底层设计是给customers表加一个custom_fields的JSON字段配合前端动态渲染表单。JSON字段在MySQL 8里原生支持查询效率也不错不改表结构就能满足灵活的字段扩展需求。6.3 给同样想自建CRM的朋友几句实在话如果你也想搞一套内部CRM我给几条实在的建议。第一第一版千万不要贪多把客户管理、跟进记录、提醒任务做扎实比做十个花哨模块有用得多。第二部署时一定要把自动备份从第一天就做好否则哪天数据库坏了你连后悔的机会都没有。第三权限模型在开始时就要想清楚宁可先收紧再放开不要一上来全员都是管理员。最后一条尽量让一线销售参与测试和反馈他们觉得好用系统才真正活得起来他们觉得难用再漂亮的功能也是摆设。
返回列表