ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PHP CRM源码二次开发实战:办公协同模块打通与避坑指南

PHP CRM源码二次开发实战:办公协同模块打通与避坑指南 简介这是一套面向中小企业与开发者的PHP客户关系CRM管理系统源码主打办公协同场景适合需要快速搭建客户管理平台的团队或个人二次开发。系统围绕公海管理、线索管理、客户管理、业绩订单、系统设置与权限管理六大模块展开覆盖客户公海分类、销售线索跟踪、成交客户维护、订单记录及管理员权限分配等完整业务链路模块化设计便于按需定制与扩展。资源包共2000个文件约30.86MB以397个PHP业务文件为核心搭配245个JS、192个HTML与85个CSS构建前后端界面另有80个xlsx、35个xls表格模板及sql、config等配置与数据文件结构清晰。目前已有144人学习下载可帮助读者直接获得一套可运行的CRM系统骨架用于研究客户关系管理流程、权限控制与办公协同实现也可作为企业信息化项目的起步基础。1. 一套 PHP CRM 源码拿到手先别急着改代码很多做企业信息化的朋友拿到一套 PHP 客户关系 CRM 管理系统源码第一反应是打开编辑器改界面、换 Logo、加字段。我见过太多团队这么干结果两周后系统跑不起来客户数据对不上办公协同模块和 CRM 主流程打架。问题不在代码质量在于没搞清楚这套系统到底由哪几块拼起来、数据怎么流、哪些地方动不得。这套「PHP 客户关系 CRM 管理系统源码 办公协同」的组合本质是一个用 PHP 写的企业级业务中台雏形客户档案、商机跟进、合同回款是 CRM 主线审批流、任务分派、公告日程是办公协同支线两条线共用一套用户体系和权限模型。它适合中小团队做二次开发也适合个人开发者拿来练手企业级项目的架构思路。但前提是你得先把它跑起来、看明白再决定改哪里。下面按我实际部署和改造的经验从环境搭建到协同模块打通一步步说清楚。2. 把源码跑起来环境、数据库与第一个登录账号2.1 PHP 版本与扩展的硬性门槛拿到源码先看根目录有没有composer.json和.env.example。常见的企业 CRM 源码基于 ThinkPHP 6 或 Laravel 8/9这两个框架对 PHP 版本要求不同。ThinkPHP 6 最低 PHP 7.2.5Laravel 9 要求 PHP 8.0 以上。我一般先用php -v确认版本再对照框架文档。扩展方面除了常规的pdo_mysql、mbstring、json、opensslCRM 系统通常还需要bcmath金额计算、gd或imagick头像和合同附件缩略图、fileinfo上传文件类型检测。办公协同模块如果带消息推送可能依赖swoole或redis扩展做队列。# 检查 PHP 版本和关键扩展 php -v php -m | grep -E pdo_mysql|mbstring|bcmath|gd|fileinfo|redis # 如果缺少扩展以 Ubuntu 为例安装 sudo apt install php8.1-bcmath php8.1-gd php8.1-redis php8.1-mbstring逻辑说明先确认版本再补扩展。参数上注意bcmath的精度设置CRM 里合同金额和回款计划涉及浮点运算用bcadd、bcmul替代原生加减乘除能避免 0.10.2 这类精度问题。gd扩展在生成客户头像缩略图时会用到如果源码里用了Intervention Image库没有gd会直接报 500。2.2 数据库导入与配置文件修改源码包里的sql目录通常有一个.sql文件用 Navicat 或命令行导入。导入前先建库字符集选utf8mb4排序规则utf8mb4_unicode_ci否则客户姓名里的生僻字和 emoji 会变问号。# 创建数据库并导入 mysql -u root -p -e CREATE DATABASE crm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -u root -p crm_db /path/to/crm_db.sql # 复制环境配置文件 cp .env.example .env然后编辑.envAPP_DEBUG true DB_TYPE mysql DB_HOST 127.0.0.1 DB_NAME crm_db DB_USER root DB_PASS your_password DB_PORT 3306 DB_CHARSET utf8mb4逻辑说明APP_DEBUG在部署阶段设为true方便看报错上线后必须改false否则泄露路径和 SQL。数据库密码不要用root空密码很多源码默认配置就是空密码这是血泪教训——我见过服务器被扫到直接拖库的。参数上DB_CHARSET必须和建库时一致。如果导入后中文乱码检查三个地方数据库字符集、表字符集、连接字符集。连接字符集在.env里控制表字符集在 SQL 文件里定义数据库字符集在建库语句里。2.3 初始账号与权限体系验证导入 SQL 后admin表或user表里通常有一条默认管理员记录密码是 MD5 或 bcrypt 加密。常见默认账号是admin/123456但不同源码不一样。如果登录不了直接看 SQL 文件里INSERT INTO admin那条语句把密码字段的值复制出来用在线 MD5 反查或者直接替换成已知密码的哈希。// 生成 bcrypt 密码哈希用于替换数据库里的密码字段 ?php $password new_password_here; $hash password_hash($password, PASSWORD_BCRYPT); echo $hash;逻辑说明bcrypt 每次生成的哈希不同但都能验证通过。把输出的哈希值更新到admin表的password字段就能用新密码登录。参数上注意PASSWORD_BCRYPT的 cost 默认是 10如果服务器性能差可以降到 8但安全性会下降。登录后第一件事不是改界面是进「系统设置 → 权限管理」看角色和节点权限怎么配的。办公协同模块的审批流、任务分派都依赖这套权限。如果权限节点没配全普通员工登录后看不到协同菜单会以为是 bug其实是权限没勾。3. 客户数据模型与办公协同的打通点3.1 客户表、联系人表、商机表的关联逻辑一套 CRM 的核心数据模型就三张表crm_customer客户、crm_contact联系人、crm_business商机。客户和联系人是 1:N客户和商机也是 1:N联系人和商机通过contact_id关联表示这个商机是谁在跟。-- 查看客户表结构 DESCRIBE crm_customer; -- 查一个客户下的所有联系人和商机 SELECT c.id AS customer_id, c.name AS customer_name, ct.name AS contact_name, ct.phone, b.title AS business_title, b.amount, b.status FROM crm_customer c LEFT JOIN crm_contact ct ON ct.customer_id c.id LEFT JOIN crm_business b ON b.customer_id c.id WHERE c.id 1;逻辑说明LEFT JOIN保证即使客户没有联系人或商机也能查出来。参数上注意crm_business的status字段常见值是1跟进中、2已成交、3已流失不同源码可能用枚举或数字改代码前先看字段注释。办公协同的打通点在这里当商机状态变为「已成交」系统应该自动触发一条审批流通知财务和法务。这个触发逻辑通常在crm_business的模型事件里或者用数据库触发器。我一般不改数据库触发器而是在模型afterUpdate钩子里写业务逻辑方便调试和回滚。3.2 审批流与任务分派的实现方式办公协同模块的审批流常见实现有两种一种是基于workflow表的节点流转一种是硬编码在控制器里的if-else。前者灵活但配置复杂后者简单但改流程要动代码。// 在商机成交后创建审批任务 // 文件路径app/crm/model/Business.php protected static function boot() { parent::boot(); static::updated(function ($business) { if ($business-status 2 $business-getOriginal(status) ! 2) { // 创建审批记录 $approval new \app\oa\model\Approval(); $approval-title 商机成交审批 . $business-title; $approval-type business_deal; $approval-related_id $business-id; $approval-status 0; // 待审批 $approval-save(); // 分派给财务角色 $financeUsers \app\admin\model\User::where(role_id, 3)-column(id); foreach ($financeUsers as $uid) { \app\oa\model\Task::create([ title 审批商机成交 . $business-title, user_id $uid, approval_id $approval-id, status 0, ]); } } }); }逻辑说明getOriginal(status)拿到更新前的值避免重复触发。参数上role_id 3是财务角色这个值因系统而异改之前先在admin_role表里确认。任务分派用foreach给每个财务都建一条如果财务人多建议改成队列异步处理否则保存商机时会卡。办公协同的另一个打通点是日程和客户跟进。销售在客户详情页添加跟进记录时系统应该同步在协同模块生成一条日程提醒。这个逻辑我一般放在控制器的save方法里用事务包住保证跟进记录和日程要么都成功要么都失败。3.3 数据权限让销售只看自己的客户企业 CRM 最容易被忽略的是数据权限。默认情况下销售 A 能看到销售 B 的客户这是灾难。数据权限的实现通常在查询构造器里加where条件。// 在客户列表查询中加数据权限过滤 // 文件路径app/crm/controller/Customer.php public function index() { $user session(admin_user); $query \app\crm\model\Customer::where(status, 1); // 非管理员只看自己负责的客户 if ($user[role_id] ! 1) { $query-where(function ($q) use ($user) { $q-where(owner_id, $user[id]) -whereOr(share_ids, like, %, . $user[id] . ,%); }); } $list $query-paginate(20); return view(customer/index, [list $list]); }逻辑说明owner_id是客户负责人share_ids是共享给其他人的 ID 列表用逗号分隔。参数上paginate(20)每页 20 条根据服务器性能调整。注意whereOr在 ThinkPHP 里的用法如果写成where(owner_id, $user[id])-whereOr(...)不加闭包会破坏前面的status 1条件导致已删除的客户也查出来。办公协同模块的数据权限同理任务和审批也要按角色过滤。我见过一个系统销售能看到全公司的审批记录包括财务的付款审批这是权限设计翻车。改的时候在oa_task和oa_approval的查询里加同样的角色判断。4. 二次开发避坑改界面、加字段、接支付4.1 改界面别动核心模板很多源码用 ThinkPHP 的模板引擎视图文件在view目录。改界面时只改view下的 HTML 和 CSS别动public/static里的 JS 核心文件。我见过有人为了改一个按钮颜色直接改app.js结果所有页面的弹窗都失效。!-- 正确做法在视图里覆盖样式 -- {extend namebase /} {block namestyle} style .customer-list .btn-primary { background-color: #2d8cf0; border-color: #2d8cf0; } /style {/block}逻辑说明用{block namestyle}在页面级加样式不影响全局。参数上颜色值用十六进制别用red这种关键字方便后续统一替换。4.2 加字段要同步改模型、视图和验证器给客户表加一个「行业」字段需要改四个地方数据库加列、模型加$field或$schema、视图加输入框、验证器加规则。少一个地方就出问题。ALTER TABLE crm_customer ADD COLUMN industry VARCHAR(50) DEFAULT COMMENT 所属行业 AFTER name;// 模型里允许写入 protected $field [name, industry, phone, owner_id, ...]; // 验证器里加规则 protected $rule [ name require|max:100, industry max:50, ];逻辑说明AFTER name把新字段放在 name 后面方便查看。参数上VARCHAR(50)够用行业名称不会太长。如果源码用了$schema严格模式还要在$schema里加字段定义否则写入被过滤。4.3 接支付和短信的注意事项CRM 里常见的支付场景是合同回款短信场景是跟进提醒。接支付时回调地址必须用公网可访问的域名本地localhost收不到异步通知。短信接口注意频率限制别在循环里发用队列。// 回款支付回调处理 public function notify() { $data file_get_contents(php://input); $json json_decode($data, true); // 验证签名 if (!$this-verifySign($json)) { return fail; } // 更新回款记录 $payment \app\crm\model\Payment::where(order_no, $json[out_trade_no])-find(); if ($payment $payment-status 0) { $payment-status 1; $payment-pay_time time(); $payment-save(); } return success; }逻辑说明file_get_contents(php://input)拿原始 POST 数据比$_POST可靠。参数上verifySign是自定义的签名验证方法不同支付平台算法不同别跳过这步否则会被伪造回调。返回success告诉支付平台别再重试返回fail会触发重试机制。5. 常见问题排查登录失败、数据错乱、协同不触发5.1 登录后跳回登录页现象输入账号密码提示登录成功但页面跳转后又回到登录页。原因session 没保存住。常见是runtime目录没写权限或者.env里SESSION_DRIVER配成了file但目录不存在。解决chmod -R 777 runtime检查config/session.php里的path配置。如果用了 Redis 存 session确认 Redis 服务在跑SESSION_DRIVERredis时.env里要有REDIS_HOST和REDIS_PORT。5.2 客户列表数据重复现象客户列表里同一个客户出现两次。原因查询用了JOIN但没去重或者crm_customer表和crm_contact表关联时一个客户有多个联系人导致行数翻倍。解决用group(crm_customer.id)去重或者改用LEFT JOIN子查询只取一个联系人。参数上group在 MySQL 5.7 以上要注意ONLY_FULL_GROUP_BY模式如果报错把group里的字段都写全。5.3 办公协同任务不触发现象商机成交后审批任务没生成。原因模型事件没生效或者status字段的类型不对。比如数据库里status是varchar代码里用 2比较2 2在 PHP 8 里是false。解决用严格比较或者把数据库字段改成tinyint。检查boot方法有没有被父类覆盖ThinkPHP 模型事件要在boot里注册别写在__construct里。5.4 上传附件失败现象上传合同附件提示「文件类型不允许」。原因fileinfo扩展没装或者config/filesystem.php里的mimetype白名单太严。解决装fileinfo扩展在白名单里加application/pdf、image/jpeg、image/png。参数上注意upload_max_filesize和post_max_size的 PHP 配置默认 2M 和 8M合同扫描件经常超。5.5 权限改了不生效现象给角色加了菜单权限用户重新登录还是看不到。原因权限缓存没清。很多 CRM 把权限节点缓存在runtime/cache或 Redis 里。解决后台点「清除缓存」或者手动删runtime/cache目录。如果用了 Redisredis-cli flushdb清当前库。参数上注意别flushall会把其他库的数据也清掉。6. 用队列和定时任务把协同模块跑顺办公协同模块最影响体验的是「慢」和「漏」。销售保存跟进记录要等 3 秒因为同步发了短信和邮件审批任务漏触发因为模型事件在批量更新时不生效。这两个问题我用队列和定时任务解决。先说队列。把发短信、发邮件、生成日程这些耗时操作丢到 Redis 队列控制器里只写Queue::push()响应时间从 3 秒降到 200 毫秒。ThinkPHP 用think-queueLaravel 用内置队列。配置如下# .env 队列配置 QUEUE_CONNECTION redis REDIS_HOST 127.0.0.1 REDIS_PORT 6379 REDIS_PASSWORD # 启动队列监听 php think queue:listen --queue default --tries 3参数上--tries 3表示失败重试 3 次超过就进failed_jobs表。注意队列监听进程要用supervisor守护否则终端一关就停。我一般配两个进程一个处理默认队列一个处理sms和email高优先级队列。再说定时任务。商机成交后如果模型事件没触发用定时任务兜底每分钟扫一次crm_business表把status 2且approval_created 0的记录找出来补建审批任务。// 定时任务补建审批任务 // 文件路径app/command/CheckBusiness.php public function handle() { $list \app\crm\model\Business::where(status, 2) -where(approval_created, 0) -limit(100) -select(); foreach ($list as $business) { // 创建审批和任务逻辑同模型事件 // ... $business-approval_created 1; $business-save(); } }逻辑说明limit(100)防止一次处理太多卡死approval_created字段是新增的标记位避免重复创建。参数上定时任务频率别太高1 分钟一次够用太频繁会增加数据库压力。最后说一个验证方法改完队列和定时任务后别只看日志实际走一遍流程。新建一个客户加一个商机把状态改成「已成交」然后看oa_approval表里有没有记录oa_task表里有没有任务短信有没有收到。我习惯在测试环境用redis-cli monitor看队列有没有推入用tail -f runtime/log/queue.log看消费有没有报错。这套 PHP CRM 源码的改造我最大的教训是别一上来就改代码先跑通、看数据流、配权限再动刀。办公协同和 CRM 的打通点就那么几个抓住商机成交、跟进记录、审批任务这三条线剩下的都是体力活。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进