☰
勾股OA:ThinkPHP6+Layui+MySql开源办公系统部署与二次开发
2026/10/22 4:58:08 网站建设 项目流程

简介:基于ThinkPHP6、Layui与MySQL构建的开源企业办公系统勾股OA,产品定位清晰:面向中小企业提供零门槛、可落地的办公自动化方案,也便于开发者基于该框架二次构建CRM、ERP或业务管理系统。系统原生集成系统设置、人事管理、消息管理、审批管理、日常办公、客户管理、合同管理、项目管理、财务管理等模块,覆盖企业日常运转核心环节。压缩包共997个文件,其中包含329个HTML页面、275个JavaScript交互脚本、191个PHP业务逻辑文件、84个CSS样式表,并附有SQL数据库初始化脚本、配置与少量图片资源;包体约10.3MB,目录按模块分区,便于按需定位与二次维护。当前已有319人浏览学习,适合需要快速建立企业级办公系统,或希望基于成熟模板进行业务深度定制的技术团队与个人开发者。整个系统采用模块化设计,后台与接口分层清晰,直接部署后即可体验完整流程,开发者可依据实际业务替换页面、扩展模型或增加接口,从而快速实现企业定制化需求。

1. 勾股OA是什么:把ThinkPHP6+Layui+MySql变成一套能直接跑的企业办公系统

朋友公司要上OA,第一反应是买SaaS,算完一年的账号费,再想想数据放在别人服务器上,又开始犹豫。其实一套基于ThinkPHP6+Layui+MySql打造的开源企业办公系统就能解决这类诉求。勾股OA正是这条路:它把审批、考勤、通知、通讯录这些企业管理里的高频模块,用PHP生态里最成熟的组合预先做好,部署到自己的服务器上,数据不出门,二次开发也有干净的代码底座。这篇笔记写给两类人:一类是想快速搭建办公自动化系统、又不想从零写代码的技术负责人;另一类是打算在这套系统上做定制开发的PHP工程师。我会把选型理由、部署步骤、二次开发路径和上线踩坑一次性讲透。

2. 为什么选ThinkPHP6+Layui+MySql:先把代码结构看懂一半

2.1 ThinkPHP6给OA提供了什么:MVC骨架、验证器与数据库封装

ThinkPHP6的目录结构天然适合OA这类传统B/S系统。入口文件在public目录下,app目录按控制器、模型、视图分好层,config目录集中管配置。相比前后端分离方案,服务端渲染模式下的TP6把路由解析、请求参数获取、数据库操作封装得足够顺手,一个熟悉PHP的开发者看半天代码就能上手改需求。

TP6对OA开发最有用的三个能力是验证器、Db门面和中间件。以提交请假单为例,控制器里不需要手写一堆if (empty($name)),用验证器定义规则即可;查询数据库用Db::name('表名')一行链式调用,返回的数组直接传给模板渲染。代码写出来是下面这个形态:

<?php namespace app\admin\controller; use app\BaseController; use think\facade\Db; use think\facade\View; class Leave extends BaseController { public function index() { $lists = Db::name('leave')->where('status', '>', 0)->paginate(10); return View::fetch('index', ['lists' => $lists]); } }

Db::name('leave')对应数据库中的leave表,不需要额外定义模型类;paginate(10)是TP6的分页方法,返回的分页对象里包含数据和总条数;View::fetch()找到view/leave/index.html模板并注入变量。这套写法的好处是代码量小、排错直观——出问题时顺着控制器里的方法名就能找到对应模板和表名。

2.2 Layui在这里的角色:不用前端工程化的UI栈

Layui 2.x的经典用法是不需要npm、不需要webpack,直接在模板里引一个layui.js和样式文件,然后用layui.use按需加载模块。它和TP6服务端渲染的配合非常顺畅:TP6负责把页面骨架和数据输出成HTML,Layui负责把表格、表单、日期选择器、弹出层这些交互组件渲染出来。

在勾股OA这类系统里,用得最多的Layui模块是table、form、layer、laydate。表格列表通常走异步接口,模板里放一个空的<table id="listTable">,JS里用table.render()指定请求的URL,后端返回JSON,前端自动渲染分页表格。经典写法如下:

