简介:这是一份基于PHP开发的OA办公系统完整源码包,面向需要快速搭建内部办公自动化系统的中小企业技术运维人员,也适合具备一定PHP基础的开发者学习参考。系统支持PHP5.2/5.3/5.4与MySQL数据库组合,内置安装引导、菜单权限管理、数据备份与还原等常用模块,整体架构清晰,方便二次定制。压缩包共36.7MB,其中包含可运行的PHP源码与演示数据库,还附有从解压上传、填写数据库信息到恢复demo1481562750.sql备份节点的一整套说明,能够帮助使用者避开环境校验和部署中常见的坑。目前已有313人学习下载。对想动手实践传统PHP开发模式或搭建一套轻量级OA系统的人来说,这份源码加数据库的组合包是比较合适的参考素材。
1. 基于PHP的OA办公系统:为什么源码部署比从零开发更现实
拿到一个「基于PHP的OA办公系统(源码+数据库).zip」,绝大多数人第一反应是「解压、导入、能打开就行」。但我建议你先想清楚一个问题:这套源码是给你用来跑业务,还是给你用来学架构?两者的阅读重点完全不同。PHP的OA办公系统在中小企业里依然是最常见的内部信息化形态,一套源码里通常浓缩了权限模型、审批流、考勤计算、附件管理这几大块硬骨头,而这些恰恰是从零开发时最容易翻车的地方。所以我一般会建议:先把它当教材跑通,再把它当工程改造成自己能维护的版本。这篇笔记就按「看清构成 → 本地部署 → 拆数据库设计 → 避坑 → 进阶改造」的顺序来拆解。
2. 读懂这套OA源码:模块构成与核心技术栈
2.1 OA系统里必定出现的几个核心模块
不管压缩包来自哪个项目,一套完整的OA系统在目录结构上基本都能找到这几个模块:用户与组织架构管理、权限控制(RBAC)、审批流、考勤、公告通知、文件/附件管理。其中审批流是整个系统里最容易写乱的部分,因为它涉及状态机的流转,而且每个公司审批链条还不一样;其次就是考勤,如果源码里带了排班和补卡逻辑,那这个活儿已经不算简单了。
看源码时先别急着打开某个Controller文件,第一步是扫目录,把模块划分摸清楚再往下钻。常见的结构是把业务逻辑放在/application或/app下,按admin、api、common分包;有的老项目会把配置和模块混在一起,看起来就特别像黑匣子。这个时候切记别做「源码考古学家」,从入口文件index.php和路由配置开始追,效率最高。
2.2 框架选型:原生PHP还是ThinkPHP这类后端框架
大部分PHP OA系统会选择ThinkPHP、Laravel这类成熟后端框架,因为这能显著降低做权限和数据字典的重复劳动;也有少量项目用原生PHP写,优点是部署时不需要Composer那一套依赖,缺点是代码组织方式基本靠约定,不同作者写出来的风格天差地别,维护成本高。
判断框架的办法很简单:看目录里有没有think文件或artisan文件。有think大概率是ThinkPHP;有artisan就是Laravel;两者都没有、且入口直接引入一堆require_once的那就是原生PHP项目。ThinkPHP在OA领域里出镜率极高,因为它的快速开发模式很适合MIS类系统,社区里大量OA源码都是基于ThinkPHP 5/6写的。你在这套源码里如果看到Controller、Model分层但没引入命名空间,多半是TP5的老写法。
2.3 数据库脚本:MySQL是绝对主力
压缩包里的数据库文件不外乎两种形态:一个完整的.sql文件,或是放在sql目录下的分模块脚本。OA系统用MySQL是压倒性多数,因为你几乎找不到一个会用PostgreSQL或SQLite来做OA的中小企业项目——这不是技术问题,而是运维习惯问题,MySQL的导出导入、主从同步、日常备份工具链最成熟。
拿到.sql文件后,强烈建议先用文本编辑器打开看头部注释,确认编码是utf8mb4还是utf8。如果是utf8,后患是表情符号存不进去,系统里只要有人往备注里粘贴一个Emoji,整条记录就写入失败。这一步只要花两分钟,能省掉之后好几周的玄学排障。
3. 真正的部署流程:从压缩包到浏览器能打开
3.1 本地环境准备:LAMP还是LNMP
无论你用的是Windows、macOS还是Linux,只要目标是「先让源码跑起来」,统一建议用PHPStudy或Docker这类集成环境。PHPStudy适合不想折腾的读者,Docker适合想尽可能贴近生产环境的情况。这里给出的是基于Linux的LNMP环境命令,是常规做法:
# 安装 Nginx、PHP、MySQL(以Ubuntu 22.04为例) sudo apt update sudo apt install -y nginx php8.1-fpm php8.1-mysql mysql-server # 安装常用PHP扩展 sudo apt install -y php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-zip参数说明:php8.1-mysql是新版PHP连接MySQL的驱动扩展,没有它PDO和mysqli都连不上库;mbstring负责多字节字符串处理,OA系统里中文拼音排序、截断都依赖它;gd用于验证码图片生成,没有它登录页的验证码就会报500。
装完之后启动服务,然后把压缩包解压到Web根目录,比如/var/www/html/oa。这里有一个最常见的权限坑:Nginx的运行用户是www-data,如果源码目录是root用户解压出来的,PHP就没有权限读写Runtime、Uploads这类目录。
# 授予Web运行用户对源码目录的读写权限 sudo chown -R www-data:www-data /var/www/html/oa sudo chmod -R 755 /var/www/html/oa # 如果业务需要上传附件,Runtime和Uploads目录要放开到755chown这一步很多人会漏掉,导致页面能打开但一登录就报「目录不可写」。PHP项目不像Java项目那样频繁需要重启,改完配置立即生效,这让调试很舒服;但权限问题也因此更隐蔽——代码没问题、PHP没问题,纯粹是文件系统拦住了写入,日志里还不一定报得清楚。
3.2 导入数据库:别用Navicat拖文件
数据库导入看起来简单,但操作姿势不对会留下编码后患。常见做法有两种:通过mysql命令行导入,或通过phpMyAdmin导入。推荐命令行方式,因为你不需要在浏览器里等待上传大SQL文件然后超时。
# 先创建数据库,注意字符集要跟SQL文件头里的一致 CREATE DATABASE oa_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 在shell中导入SQL文件 mysql -u root -p oa_system < /path/to/oa_system.sql参数说明:DEFAULT CHARACTER SET utf8mb4指定库的默认字符集,这一步决定了以后所有新建表是否默认支持Emoji和生僻字;COLLATE utf8mb4_general_ci是排序规则,对中文拼音排序的支持比较好。如果SQL文件本身是utf8,而库是utf8mb4,文件里的CREATE TABLE语句一般会自带字符集,冲突不大;怕的是SQL文件头部写的是latin1,这种老古董在导入时建议先用编辑器批量替换后再导入。
导入完成后,务必检查一个细节:查一下有没有内置管理员账号。OA系统的初始管理员密码通常是一串MD5哈希,比如e10adc3949ba59abbe56e057f20f883e,这是123456的MD5值,也是最常见的默认密码。你可以在SQL文件里搜admin关键词来确认,改掉这个默认密码应该是部署之后做的第一件事。
3.3 配置文件改动:数据库连接、URL、会话
PHP框架的配置一般在/config/database.php或.env文件中。数据库连接信息是核心,但把数据库连通之后还有两个隐藏配置会影响后续使用:URL模式和会话配置。
以ThinkPHP为例,config/database.php里需要修改的基本是hostname、database、username、password这四项。但还要留意config/app.php中的url_route_on(是否开启路由)和auto_build_module(是否自动构建模块),这两项决定了URL是index.php?s=/admin/login这种兼容格式,还是/admin/login这种友好路由格式。
// config/database.php —— ThinkPHP 6风格的数据库配置 return [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'oa_system', 'username' => 'oa_user', 'password' => '你的强密码', 'hostport' => '3306', 'charset' => 'utf8mb4', 'prefix' => 'oa_', 'debug' => true, ];参数说明:prefix是数据表前缀,OA系统里几乎必带前缀,比如oa_user、oa_dept,它能让一个数据库里同时跑多套系统而不互相干扰;debug在生产环境必须设为false,否则错误信息会直接打印到页面上,把SQL语句和文件路径全暴露出去,这是安全事故的高发点。实际部署时别直接使用root账号连数据库,创建一个专用账号并按需授权即可。
4. 数据库与权限设计:OA系统里的核心表架构
4.1 读懂用户表和部门表的设计思路
OA系统数据库里最常见的表结构是围绕「用户—部门—角色—权限」这四个维度展开的。用户表不会只存账号密码,还会冗余存部门ID、职位ID、直属上级ID,这样查询时才能大幅减少JOIN次数,让页面响应更快。举一个典型的用户表结构:
CREATE TABLE `oa_user` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `dept_id` int(11) DEFAULT NULL COMMENT '所属部门ID', `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(255) NOT NULL COMMENT 'MD5或bcrypt加密后的密码', `realname` varchar(50) DEFAULT NULL COMMENT '真实姓名', `email` varchar(100) DEFAULT NULL, `mobile` varchar(20) DEFAULT NULL, `status` tinyint(1) DEFAULT '1' COMMENT '1启用 0禁用', `last_login_time` datetime DEFAULT NULL, PRIMARY KEY (`user_id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';这个设计有一个值得学习的点:dept_id是冗余字段,本来可以通过关联表查出来,但OA系统里用户归属部门是高频查询,冗余以后可以减少一次关联查询,这在用户量大时差别很明显。password字段用varchar(255)而不是varchar(32),说明这套系统的密码存储方式可能不止MD5,也可能是bcrypt或password_hash生成的长字符串,这在设计上是更稳妥的。
4.2 权限模型的落法:RBAC三件套
OA系统的权限控制几乎都是RBAC(基于角色的访问控制)的变体。核心是三张表:角色表、权限表、用户角色关联表。权限表里往往是一串菜单URL或按钮标识,角色表把这些URL聚合起来,用户再关联角色。
CREATE TABLE `oa_role` ( `role_id` int(11) NOT NULL AUTO_INCREMENT, `role_name` varchar(50) NOT NULL, `remark` varchar(255) DEFAULT NULL, `status` tinyint(1) DEFAULT '1', PRIMARY KEY (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `oa_role_user` ( `role_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`role_id`, `user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `oa_menu` ( `menu_id` int(11) NOT NULL AUTO_INCREMENT, `parent_id` int(11) DEFAULT '0', `menu_name` varchar(50) NOT NULL, `url` varchar(255) DEFAULT NULL, `perms` varchar(100) DEFAULT NULL COMMENT '权限标识,如 oa:approve:view', `sort` int(11) DEFAULT '0', PRIMARY KEY (`menu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这套设计的核心是「用户不直接绑权限,而是绑角色」,当公司入职一百个新员工时,只需要批量关联角色,不需要逐条给他们配菜单。perms字段是权限标识字符串,在代码里通过判断当前用户是否拥有oa:approve:view这个标识来决定能不能看审批列表。这是比单纯控制菜单显示更细粒度的方案,按钮级权限靠这个字段实现。看源码时顺藤摸瓜搜perms就能定位到权限校验的核心逻辑。
4.3 审批流的状态机设计
审批流是一套OA系统的灵魂,它的核心不是业务代码,而是数据库里的状态设计。经典的审批流会有一张oa_leave(请假表)和一张oa_approve_log(审批记录表)。请假表里存status字段,通常0是待审批、1是同意、2是驳回、3是撤销;审批记录表里每一条都记录是哪个节点、谁审批的、结果是什么、备注写了什么。
CREATE TABLE `oa_approve_log` ( `log_id` int(11) NOT NULL AUTO_INCREMENT, `biz_type` varchar(30) NOT NULL COMMENT '业务类型,如 leave', `biz_id` int(11) NOT NULL COMMENT '业务单据ID', `node` varchar(30) DEFAULT NULL COMMENT '审批节点', `approver_id` int(11) NOT NULL, `action` tinyint(1) DEFAULT '1' COMMENT '1同意 2驳回', `remark` varchar(500) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`log_id`), KEY `idx_biz` (`biz_type`, `biz_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这套设计的巧妙之处在于它用biz_type+biz_id的组合来抽象不同类型的审批单,请假走报销不需要另外建一套审批记录表。看源码重点看这个表的查询逻辑——靠谱的实现一定会按biz_type和biz_id建联合索引,否则单据多了以后审批记录查询会越来越慢。这也是「源码+数据库」这个组合最值得学习的地方:好系统不是功能多,而是表结构经得起数据量增长。
5. 避坑指南:照着源码部署为什么还会翻车
5.1 PHP版本导致函数直接消失
现象:页面打开后白屏,打开PHP错误日志发现Call to undefined function mysql_connect()。
原因:老OA源码基于PHP 5.x编写,用的是mysql_*系列函数,这套函数在PHP 7.0里已经被彻底移除,换成mysqli_或PDO了。不是代码逻辑错了,是运行环境根本不认识这些函数。
解决:先确认代码里用的是mysql_connect还是mysqli_connect。如果是前者且代码量不大,可以用一个兼容层把所有mysql_前缀函数映射到mysqli_上;如果代码量大,建议直接安装PHP 5.6来跑这套老系统,但PHP 5.6有安全风险,只适合本地学习,绝对不能上生产外网。
5.2 时区没配对导致审批时间错乱
现象:系统提交审批单后,显示的时间比实际时间晚了8小时,考勤打卡的记录也全部偏移。
原因:PHP 默认时区是UTC,中国用户在东八区,日期函数输出的时间就会少8小时,保存进数据库的时间自然也是错的。这不是数据库的问题,是PHP进程的时区配置问题。
解决:在php.ini里设置date.timezone = Asia/Shanghai,或者在项目入口文件顶部加一行date_default_timezone_set('Asia/Shanghai')。改完重启PHP-FPM立即生效。
5.3 上传中文文件名导致附件打不开
现象:上传附件时提示成功,但下载时文件名乱码,或者文件压根打不开。
原因:部分老代码在保存附件时直接用了原始文件名,没有做随机重命名,导致中文文件名在传输过程中被浏览器或服务器编码转换搞坏。Nginx默认会对URL中的非ASCII字符做编码,和存储时的文件名一对比就找不到文件了。
解决:看源码里附件上传部分,确认是否使用了md5(uniqid())这类方式重命名保存文件,数据库里只存一个新的文件名。如果源码没做这一步,需要在Uploads目录的下载入口加一层文件映射,保证磁盘文件名是纯ASCII、展示名称是中文。这是OA系统里非常经典的历史包袱。
5.4 定时任务不执行:考勤统计一直是空的
现象:系统的考勤日报、周报数据一直是空的,但手动点刷新又能出来。
原因:OA系统的定时任务通常依赖crontab,如果代码里写了Cron入口但没有在服务器上配置crontab计划,PHP脚本永远不会被触发。很多Windows本地开发环境压根没有计划任务概念,开发时根本测不出来。
解决:找到项目里的cron.php或command目录,在Linux服务器上添加:
crontab -e # 每5分钟执行一次考勤统计任务 */5 * * * * /usr/bin/php /var/www/html/oa/cron.php > /dev/null 2>&1>/dev/null 2>&1的意思是丢弃正常输出并把错误也丢进去,避免产生大量垃圾日志文件;如果任务执行失败想看原因,把这段去掉让错误输出到文件里再排错。
5.5 连接池没做导致数据库连接数暴涨
现象:OA系统刚开始跑很正常,上线一周后MySQL报Too many connections,重启MySQL后过一天又会复现。
原因:PHP每处理一个请求就会新建一个数据库连接,请求结束就释放。如果线上并发量超过MySQL默认的max_connections,连接数就会打满。OA系统的用户量大但请求密集度低,理论上不太容易打满,但如果某些页面有慢查询,每个连接被占用的时间就变长,连接池效应明显。
解决:短中期方案是调大MySQL的max_connections,比如从151调到500,同时开启慢查询日志找到耗时超过1秒的SQL进行优化。更根本的方案是引入持久化连接,比如使用PDO的ATTR_PERSISTENT或引入代理层做连接池复用。值得注意的是PHP和Java不一样,PHP进程是短命的,持久连接在PHP-FPM模式下才有实际意义,配错了反而会出问题。
6. 进阶落地:把OA源码改造成能上生产的系统
把源码跑通只是第一步,真正有价值的在于把它往生产环境推。这里分享三个改造方向,投入产出比最高。
第一,给数据库加一层查询缓存。OA系统里大量请求集中在部门树、用户列表、字典数据这些不频繁变化的查询上,没必要每次都打MySQL。常见的做法是把这些数据缓存到Redis里,并设置60秒过期时间。改造工作很集中:找到Model层的查询方法,在查数据库前先查Redis,没命中再查库并把结果写回缓存。
// 用Redis缓存部门树列表 public function getDeptTree() { $cache = \think\facade\Cache::get('dept_tree'); if ($cache) { return $cache; } $deptTree = Db::name('dept')->select()->toArray(); \think\facade\Cache::set('dept_tree', $deptTree, 60); return $deptTree; }这段代码的逻辑说明:先看缓存里有dept_tree就拿缓存里的数据返回,没有就去数据库查,再把结果塞进Redis缓存60秒。60秒的TTL设置是中庸之选——不会因为太长导致人员调动后一整天看不到新数据,也不会因为太短导致缓存形同虚设。
第二,对接企业微信/钉钉的免登能力。OA系统的登录体验是个高频痛点,内网部署的系统每次都要手输账号密码,这对用户来说很烦。靠谱的做法是OAuth免登:企业微信前端拿到code,后端拿code去换身份,找到对应手机号再自动绑定本系统的用户会话。改造重点在登录控制器,逻辑其实只有三步:接收code、调用企业微信接口换用户信息、查询本系统用户表建立会话。
第三,数据备份方案。OA系统的数据价值比系统本身还值钱,部署时就要把备份脚本放到位。简单的做法是每天凌晨用mysqldump全量导出核心库,保留最近7天备份;有余力的再对上传目录做同步备份。
0 2 * * * mysqldump -u oa_user -p'密码' oa_system | gzip > /backup/oa_$(date +\%Y\%m\%d).sql.gz find /backup -name 'oa_*.sql.gz' -mtime +7 -delete备份和清理写在一条crontab里,是个非常偷懒但有效的做法:每天凌晨2点导出数据库并压缩,然后删除7天前的备份,保证磁盘不会被无限占满。
回到开头那个问题:这套源码值不值得用?如果是为了学习PHP企业级应用的组织方式,或者为了快速给公司搭一套内部系统,答案是值得的;如果是为了支撑几十万人的复杂审批流,PHP本身不是瓶颈,瓶颈在数据库表结构设计上,这时候你需要的就不是源码了,而是先把数据模型重构一遍。我自己的习惯是拿到任何一套OA源码,第一周只做三件事:跑通、读懂数据字典、画出审批流的状态图,做完这三件事再决定改还是重写。这套方法论比源码本身更有用——毕竟源码是别人写好的答案,而你要面对的是自己公司那道没写答案的题。希望帮到你。
本文还有配套的精品资源,点击获取