
简介这是一份基于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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE oa_role_user ( role_id int(11) NOT NULL, user_id int(11) NOT NULL, PRIMARY KEY (role_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这套设计的核心是「用户不直接绑权限而是绑角色」当公司入职一百个新员工时只需要批量关联角色不需要逐条给他们配菜单。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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这套设计的巧妙之处在于它用biz_typebiz_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 21/dev/null 21的意思是丢弃正常输出并把错误也丢进去避免产生大量垃圾日志文件如果任务执行失败想看原因把这段去掉让错误输出到文件里再排错。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源码第一周只做三件事跑通、读懂数据字典、画出审批流的状态图做完这三件事再决定改还是重写。这套方法论比源码本身更有用——毕竟源码是别人写好的答案而你要面对的是自己公司那道没写答案的题。希望帮到你。本文还有配套的精品资源点击获取