<!-- 视图文件里的表格容器 --> <table id="listTable" lay-filter="listTable"></table> <script> layui.use(['table', 'form', 'layer'], function(){ var table = layui.table; var layer = layui.layer; table.render({ elem: '#listTable', url: '/admin/leave/getLists', cols: [[ {field: 'id', title: '编号', width: 80}, {field: 'name', title: '申请人', width: 120}, {field: 'create_time', title: '申请时间', width: 180} ]], page: true }); }); </script>

url指向TP6控制器里返回JSON的方法;cols定义表格列;page: true开启分页。这里有个常见坑是Layui的table默认要求接口返回{"code":0,"msg":"","count":N,"data":[...]}格式,而TP6的paginate()返回结构是{"total":N,"per_page":...}之类,所以取列表JSON时一般要单独组装一次,后面操作时会讲到。

2.3 MySQL表结构:OA系统审批流的建模套路

勾股OA基于MySQL做数据存储,核心其实是少数几张关系表撑起整个审批链路。典型OA系统的表结构大差不差,我一般拿到开源代码后会先看四个关键表:用户与部门表、流程模板表、流程节点表、审批记录表。

用户与部门表维持树形结构,parent_id指向上级部门;流程模板表定义“请假审批”“报销审批”这类流程叫什么名字、走几步;流程节点表定义每一步由哪个角色审批、能否转交;审批记录表存每一次审批操作的结果和意见。业务申请单(比如请假单、加班单)通过一个process_id字段关联到流程模板上,再通过状态字段标记当前走到哪一步。

表作用关键字段说明
部门表id, parent_id, name, sort树形结构,parent_id为0表示顶级部门
用户表id, dept_id, username, nickname, status通过dept_id关联部门
流程模板表id, name, code, create_time定义一类审批流程
流程节点表id, process_id, node_name, auditor_typeauditor_type标识按角色还是按指定人员审批
审批记录表id, biz_type, biz_id, auditor_id, result, remark, create_time记录每次审批动作

这张表结构的设计逻辑是“模板与实例分离”:流程模板定义好之后可以长期复用,每次请假、报销都会产生一条申请数据关联模板。审批记录表用biz_type加biz_id的多态字段关联不同的业务单,而不是为每种单据单独建一张审批记录表,这样新增业务模块时不需要改动审批记录结构。

2.4 一个请求从URL到页面的完整链路

明白了表结构,再看一次完整请求链路就很有体感。假设用户打开“请假列表”页面,浏览器请求/admin/leave/index,Nginx把请求交给public/index.php,TP6路由解析出控制器为admin模块下的Leave控制器,执行index()方法。index()里用Db门面查询数据库,把分页结果传给模板,模板渲染成HTML返回浏览器,最后Layui在浏览器端把表格增强成可交互的分页组件。

这段链路里每个环节都能单独验证。页面空白先看Nginx错误日志;控制器报错开启TP6的调试模式能看到具体文件和行号;查不到数据直接手动在MySQL客户端执行同一条SQL,判断是SQL问题还是数据问题。排查思路比背命令更重要,这也是我反复和新人强调的:这条链路理解了,后面改任何模块都能按图索骥。

3. 本地跑通勾股OA:从环境准备到登录后台的完整步骤

3.1 环境准备:PHP版本、扩展与站点配置

勾股OA基于ThinkPHP6开发,TP6官方支持PHP 7.3到8.0以上,但实际部署时我更推荐PHP 7.4。原因很实在:不少开源OA的代码是从PHP 5.x时代迁移过来的,在PHP 8.0上容易出现str_replace、隐式类型转换之类的兼容告警,虽然不影响核心功能,但某些第三方扩展库可能在PHP 8下直接报错。PHP 7.4性能足够、兼容性又稳,是这类系统的“甜点版本”。

PHP扩展方面,安装前先检查以下几项是否启用:

php -v php -m | grep -E '^(fileinfo|sodium|curl|openssl|pdo_mysql|mbstring|gd)$'

fileinfo用于文件上传时获取MIME类型,OA系统里传附件几乎必用;pdo_mysql是连接MySQL的驱动,缺失会直接连不上库;mbstring用于中文字符串处理,没有它会遇到乱码;gd是图片缩略图需要。如果TP6自带的扩展检测页面提示缺失,可以用系统包管理器安装,比如Debian系执行apt install php7.4-{fileinfo,mbstring,gd,curl}。

