先说个背景。去年团队规模从三个人扩到十来个人的时候我们做的第一件事不是换办公室而是认真解决客户信息管理的问题。之前客户资料全躺在个人微信、Excel 表格和邮箱里每个人记法还不一样有人记在备注里,有人单独建了个文档开会复盘时光是“这个客户到底谁跟进过”就要花十分钟去溯。市面上免费的 CRM 试了一圈要么功能锁得死死的要么数据越放越多心里越发虚最后我决定自己搭一套可长期维护的客户管理系统代号 DeskcommCRM。这套系统从立项到现在跑了差不多一年稳定扛住了日常的客户录入、跟进、工单流转和报表统计今天把这套系统的完整思路、架构设计、部署步骤和踩过的坑一次性整理出来。不管你是刚准备给团队上 CRM 的负责人还是想给自己折腾一套“数据完全归自己管”的客户系统的开发者这篇文章应该都能提供一份不错的参考。我会把关键的技术选型、核心数据模型、部署流程和常见故障排查都写清楚不会只讲概念会尽量落到可以照着做的程度。1. 项目思路与整体设计拆解1.1 为什么自己搭一套而不是直接用免费的 SaaS CRM这是一个绕不开的问题。目前市面上的免费 CRM 非常多听起来 “免费”两个字好像很划算真把核心业务流程跑起来就会发现问题。一是功能边界非常严格。免费版通常意味着用户数受限、客户记录数受限、报表能力被砍、API 访问被限制。团队在 3 个人的时候这些限制还不明显一旦到了 10 人以上免费版基本就是在逼你升级付费版而且价格不是按用户数线性涨是直接跳档上涨。二是数据所有权的问题。客户资料是公司最核心的资产之一放在别人服务器上虽然一般不会出问题但你的数据导出、备份、迁移手段都受制于人。有些平台导出数据要联系客服甚至导出格式还不友好。对于我们自己来说客户跟进记录、沟通历史、合同金额这些信息应该存放在自己可控的基础设施上这是做这个项目最原始的出发点。三是定制能力。销售团队的管理方式和业务形态千差万别标准的 CRM 字段未必贴合你的流程。比如我们团队特别需要“客户来源渠道 最近跟进时间 下次跟进提醒”的组合视图很多 SaaS 产品做不了这种自定义筛选或者做起来交互极其繁琐。自建的系统可以完全按照团队流程来设计字段、状态机和权限边界想改就改不需要提工单等排期。当然自建不是没有代价。服务器成本、运维投入、安全加固都要自己负责这也是很多团队不愿意碰自建的原因。但如果你愿意花一到两天时间把基础环境搭好后续维护工作量其实是可控的。1.2 DeskcommCRM 的核心模块规划开始写代码之前我花了大半天梳理团队到底需要哪些模块。很多自建项目死在“什么都想做”所以我一开始就把范围卡死只做这五个核心模块客户管理维护客户基础信息名称、行业、规模、来源渠道、联系人、地址支持自定义标签和状态流转线索、潜在、跟进中、成交、沉睡、流失。跟进记录每次和客户沟通后记录时间、方式、沟通摘要、下一步计划所有记录按时间线展示不允许编辑历史只允许补充防止事后改口。商机与合同把有意向的客户转成商机关联预计金额、预计成交时间、阶段推进历史成交后关联合同编号。工单/售后客户报修、售后问题统一走工单流程支持分配处理人、优先级、状态流转、处理备注。数据报表按日/周/月维度统计新增客户数、跟进次数、商机金额、成交转化率用于每周复盘。这个模块划分并不复杂但能覆盖一个销售驱动型小团队 90% 的日常工作。我刻意没有做复杂的营销自动化、客户公海、销售漏斗打分这些功能因为第一版上线后真正用起来再迭代比一开始就堆十个模块结果每个都半成品强得多。1.3 技术选型背后的逻辑技术栈我选了 LNMPLinux Nginx MySQL PHP加 Redis没有用 Java 重框架也没上微服务。原因很简单一个十几人团队的内部 CRM核心诉求是开发效率高、维护成本低、出问题好排查而不是百万级并发。PHP 配合 Laravel 框架开发这类业务系统非常顺手模型关系、队列、定时任务、权限中间件都是现成的生态成熟遇到问题搜一下就能找到答案。缓存和 Session 用 Redis 而不是文件缓存是因为 CRM 系统对多用户并发访问的 Session 一致性要求比较高。文件形式的多机同步非常麻烦直接用 Redis 做集中式 Session 存储后续如果要水平扩容加一台应用服务器改一下配置就好不用动应用代码。Nginx 负责静态资源和服务入口PHP-FPM 处理动态请求MySQL 存业务数据Redis 做缓存和队列驱动这套组合在单机上跑两三百人的团队都没有压力。所以如果你也在纠结技术选型我的建议是能用单体架构解决的事情别急着上微服务能用 PHP/Python/Node 这些高效语言做的事不要在 Java 的构建周期里消耗时间。2. 系统架构与核心数据模型设计2.1 整体架构一个入口、两个存储、一个任务调度DeskcommCRM 的部署结构很简洁总共四个角色Nginx对外统一入口处理 HTTPS、静态文件、反向代理PHP-FPM运行业务代码Laravel 框架MySQL主数据库存储客户、跟进、商机、工单、用户等全部业务数据Redis缓存、Session、队列任务另外还有一个系统级 Cron 定时任务负责处理邮件队列、数据备份、定期报表生成。这四个服务在一台 4 核 8G 的云服务器上跑得非常轻松日常负载基本没超过 20%。集群和负载均衡我没有做。对于内部 CRM 场景单机加完善的备份策略已经足够可靠。真到单机扛不住的那天这套结构也支持快速升级Nginx 负载均衡层加一台应用服务器、数据库主从分离即可代码层面不需要大的改动。2.2 数据模型怎么设计才不容易后期改到崩溃数据库表设计是这个系统里最值得花心思的部分。我第一版建表的时候踩过一个坑把客户状态直接用一个枚举字段存起来后来业务流程调整要新增一个“待回访”状态不得不改表结构数据迁移、代码改动牵一发动全身。后来我改成了更通用的设计状态不直接在客户表里写死而是用状态流转表记录每一次状态变更。客户表只保留当前状态的引用状态变更作为一个独立事件存下来这样任何时候都能复盘“这个客户是怎么一步步从线索变成成交的”而且新增状态只需要在枚举配置里加选项不需要改表结构。核心表结构大概是这样的users用户表字段包括 id、name、email、password、role_id、is_active、last_login_at。customers客户表字段包括 id、user_id负责人、name、industry、source、status_id、contacts、remark、next_follow_at、created_at、updated_at。follow_ups跟进记录表字段包括 id、customer_id、user_id、type通话/微信/邮件/拜访、content、next_action、created_at。status_histories状态流转记录表字段包括 id、customer_id、from_status、to_status、user_id、changed_at。deals商机表字段包括 id、customer_id、title、amount、expected_close_date、stage、status。tickets工单表字段包括 id、customer_id、title、handler_id、priority、status、content、created_at。roles 和 permissions角色表和权限表配合 Laravel 的权限包做鉴权。这里想特别说一下 customers 表的 next_follow_at 字段。这个字段是后来上线一个月才补上的因为大家查看客户列表的时候最关心的是“我今天应该跟哪些客户聊”单纯按更新时间排序看不出任何优先级。有了这个字段首页列表可以按“跟进计划时间”倒序排列快到期的客户自然顶到最前面销售基本不用自己记“明天该联系谁”了。2.3 权限模型从单人使用到多人协作的权限收敛单人使用的时候权限控制无所谓但多人团队就必须把“谁能看什么、谁能改什么”理清楚。我设计的是三层权限模型功能权限控制用户可以访问哪些菜单比如只有管理层可以看报表只有运营可以配置客户来源渠道。数据权限控制用户可以看哪些客户的资料。默认设置是“私有 共享”普通用户只能看自己负责的客户管理员和直属上级可以看整个团队的数据。操作权限控制用户能执行哪些操作比如普通销售可以创建客户、写跟进但不能删除客户可以修改自己的跟进记录但不能改别人的记录。这个权限模型是通过 role_id 关联用户表配合 Laravel 的 Gate 和 Policy 机制实现的。实际开发中我建议不要自己造权限框架直接用现成的方案比如 Laravel 生态的 spatie/laravel-permission可以省掉很多细节处理上的麻烦。关于新员工接入我这里做的是管理员在后台点击“邀请成员”生成一个带 token 的邀请链接通过邮箱发送给新同事。新同事点开链接设置密码后账号自动启用并默认分配一个“销售”角色。整个流程非常顺即使不懂技术的同事也能自己完成注册。3. 从零部署实操让 DeskcommCRM 跑起来3.1 服务器环境准备与基础配置部署这块我尽量说细一点因为实际踩过的坑和官方文档里写的有很多差异。首先是服务器选型我用的是 4 核 8G 内存的云服务器系统选择 Debian 12。纯净系统安装完成后第一时间更新软件源和系统组件apt update apt upgrade -y然后安装基础工具包括 Git、curl、vim、软件源配置工具apt install -y git curl vim wget gnupg lsb-release ca-certificates安装 Nginx、PHP 及扩展、MySQL、Redis。这里强调一下PHP 扩展不要只装基础版一定要根据框架需求装齐不然后续 composer install 的时候会有大量报错。# 安装 Nginx apt install -y nginx # 安装 PHP 及常用扩展 apt install -y php-fpm php-mysql php-redis php-mbstring \ php-xml php-curl php-zip php-gd php-bcmath php-intl # 安装 MySQL apt install -y mysql-server # 安装 Redis apt install -y redis-server安装完成之后先把 Redis 和 MySQL 设置为开机自启并确认服务状态systemctl enable redis-server mysql systemctl start redis-server mysql这个阶段最容易出问题的是 PHP 版本和扩展不完全匹配。在 Debian 12 上默认装的是 PHP 8.2主流框架和扩展都已经支持得比较好了所以没有过于纠结版本。如果以后要装一些老项目建议先php -v确认版本再根据项目要求调整软件源。3.2 Nginx 站点配置与 PHP-FPM 调优项目代码我放在/var/www/deskcommcrm目录下然后用 Nginx 配置一个站点。这里给出一个实际能用的配置里面已经包含 PHP-FPM 转发和伪静态处理server { listen 80; server_name crm.example.com; # 换成你自己的域名 root /var/www/deskcommcrm/public; index index.php index.html; charset utf-8; access_log /var/log/nginx/deskcommcrm.access.log; error_log /var/log/nginx/deskcommcrm.error.log; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }写这段配置的时候要注意几个细节一是root必须指向 Laravel 项目的public目录不然所有请求都会打到index.php之外导致路由失效。二是try_files的写法要保证所有非真实文件的请求都重新路由到index.php这样 Laravel 才能处理 RESTful 风格的 URL。三是 PHP-FPM 的 socket 路径必须和系统实际安装的版本一致如果你装的是 PHP 8.1那这段路径要改成php8.1-fpm.sock。配置完成后重新加载 Nginx然后测试一下是否能访问到 Laravel 默认页面nginx -t systemctl reload nginxPHP-FPM 我也简单做了一点调优主要是把并发处理能力提上去。编辑/etc/php/8.2/fpm/pool.d/www.conf把pm dynamic改为如下参数pm dynamic pm.max_children 30 pm.start_servers 5 pm.min_spare_servers 5 pm.max_spare_servers 15如果你不确定这些数值合不合理最笨但也最有效的方法是看运行日志和监控图。刚开始配置保守一些之后观察内存和 CPU 使用率再做调整。3.3 MySQL 数据库初始化与 Redis 配置MySQL 8.0 安装完成后默认 root 用户需要通过 auth_socket 方式登录也就是直接以系统 root 身份执行mysql命令。我建议新建一个业务专用账号不要用 root 跑应用降低被拖库后的影响面-- 用 root 身份进入 mysql -- 创建数据库 CREATE DATABASE deskcommcrm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建业务账号并授权 CREATE USER crm_userlocalhost IDENTIFIED BY 这里换成强密码; GRANT ALL PRIVILEGES ON deskcommcrm.* TO crm_userlocalhost; FLUSH PRIVILEGES; EXIT;数据库字符集一定要选 utf8mb4这样客户名称、备注里有 emoji 或生僻字也能正常存储不会出现乱码。Redis 默认绑定了127.0.0.1这个配置对单机部署来说已经足够安全不需要额外改端口也不要开启protected-mode no。如果以后增加独立应用服务器再考虑用专用内网 IP 绑定 Redis不要直接暴露到公网。3.4 部署应用代码与环境配置代码部署我用的是 Git 加 Composer。在服务器拉取项目代码后先安装依赖cd /var/www/deskcommcrm cp .env.example .env composer install --no-dev --optimize-autoloader然后编辑.env文件把数据库、Redis、应用地址等信息填进去。这里需要注意几个最容易写错的点APP_NAMEDeskcommCRM APP_ENVproduction APP_DEBUGfalse APP_URLhttps://crm.example.com LOG_CHANNELstack DB_CONNECTIONmysql DB_HOST127.0.0.1 DB_PORT3306 DB_DATABASEdeskcommcrm DB_USERNAMEcrm_user DB_PASSWORD你的数据库密码 CACHE_STOREredis SESSION_DRIVERredis QUEUE_CONNECTIONredis REDIS_HOST127.0.0.1 REDIS_PASSWORDnull REDIS_PORT6379填完之后依次执行数据迁移、生成应用密钥、优化缓存php artisan key:generate php artisan migrate --force php artisan config:cache php artisan route:cache php artisan view:cache这一步执行完后我的第一版系统就基本能跑通了。有一点要提醒php artisan config:cache执行后如果再改.env文件必须重新执行一次缓存刷新否则新配置不会生效。我曾经因为改了数据库密码但忘了刷缓存排查了半小时才发现应用还在用旧配置。3.5 定时任务与邮件通知配置CRM 系统里有很多任务是定时执行的比如每天生成跟进提醒、每周发送数据周报、定期清理临时文件。Laravel 的任务调度需要系统 Cron 每分钟触发一次crontab -e加入以下一行* * * * * cd /var/www/deskcommcrm php artisan schedule:run /dev/null 21邮件通知我用了 SMTP 方式对接的是企业邮箱服务。编辑.env里的邮件相关配置MAIL_MAILERsmtp MAIL_HOSTsmtp.example.com MAIL_PORT465 MAIL_USERNAME你的完整邮箱地址 MAIL_PASSWORD邮箱授权码 MAIL_ENCRYPTIONssl MAIL_FROM_ADDRESS你的完整邮箱地址 MAIL_FROM_NAMEDeskcommCRM这里踩过几个坑。首先很多人会直接在 MAIL_PASSWORD 里填邮箱登录密码但大多数企业邮箱要求填的其实是开启 SMTP 服务后生成的授权码不是登录密码填错就一直报认证失败。其次如果 465 端口不通可以尝试 587 tls 加密方式具体取决于你的邮箱服务商支持情况。最后邮件发送如果失败一定要先看 Laravel 日志错误信息里通常已经明确告诉你是认证问题、端口问题还是域名问题。全部配置完成后我执行了一次简单的测试注册一个测试账号发送一封欢迎邮件确认能正常收到。这一步通过后再正式开放给团队成员使用。4. 在线稳定性与数据安全的两道防线4.1 怎么保证 CRM 系统“永久在线”团队内部系统最忌讳用的时候打不开。我们团队每天上班第一件事就是打开 CRM 刷新一下当日待跟进列表如果这时候服务挂了整个销售节奏都会受影响。要让系统尽量保持可用需要从两个方面入手进程守护和资源预警。进程守护用 Linux 自带的 systemd 就能做。我把 Nginx、PHP-FPM、MySQL、Redis 都设置为开机自启并配置了自动重启策略。以 PHP-FPM 为例在/etc/systemd/system/multi-user.target.wants/php8.2-fpm.service中确认Restartalways即可。这样即使某个 PHP-FPM 进程因为资源问题崩溃systemd 会自动拉起新进程多数情况下用户无感知。资源预警我写了一个简单的 Shell 脚本每五分钟检查一次磁盘、内存和存活进程异常时通过邮件通知我#!/bin/bash # 检查磁盘可用空间小于 10% 时告警 disk_usage$(df / | awk NR2 {print $5} | sed s/%//) if [ $disk_usage -gt 90 ]; then echo 磁盘使用率超过 90% | mail -s DeskcommCRM 磁盘告警 adminexample.com fi # 检查 MySQL 进程是否存在 if ! pgrep mysqld /dev/null; then echo MySQL 已停止 | mail -s DeskcommCRM MySQL 告警 adminexample.com fi配合系统的 crontab 每五分钟执行一次基本能做到故障发生五分钟内就收到告警。真正的“永久在线”是没法保证的但把故障发现时间缩短到五分钟内是任何一个小团队都能做到的。4.2 数据备份与恢复演练CRM 里最重要的资产就是数据所以备份策略我宁可多花时间也绝不含糊。我采用的备份方案是每天凌晨执行一次 MySQL 全量导出保留最近 14 天的备份文件同时每天同步一份到另一台独立服务器做异地备份。备份脚本如下#!/bin/bash backup_dir/data/backups/mysql timestamp$(date %Y%m%d_%H%M%S) dump_file$backup_dir/deskcommcrm_$timestamp.sql.gz mkdir -p $backup_dir # 导出并压缩 mysqldump -u crm_user -p密码 --single-transaction --routines --triggers \ deskcommcrm | gzip $dump_file # 删除 14 天前的备份 find $backup_dir -name *.sql.gz -mtime 14 -delete # 同步到异地备份服务器 rsync -avz $backup_dir/ backup192.168.1.100:/data/backups/mysql/使用--single-transaction参数可以在不锁表的情况下进行一致性备份导出时销售同事照常使用系统不会因为备份导致卡顿。--routines --triggers保证存储过程和触发器也能一起导出避免恢复时数据完整性问题。备份脚本本身只是个基础真正重要的是恢复演练。我每个季度都会挑一次生产备份恢复到测试机确认数据能正常打开、页面能正常访问再把测试环境的几个关键数据核对一遍。没有经过恢复验证的备份等于没有备份。这个道理我以前也不太在乎直到一次真实故障中恢复数据时发现备份文件损坏才意识到只有定期演练才能确认备份链路是活着的。5. 日常运维中的常见问题与排查技巧5.1 502 Bad Gateway 与 PHP-FPM 运行异常系统上线后第一个月最常遇到的就是 502。现象是页面打开的时候提示 Nginx 502 Bad Gateway重试有时候能恢复有时候持续很久。排查 502 的核心是确认 PHP-FPM 是否在运行。先看进程状态systemctl status php8.2-fpm如果进程已经挂掉查看日志找原因tail -n 50 /var/log/php8.2-fpm.log我们之前遇到过的情况是 PHP-FPM 子进程频繁崩溃日志里大量报 “WARNING: [pool www] server reached pm.max_children”。说白了就是并发量超过子进程上限新请求来不及处理。解决方法是调大pm.max_children同时配合查看内存占用总内存 8G每个 PHP 进程按 128M 计算max_children 调大到 40 左右是有余量的但如果你服务器只有 2G 内存盲目调大会触发 OOM Killer 把进程全部杀掉反而更崩溃。遇到 502 不要一上来就重启先看一眼日志再决定是调参还是修代码这个习惯能帮你省下大量时间。5.2 MySQL 连接数打满导致系统假死还有一次系统整体假死页面一直转圈Nginx 的接入日志里大量connect() to unix:/var/run/php/php8.2-fpm.sock failed错误但 PHP-FPM 又没有挂。后来定位到是 MySQL 的连接数被打满了。原因是团队早上集中登录系统同时还有报表页面和定时任务全部在跑MySQL 默认的连接数只有 151直接被挤爆。处理方案分两层临时提升连接数上限 长期优化慢查询。临时解法SET GLOBAL max_connections 500;永久解法是修改 MySQL 配置文件把max_connections设置为 500同时把应用层一些不必要的长连接改为短连接。但连接数调大不是万能药它只是增加系统能同时处理的请求数真正的瓶颈往往是查询效率。通过慢查询日志找出频繁执行的 SQL加合适的索引减轻数据库压力。比如 follow_ups 表里使用频率最高的筛选条件是customer_id和user_id我给这两个字段建了联合索引查询速度从两百多毫秒降到了十毫秒以内。5.3 定时任务不执行或重复执行定时任务在系统里非常重要因为跟进提醒、日报周报生成、数据备份都依赖它。如果你发现每天早上应该生成的一条日报没出来首先检查 Cron 是否真的在跑grep CRON /var/log/syslog | tail -n 20看到类似CRON[12345]: (root) CMD (cd /var/www/deskcommcrm php artisan schedule:run...)的日志说明 Cron 触发是正常的问题大概率还是在应用内部。Laravel 的调度器依赖于php artisan schedule:run每分钟执行一次然后由里面注册的schedule代码决定某个具体任务是否到点。我遇到过一次重复执行的情况有两个执行schedule:run的 Cron 条目被重复添加导致同一个任务被同时触发两次。查了下系统 crontab发现是之前手动调试时加了一行后来忘了删。为了避免重复执行除了保证系统里只有一个 Cron 条目还可以给关键任务加withoutOverlapping()修饰这是 Laravel 自带的防冲突机制。比如备份任务执行时间可能超过一分钟如果不加withoutOverlapping()下一次调度还会再触发两个备份进程同时跑容易造成数据库压力过大。5.4 邮件通知收不到或发不出邮件系统是 CRM 里看起来不起眼但实际很重要的部分。我们一开始做邮件通知时用户反馈说收不到欢迎邮件。查看 Laravel 日志后发现 SMTP 认证一直失败。原因正是之前提到的企业邮箱的 SMTP 密码不是邮箱登录密码而是需要在邮箱设置中单独开启 SMTP 服务后生成的授权码。许多邮箱出于安全考虑默认关闭 IMAP/SMTP 服务需要手动开启并在开启后生成一个专用授权码。另外还有一个容易被忽略的问题即使 SMTP 发送成功也可能因为邮件服务商的 SPF 和 DKIM 记录未配置导致邮件被收件方判定为垃圾邮件。对于自建邮件发送服务的场景要去域名解析里添加对应记录。这个问题排查的思路是先看应用日志确认是否有报错再分几段验证先确认 SMTP 服务器能连通再确认认证信息正确最后确认收件方是否能收到且不进入垃圾箱。逐步缩小范围不要一上来就怀疑代码。5.5 Session 失效与登录态异常为了登录态稳定我把 SESSION_DRIVER 设置成了 Redis上线初期却不定期出现“用户登录状态丢失需要反复登录”的情况特别是早上刚上班那段时间最频繁。后来排查发现是因为 Redis 的内存达到 maxmemory 上限后默认开启的allkeys-lru回收策略会把一些还没过期的 Session 也清理掉。数据被回收登录态自然就丢了。解决方法有两个方向。一个是调大 Redis 的maxmemory服务器内存比较充裕的情况下直接设置成内存总量的四分之一。另一个更合理的办法是修改 Redis 的内存淘汰策略把maxmemory-policy从allkeys-lru改为volatile-lru这样 Redis 只会优先淘汰设置了过期时间的 key而 Session 又是设置了过期时间的所以正常机制下 Session 只在真正达到过期时间后才被淘汰不会因为内存压力被提前误杀。修改 Redis 配置后Session 丢失问题基本消失了。如果你还在用文件形式的 Session建议尽快迁移到 Redis这个改动对团队多人同时在线时的稳定性提升非常明显。6. 员工接入与后续扩展建议6.1 新成员从邀请到上手的一小时流程DeskcommCRM 的账号体系设计为管理员邀请制目的是保证每个账号都有明确的归属人。具体流程是管理员在后台的“团队管理”页面点击“邀请成员”输入新同事的邮箱地址系统自动生成一个一次性邀请链接发送到对方邮箱。新同事打开链接后只需要设置自己的登录密码然后确认加入团队系统就会自动给其分配一个基础“销售”角色。管理员可以在后台把该成员调整到对应的角色比如销售主管、客服专员、管理员等不同角色对应不同的数据权限和功能权限。这个流程看起来简单但在实际使用中能极大减少管理员的负担。之前用过一款 SaaS 产品邀请员工需要先去“用户管理”创建账号再手动分配角色还要给新同事讲解怎么改密码一套流程下来十几分钟。现在这边点几下就能完成。建议在正式开放给团队之前先自己完整走一遍邀请、加入、授权、登录的流程确保邮件发得出去、链接有效。如果新同事一直收不到邀请邮件先去检查刚才提到过的 SMTP 配置以及邀请链接是否因为安全策略被邮箱拦截。6.2 数据看板、API 和移动端适配的扩展方向第一版上线跑顺之后团队开始有更多需求最典型的是三个方向数据看板、API 集成和移动端适配。数据看板目前是用表格和简单图表来实现的每天自动生成前一天的客户新增数、跟进次数、商机金额合计、成交转化率。更进一步的做法是叠加一个可视化看板让销售主管打开首页就能看到团队实时业绩。可以用 Chart.js 或 ECharts 单独写一个看板页面也可以把统计数据通过 API 输出后用 BI 工具对接。API 集成方面Laravel 本身提供了很成熟的 API 资源路由和认证机制。我们目前开放了一部分内部 API 给财务系统用于同步已成交合同的信息省掉了重复录入的环节。如果后续要对接企业微信或钉钉也只需要在这些平台配置一个应用然后通过回调把消息推送到 CRM 系统即可。移动端适配是很多团队的刚需毕竟销售在外面跑客户的时候不可能随身背电脑。我现在的做法是直接把前端改成了响应式布局基本操作比如查客户、写跟进、看工单在手机浏览器上都能顺畅完成。如果想要更好的体验可以套一个轻量级的 PWA不一定要上原生 App。对一个小团队来说维护一个原生 App 的成本实在太高响应式网页已经能覆盖绝大部分使用场景。6.3 关于这套系统后续的维护建议如果你按照这套思路自己搭了一套我的建议是保持克制。不要频繁加功能尤其是当某个人提的需求只对他自己有用的时候先记下来观察有没有第二个人也提出同样的诉求再决定是否排期开发。很多内部系统最后项目烂尾就是因为被各种零碎需求拖垮了。同时要建立“需求变更要回归测试”的习惯。CRM 这种系统客户数据是核心一个字段的修改可能影响列表查询、统计报表、数据导出等多个环节。每一次改动都要顺手验证一遍核心流程不受影响否则小问题积压到月底爆出来就是大排查。最后再分享一个实用的运维建议给服务器配置好 swap 分区至少 2G。我遇到过内存吃紧导致应用卡顿的情况加了 swap 之后稳定性提升明显。虽然速度不如物理内存快但至少能防止进程在瞬间高负载时被直接 OOM 杀掉。加上之前说的备份脚本、告警脚本和定时任务验证一套基础牢固的内部 CRM 系统完全可以做到“用得放心、坏了能修、丢不了数据”。这就是我个人折腾 DeskcommCRM 的核心心得。