Nginx站点配置里有一个关键点:网站根目录必须指向public目录。很多人习惯把根目录指到项目根路径,结果访问时会把config、route等敏感目录暴露在Web可访问范围内,而且TP6的路由会全部失效。参考配置如下:

server { listen 80; server_name your-domain.com; root /var/www/gougu/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }

root指向public目录是ThinkPHP6部署的通用约定;rewrite规则让所有不存在的文件路径统一进入index.php,由TP6内部路由解析,这是去掉URL里index.php的常规做法。如果你用Apache,则用.htaccess文件里的mod_rewrite实现同样的效果。

3.2 获取源码与安装依赖

从项目主页下载完整源码包,解压到网站目录后,先确认vendor目录是否存在。vendor是ThinkPHP6保存依赖库的地方,如果源码包带了就跳过这一步;如果没带,需要先在服务器上装好Composer,然后进入项目目录执行依赖安装。

cd /var/www/gougu composer install --no-dev --prefer-dist chmod -R 755 runtime

--no-dev表示不安装开发环境依赖,生产环境用这个可以减少文件数量并提升安全性;--prefer-dist优先下载压缩包,比从Git仓库逐个clone快很多。如果根目录里没有composer.lock文件,Composer会提示你先执行composer update,但这里建议直接按项目本身带的方式进行。runtime目录是TP6的日志、缓存、Session存储位置,权限不对会导致页面白屏或无法写入日志。

3.3 创建数据库并导入初始数据

源码包里通常会附带SQL安装文件,文件名一般类似install.sql或gougu.sql。先创建数据库再导入,注意字符集要统一用utf8mb4,否则中文正常但用户在审批意见里填个emoji就会存不进库。

mysql -uroot -p CREATE DATABASE gougu_oa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; exit; mysql -uroot -p gougu_oa < /www/backup/gougu.sql

导入前建议用文本编辑器打开SQL文件看一眼头部,确认里面没有CREATE DATABASE语句,如果有,把数据库名改成你刚创建的库名,避免导入时覆盖同名库。导入完成后,用mysql -uroot -p -e "use gougu_oa; show tables;"确认表都建出来了,正常会有几十张表,包含用户、部门、流程、审批、通知、考勤等模块。

3.4 修改数据库配置并登录后台

TP6的数据库配置在项目根目录的.env文件里,这是优先级最高的配置途径。打开后找到数据库段落,改成自己的连接信息:

DATABASE_TYPE = mysql DATABASE_HOST = 127.0.0.1 DATABASE_NAME = gougu_oa DATABASE_USER = root DATABASE_PASSWORD = your_password DATABASE_PORT = 3306 DATABASE_PREFIX = oa_

DATABASE_PREFIX是表前缀,勾股OA这类系统通常会有统一前缀,必须和SQL文件里建表语句的前缀一致。如果源码包里没有.env文件,那么配置在config/database.php中,结构一样,只是数组写法。改完之后访问站点根域名,正常情况下会跳转到后台登录页;后台入口一般在/admin,如果找不到就在安装文档里查一下。

用安装时产生的初始账号登录后,第一件事是修改管理员密码,并到“系统设置”里核对站点名称、上传目录、附件尺寸限制等参数。这里注意,默认账号密码一定不要继续用,系统放到公网上之后不到一天就会被扫描工具盯上。

4. 二次开发实操:给审批模块加一个自定义流程

4.1 先读懂勾股OA的代码骨架:从URL到控制器再到模板

做二次开发之前,我先习惯性做一件事:顺着一个现有功能把控制器、模型、视图、路由四级文件各自的位置标出来。以请假审批为例,访问/admin/leave/index时,控制器在app/admin/controller/Leave.php,模板在app/admin/view/leave/index.html,数据库操作直接写在控制器里或通过模型类完成。

<?php namespace app\admin\controller; use app\BaseController; use think\facade\Db; use think\facade\View; class Leave extends BaseController { public function index() { $user_id = session('user_id'); $lists = Db::name('leave') ->where('user_id', $user_id) ->order('id', 'desc') ->paginate(10); return View::fetch('index', ['lists' => $lists]); } }

session('user_id')是读取登录时写入的用户ID,用来过滤当前用户能看到的请假数据;order('id', 'desc')让新提交的申请排在最前面;paginate(10)生成普通分页。看到这个结构你会发现,新增一个业务模块不过就是复制这个模式:建一张表、写一个控制器、加两个页面。

4.2 新增加班申请单:建表与字段设计

我以“加班申请单”为例,演示怎么在现有结构里新增业务流程。先建一张业务表,字段覆盖基础业务信息和审批状态:

CREATE TABLE `oa_overtime` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '申请人ID', `start_time` datetime NOT NULL COMMENT '加班开始时间', `end_time` datetime NOT NULL COMMENT '加班结束时间', `hours` decimal(4,1) NOT NULL DEFAULT '0.0' COMMENT '加班时长', `reason` varchar(500) DEFAULT '' COMMENT '加班事由', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0草稿 1审批中 2通过 3驳回', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='加班申请表';

status字段是这类表的关键设计,它决定列表页怎么展示、审批按钮怎么渲染、数据进入审批流之前怎么过滤。create_time用整型保存时间戳,比datetime更适合做范围查询和比较排序,前端展示时再格式化。hours用decimal(4,1)而不是float,避免浮点精度问题在月底统计加班时长时算错小数点。

4.3 控制器与Layui页面:提交一张加班申请单

表建好之后,写控制器的apply方法和对应的提交模板。控制器要做三件事:判断请求方式、验证参数、写库并返回JSON给前端。

public function apply() { if ($this->request->isPost()) { $params = $this->request->post(); $validate = new \think\Validate([ 'start_time' => 'require|date', 'end_time' => 'require|date', 'hours' => 'require|float|between:0,24', 'reason' => 'require|max:500', ]); if (!$validate->check($params)) { return json(['code' => 0, 'msg' => $validate->getError()]); } $data = $params; $data['user_id'] = session('user_id'); $data['status'] = 1; $data['create_time'] = time(); $data['update_time'] = time(); Db::name('overtime')->insert($data); return json(['code' => 1, 'msg' => '提交成功']); } return View::fetch(); }

isPost()判断请求方式,保证表单重复提交时走同一入口;验证器规则里start_time用date规则校验格式,hours用between:0,24限定取值范围;json()返回统一格式,这样前端Layui的jquery模块可以直接根据code字段判断成功或失败。视图模板用Layui的form和laydate组件渲染:

<form class="layui-form" lay-filter="overtimeForm"> <div class="layui-form-item"> <label class="layui-form-label">开始时间</label> <div class="layui-input-inline"> <input type="text" name="start_time" id="startTime" class="layui-input" placeholder="请选择开始时间"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">结束时间</label> <div class="layui-input-inline"> <input type="text" name="end_time" id="endTime" class="layui-input" placeholder="请选择结束时间"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">加班时长</label> <div class="layui-input-inline"> <input type="text" name="hours" class="layui-input" placeholder="例如:2.5"> </div> </div> <div class="layui-form-item layui-form-text"> <label class="layui-form-label">加班事由</label> <div class="layui-input-block"> <textarea name="reason" class="layui-textarea" placeholder="填写加班原因"></textarea> </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button class="layui-btn" lay-submit lay-filter="overtimeSubmit">提交申请</button> </div> </div> </form> <script> layui.use(['form', 'laydate', 'jquery', 'layer'], function(){ var form = layui.form; var laydate = layui.laydate; var $ = layui.jquery; var layer = layui.layer; laydate.render({elem: '#startTime', type: 'datetime'}); laydate.render({elem: '#endTime', type: 'datetime'}); form.on('submit(overtimeSubmit)', function(data){ $.post('/admin/overtime/apply', data.field, function(res){ if (res.code === 1) { layer.msg('提交成功', {icon: 1}); setTimeout(function(){ location.reload(); }, 1000); } else { layer.msg(res.msg, {icon: 2}); } }, 'json'); return false; }); }); </script>

lay-filter是Layui里区分不同表单的标识,form.on('submit(overtimeSubmit)')绑定按钮提交事件;data.field自动收集表单里所有带name属性的字段,序列化成对象传给后端;return false阻止原生表单跳转,保证用AJAX提交。这段代码跑通之后,加班申请就从“写SQL”变成了“点按钮就能提交”。

4.4 审批动作与菜单挂接:把流程真正转起来

申请提交成功只是第一步,还需要写审批动作。审批通过和驳回的逻辑核心是更新业务表状态、记录审批人意见:

public function audit() { $id = $this->request->post('id'); $result = $this->request->post('result'); // pass 或 reject $remark = $this->request->post('remark'); $update = ['status' => $result === 'pass' ? 2 : 3, 'update_time' => time()]; Db::name('overtime')->where('id', $id)->update($update); return json(['code' => 1, 'msg' => '审批完成']); }

审批动作不需要太复杂的逻辑,关键是把状态流转写清楚。更完整的做法是在审批记录表里插入一条记录,保存审批人、审批时间、审批意见、审批结果,方便日后追溯。我在实际项目里会把这块写成一个公共方法,所有业务模块的审批都调同一个逻辑,减少重复代码。

最后一步是菜单挂接。勾股OA这类系统通常有菜单管理或权限节点管理,在后台菜单表里插入一条记录,名称填“加班申请”,链接填/admin/overtime/apply,启用状态设为1,再给对应角色打上权限勾选,菜单就会出现在后台导航里。这一步在不同的OA系统里操作方式略有差异,但表结构思路一致,找到菜单表插入数据即可。

5. 部署上线与运维避坑:本地能跑不代表服务器能用

5.1 先认清三处环境差异:PHP版本、扩展、伪静态

本地开发环境多用面板工具集成,PHP版本、扩展、Nginx配置都是现成的。一到服务器,环境差异就出来了。最常见的差异有三个:PHP版本不一致导致兼容问题;fileinfo等扩展缺失导致附件相关功能异常;Nginx伪静态规则没写,导致访问任何二级路径都404。

上线前先跑一条命令把服务器环境摸清:

php -v php -m nginx -t

nginx -t用于检查Nginx配置文件语法,如果配置错误,服务可能直接启动失败。这三条命令执行完,对比本地环境差异,差异项优先补齐。项目代码在上线前应该先在预发布环境完整跑一遍测试用例,不要直接在服务器上改代码找报错。

5.2 现象:页面白屏或HTTP 500

本地跑得好好的,传到服务器打开页面就白屏。这种情况九成是三类原因:PHP扩展缺失、vendor目录没传全、runtime目录不可写。

解决思路是:先把TP6调试模式打开,让真实报错显示出来。在.env文件里设置APP_DEBUG = true,再设置APP_TRACE = true,刷新页面会直接看到具体报错信息和出错的PHP文件。如果是扩展缺失,会提示Call to undefined function finfo_open()之类的错误,安装对应扩展即可。如果是runtime目录不可写,报错会指向日志文件无法写入,执行chmod -R 755 runtime授权。排错完成后切记关闭调试模式,生产环境开调试会把数据库配置和文件路径直接暴露给访问者。

5.3 现象:登录成功后立刻跳回登录页

部署到服务器后,登录页输入账号密码,显示登录成功,跳转一下又回到登录页,或者提示登录状态过期。这类问题本质是Session没保存下来,常见原因有两个:一是runtime目录下的session目录没有写权限,Session文件写不进去;二是部署在多个Web节点时,没有配置共享Session存储,A节点写入的Session,B节点读不到。

单机场景先检查目录权限,再检查config/session.php中expire配置是否设置得太短。多机场景则需要把Session存储切换到Redis或数据库:

// config/session.php return [ 'type' => 'redis', 'host' => '127.0.0.1', 'port' => 6379, 'prefix' => 'gougu_', 'expire' => 3600, ];

type设为redis后,所有Web节点共享同一个Session存储,登录状态就不会乱跳。expire即Session过期时间,按小时计算比较合理,设太短用户隔一会儿就掉线。上线前还需要确认Redis服务和PHP的redis扩展是否安装,php -m | grep redis可以快速检查。

5.4 现象:附件上传失败,明明选了文件

OA系统传附件失败,很少是代码问题,通常是好几种限制叠在一起。PHP默认限制upload_max_filesize只有2M,post_max_size是8M;Nginx默认client_max_body_size是1M;项目代码里可能还有一层附件大小限制。任何一个环节没放开,最终表现都一样:上传到一半失败或直接提示文件过大。

排查时按从外到内的顺序查:

php -i | grep -E 'upload_max_filesize|post_max_size' nginx -T | grep client_max_body_size

php -i输出PHP配置,nginx -T输出Nginx当前生效的配置。定位到限制项后,在php.ini里调大upload_max_filesize为20M、post_max_size为25M;在Nginx的http或server块里加client_max_body_size 20m;,改完重启php-fpm和Nginx。最后再确认public/uploads或附件目录的写权限,权限不对会表现为“提示上传成功但找不到文件”。

5.5 现象:定时任务不跑,考勤统计和提醒失灵

OA系统基本都有定时任务,比如每天早上汇总待办、统计考勤、发送通知。这类功能依赖命令行执行,很多部署的人只配置了Web环境,忘记了命令行环境。常见的错误是把php的绝对路径写错,或者项目路径用的是相对路径导致定时任务找不到文件。

先手动执行一遍定时任务脚本,确认能跑通再写crontab:

/usr/bin/php /var/www/gougu/think cron:run >> /var/www/gougu/runtime/cron.log 2>&1

/usr/bin/php是PHP解释器的绝对路径,不同服务器位置可能有差异,用which php查看;>>把输出追加到日志文件,出问题时能看原因。手动执行成功后,配置crontab每分钟执行一次测试,确认日志里有预期输出,再改成实际需要的频率。如果服务器用宝塔面板,也可以在面板里直接添加计划任务,效果一样。

6. 进阶:给勾股OA做性能优化与安全加固的几个地道做法

6.1 用OPcache与慢查询日志定位并解决性能瓶颈

OA系统用户量不大时性能问题不明显,但几百人同时访问时,PHP每次请求重新编译模板和代码的开销就很可观。开启OPcache是最直接的提升手段:

opcache.enable=1 opcache.memory_consumption=128 opcache.max_accelerated_files=10000 opcache.validate_timestamps=1 opcache.revalidate_freq=60

memory_consumption是OPcache缓存池大小,128对OA这种中小型应用足够;validate_timestamps设为1表示按文件修改时间判断是否重新编译,代码更新后不需要手动清缓存。改完php.ini后必须重启php-fpm,很多开发改完配置不重启,然后抱怨“做了修改没生效”,我就在这上面吃过亏。

数据库层面,开启MySQL慢查询日志能快速定位性能瓶颈:

SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;

long_query_time设置为1,执行时间超过1秒的SQL都会写入慢查询日志。常见问题是列表页查询没走索引,比如where user_id = ? order by id desc这条SQL,在user_id字段上建立索引就够了:ALTER TABLE oa_overtime ADD INDEX idx_user_id (user_id);。加索引之后一般能快一个数量级以上。

6.2 三件不起眼但保命的事:改后台入口、登录锁定、定时备份

后台入口默认是/admin,放在公网会被扫描程序频繁探测。我习惯把后台入口改成一个不明显的路径,比如在源码里将入口文件改名,或者在路由配置里把admin别名改掉。这一步挡不住有耐心的人,但能过滤掉绝大多数自动化扫描。

登录失败锁定也值得做:在登录控制器里记录连续失败次数,超过5次锁10分钟,失败次数存Session或Redis。这个功能不用改太多代码,在现有登录方法里加几行计数逻辑即可。最后是备份,OA系统的数据丢了几乎不可恢复,我现在的习惯是写一个crontab每天凌晨备份数据库,保留最近30天的备份文件:

0 2 * * * mysqldump -uroot -p'密码' gougu_oa | gzip > /www/backup/gougu_oa_$(date +\%Y\%m\%d).sql.gz

我接手过一套OA系统,前一位开发者把备份脚本放在服务器根目录,磁盘满了之后备份失败了一个月,直到数据库误删才发现没有任何可用备份。之后我把备份目录独立挂载,脚本里增加磁盘空间检查,故障才不再出现。这套开源OA的方向是值得投入的,但前提是你把它当成一套要长期运维的内部系统来对待,而不是装完就撒手。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询