简介:《通达OA二次开发手册》面向具备编程基础、需要定制Office Anywhere网络智能办公系统的技术人员,帮助其理解系统架构并完成功能扩展与业务适配。手册从开发环境搭建讲起,涵盖OfficeFPM、OfficWeb、PHP与MySQL的参数配置及协同关系,并逐一解析auth.inc.php、header.inc.php、common.inc.php、conn.php等核心文件的作用;数据库管理部分则涉及phpMyAdmin安装使用、表结构分析与备份恢复策略,第三章还完整演示了模块目录建立、菜单创建、权限分配与编码测试的流程。资源为1个PDF文件,压缩包约188KB,轻量便于随查随用。目前已有89人学习,适合希望快速上手通达OA二次开发、掌握环境配置与模块创建思路的开发者参考。
1. 通达OA二次开发手册:从一份PDF到能跑通的第一个自定义模块
很多单位用通达OA用了五六年,流程审批、公文、考勤都跑得挺顺,直到业务部门提一个「能不能在请假单上自动带出上个月加班时长」的需求,才发现标准功能改不动。这时候翻到一份《通达OA二次开发手册.pdf》,第一反应往往是:这东西到底能不能落地,还是只能看看。通达OA的二次开发本质是在既有PHP框架上做模块扩展和钩子挂载,不是从零写一套系统,所以门槛比想象中低,但坑也比想象中密。这篇笔记面向两类人:一类是单位里兼着管OA的运维,想自己动手改点小功能;另一类是接外包的PHP开发者,第一次碰通达这套结构。我会按「先搞清目录结构 → 再跑通最小模块 → 再处理数据表和权限 → 最后讲排错」的顺序讲,每一步都给能直接抄的命令和代码,参数怎么改、失败看哪里都写清楚。手册本身是索引,真正干活靠的是对目录和数据库的理解,这一点先立住,后面才不会翻车。
2. 通达OA二次开发的环境与目录:先搞清楚代码往哪放
2.1 通达OA的目录结构和二次开发入口
通达OA的Web根目录通常叫webroot,不同版本可能叫www或htdocs,但内部结构基本一致。核心目录有这么几个:inc/放公共函数和数据库连接,module/放各功能模块,static/放前端资源,attachment/放上传文件,mysql/或独立数据库服务放数据。二次开发最常动的是module/和inc/,前者加新页面,后者挂钩子。
判断一个通达OA是不是标准部署,看根目录有没有inc/conn.php和module/两个东西就够了。conn.php里是数据库连接参数,module/下每个子目录对应一个功能,比如module/workflow/是工作流,module/calendar/是日程。你要加的自定义模块,就在module/下新建一个目录,比如module/custom_leave/,里面放index.php和必要的类文件。
这里有个容易忽略的点:通达OA的入口文件通常有一个统一的权限校验,直接访问module/custom_leave/index.php会被拦。常见做法是在文件头部引入inc/auth.php或对应的权限检查文件,具体文件名看版本,一般在inc/下能找到auth.php、checklogin.php之类。我一般会先复制一个现有模块的index.php,把里面的业务逻辑删掉,保留头部引入和权限判断,这样最稳。
2.2 用最小命令验证环境是否可写可跑
动手之前先确认三件事:PHP能不能跑、数据库连不连得上、目录有没有写权限。下面这几条命令在服务器上直接执行,输出正常再往下走。
# 查看PHP版本,通达OA常见兼容区间是PHP 5.6到7.4,具体看版本 php -v # 进入Web根目录,确认关键文件存在 cd /path/to/webroot ls inc/conn.php module/ static/ # 检查module目录是否可写,二次开发要在这里建目录 touch module/_test_write && rm module/_test_write && echo "module可写" # 如果数据库在本机,测试连接,用户名密码从inc/conn.php里找 mysql -u oa_user -p -e "show databases;"php -v的输出重点看主版本号,PHP 8以上很多老通达OA会报废弃函数警告,不是不能跑,但日志会变多。ls那一步如果inc/conn.php不存在,说明部署不完整或者路径不对,先解决这个再谈开发。touch测试的是文件系统权限,Linux下Web进程用户和登录用户可能不同,如果这里失败,后面建模块目录也会失败。mysql命令里的用户名密码不要猜,直接打开inc/conn.php看,里面通常是明文或简单编码,这是排查数据库问题的第一手资料。
数据库连通之后,建议再执行一条查询确认表前缀。通达OA的表前缀默认是td_,但有些部署改过。查一下:
-- 确认表前缀和用户表结构 SHOW TABLES LIKE '%user%'; DESC td_user;SHOW TABLES的结果里如果看到td_user、td_workflow_run这类表,说明前缀是td_。DESC td_user看用户表字段,后面做权限关联会用到USER_ID、USER_NAME、DEPT_ID这几个。这一步不做,后面写SQL很容易把表名写错,报「表不存在」还找不到原因。
2.3 第一个自定义模块:从复制到改通
新建module/custom_leave/index.php,内容先做到「能打开、能显示当前登录用户」。代码如下:
<?php // 引入通达OA的公共初始化和权限校验 // 具体文件名按版本调整,常见是inc/auth.php或inc/checklogin.php include_once "../../inc/auth.php"; include_once "../../inc/conn.php"; // 获取当前登录用户信息,通达OA通常用$_SESSION存储 $userId = $_SESSION["LOGIN_USER_ID"] ?? ""; $userName = $_SESSION["LOGIN_USER_NAME"] ?? ""; if ($userId === "") { echo "未登录或会话失效"; exit; } // 查询用户部门,演示数据库读取 $sql = "SELECT DEPT_ID FROM td_user WHERE USER_ID = '" . addslashes($userId) . "'"; $cursor = exequery(TD::conn(), $sql); $row = mysql_fetch_array($cursor); $deptId = $row["DEPT_ID"] ?? "未知"; echo "当前用户:" . htmlspecialchars($userName) . ",部门ID:" . htmlspecialchars($deptId);这段代码的逻辑分三层:第一层是引入,auth.php负责会话和权限,conn.php负责数据库连接,两个都引入才能用exequery这类通达封装的函数。第二层是从$_SESSION取登录信息,通达OA的会话变量名在不同版本里可能是LOGIN_USER_ID或USER_ID,如果取不到,先打印print_r($_SESSION)看实际键名。第三层是查询,exequery是通达对mysql_query的封装,第一个参数是连接对象,第二个是SQL。这里用addslashes做简单转义,生产环境建议用通达自带的过滤函数,具体看inc/utility.php里有没有td_htmlspecialchars之类。
参数说明:../../inc/auth.php的相对路径取决于你的模块目录深度,module/custom_leave/到根目录是两级,所以用../../。如果模块放在更深一层,路径要相应调整。td_user表名如果前缀不是td_,改成实际前缀。DEPT_ID字段在部分版本里叫DEPT_ID,部分叫DEPTID,用DESC td_user确认。
访问http://你的OA地址/module/custom_leave/index.php,能看到「当前用户:xxx,部门ID:xxx」就算跑通了。这一步是整个二次开发的地基,后面所有功能都是在这个骨架上加逻辑。
3. 数据表操作与工作流钩子:二次开发真正干活的地方
3.1 通达OA数据表命名规律与安全查询
通达OA的表大致分几类:td_user开头是用户和组织,td_workflow_开头是工作流,td_flow_开头是流程实例,td_oa_开头是通用办公数据。二次开发读数据多、写数据少,但一旦要写,必须知道哪些表能写、哪些表只能读。
能安全读的表:td_user、td_department、td_workflow_type、td_workflow_run。这些表结构相对稳定,字段含义明确。谨慎写的表:td_workflow_run的状态字段、td_flow_run的流程数据。绝对不要直接改的表:td_user的密码字段、td_sys_开头的系统配置表。改这些表轻则功能异常,重则整个OA登录不了,血泪经验。
查询时用通达封装的exequery,不要直接用mysql_query,因为通达可能在连接上做了字符集和时区设置。下面是一个按部门统计用户数的例子:
<?php include_once "../../inc/auth.php"; include_once "../../inc/conn.php"; // 统计每个部门的用户数,按部门ID分组 $sql = "SELECT DEPT_ID, COUNT(*) AS user_count FROM td_user WHERE DEPT_ID IS NOT NULL AND DEPT_ID != '' GROUP BY DEPT_ID ORDER BY user_count DESC"; $cursor = exequery(TD::conn(), $sql); // 先取部门名称做映射,避免在循环里反复查库 $deptMap = []; $deptSql = "SELECT DEPT_ID, DEPT_NAME FROM td_department"; $deptCursor = exequery(TD::conn(), $deptSql); while ($deptRow = mysql_fetch_array($deptCursor)) { $deptMap[$deptRow["DEPT_ID"]] = $deptRow["DEPT_NAME"]; } while ($row = mysql_fetch_array($cursor)) { $deptName = $deptMap[$row["DEPT_ID"]] ?? "未知部门"; echo $deptName . ":" . $row["user_count"] . "人<br>"; }逻辑说明:先查用户表按部门分组,再一次性查部门表建映射,最后循环输出。这样避免在用户循环里每条都查一次部门表,数据量大时性能差别很明显。参数上,DEPT_ID IS NOT NULL AND DEPT_ID != ''是为了排除没设部门的用户,通达OA里有些账号是系统账号,不挂部门。COUNT(*) AS user_count的别名在mysql_fetch_array里用关联键取,注意大小写要和SQL里一致。
如果查询结果为空,先确认td_user表里有没有数据,再确认DEPT_ID字段名是否正确。有些版本用DEPT_ID,有些用DEPTID,用DESC看最准。
3.2 工作流钩子的挂载位置与触发时机
通达OA的工作流是二次开发需求最集中的地方。常见需求:流程提交后发通知、流程结束时写外部系统、表单字段联动。这些都要挂钩子。通达的工作流钩子一般放在module/workflow/下的几个关键文件里,比如流程保存、流程结束、流程转交。
以「流程结束后写一条自定义日志」为例,找到流程结束的处理文件,常见是module/workflow/flow_end.php或类似命名。在流程状态更新之后、页面跳转之前插入代码:
<?php // 这段代码插入到流程结束逻辑之后 // $RUN_ID 是流程实例ID,$FLOW_ID 是流程定义ID,这两个变量在上下文中通常已存在 if (!empty($RUN_ID) && !empty($FLOW_ID)) { // 查流程发起人,用于记录 $runSql = "SELECT BEGIN_USER FROM td_workflow_run WHERE RUN_ID = '" . intval($RUN_ID) . "'"; $runCursor = exequery(TD::conn(), $runSql); $runRow = mysql_fetch_array($runCursor); $beginUser = $runRow["BEGIN_USER"] ?? ""; // 写入自定义日志表,表要提前建好 $logSql = "INSERT INTO td_custom_flow_log (RUN_ID, FLOW_ID, BEGIN_USER, END_TIME) VALUES ('" . intval($RUN_ID) . "', '" . intval($FLOW_ID) . "', '" . addslashes($beginUser) . "', NOW())"; exequery(TD::conn(), $logSql); }逻辑说明:先判断$RUN_ID和$FLOW_ID有没有值,避免在非流程场景下误写。然后查流程发起人,最后插入自定义日志表。intval用于数字型字段,addslashes用于字符串型字段,这是最基本的防注入习惯。NOW()是MySQL函数,写服务器当前时间。
自定义日志表要提前建:
CREATE TABLE td_custom_flow_log ( ID INT AUTO_INCREMENT PRIMARY KEY, RUN_ID INT NOT NULL, FLOW_ID INT NOT NULL, BEGIN_USER VARCHAR(50), END_TIME DATETIME, INDEX idx_run (RUN_ID) );表名用td_前缀保持一致,INDEX idx_run是为了后面按流程实例查日志时快一点。字段类型上,RUN_ID和FLOW_ID用INT,BEGIN_USER用VARCHAR(50),通达OA的用户ID一般不超过这个长度。
挂钩子最容易翻车的地方是找错文件。不同版本通达OA的工作流文件命名有差异,有的叫flow_end.php,有的叫workflow_end.php。我一般会先在module/workflow/下用grep搜关键词:
# 在当前目录及子目录搜索流程结束相关的代码 grep -rn "流程结束\|flow_end\|end_flow" module/workflow/ | head -20grep -rn的-r是递归,-n显示行号,head -20只看前20条。找到候选文件后,先备份再改,改完立刻测一条流程,看日志表有没有数据。没有数据就检查:钩子文件对不对、变量名对不对、SQL有没有报错。SQL报错可以临时在exequery后面加echo mysql_error();看具体错误,测完删掉。
3.3 权限校验与菜单挂载
自定义模块要出现在通达OA的菜单里,需要改菜单配置。通达OA的菜单一般存在数据库表td_sys_menu或类似表里,也有版本用XML文件。常见做法是直接在数据库里插一条菜单记录:
-- 插入自定义菜单,PARENT_ID根据实际菜单树调整 INSERT INTO td_sys_menu (MENU_ID, MENU_NAME, MENU_URL, PARENT_ID, SORT_NO) VALUES (9001, '请假统计', '/module/custom_leave/index.php', 0, 99);MENU_ID要选一个没被占用的,9001这种大数字一般安全。PARENT_ID是父菜单ID,0表示顶级,如果要挂在「办公」下面,先查td_sys_menu里「办公」的MENU_ID。SORT_NO是排序,99表示靠后。插完菜单要清缓存,通达OA的菜单缓存一般在inc/cache/或attachment/cache/下,删掉对应文件或重启Web服务。
权限方面,通达OA的菜单权限和用户角色关联,表可能是td_sys_menu_priv或类似。如果插了菜单但用户看不到,先确认这个表里有没有给对应角色授权。最直接的办法是拿管理员账号登录看能不能看到,管理员能看到说明菜单本身没问题,是权限分配的事。
4. 通达OA二次开发避坑:那些手册不会写的翻车现场
4.1 现象:改完代码页面白屏,日志里只有一行「连接数据库失败」
原因:inc/conn.php里的数据库配置被改过,或者数据库服务没启动。通达OA的数据库连接失败不会在页面上显示详细错误,只会白屏或跳转。
解决:先看数据库服务状态,Linux下systemctl status mysql或service mysql status。如果服务正常,打开inc/conn.php核对主机、端口、用户名、密码、库名。特别注意端口,有些部署把MySQL端口从3306改成了3307或其它,conn.php里如果写的是localhost可能走socket而不是TCP,改成127.0.0.1:3307试试。改完清一下inc/cache/下的缓存文件。
4.2 现象:自定义模块能打开,但所有通达OA的函数都报「未定义」
原因:引入的公共文件不对,或者引入顺序错了。通达OA的exequery、TD::conn()这些函数定义在inc/conn.php或inc/utility.php里,如果只引入了auth.php没引入conn.php,就会报未定义。
解决:确认模块文件头部同时引入了权限文件和数据库文件。顺序上一般先auth.php再conn.php,因为auth.php可能依赖会话,conn.php依赖配置。如果还是报错,用grep -rn "function exequery" inc/找到函数定义在哪个文件,把那个文件也引入。
4.3 现象:工作流钩子挂上去之后,流程提交变慢或超时
原因:钩子里的SQL查询太慢,或者写了死循环。通达OA的流程提交是同步的,钩子里的代码会阻塞页面返回。
解决:钩子里的逻辑尽量轻,能异步的异步。如果必须查大表,加索引。比如按RUN_ID查日志表,RUN_ID上要有索引。如果钩子里要调外部接口,设超时时间,不要用默认的无超时。临时排查可以在钩子开头和结尾加error_log(microtime(true)),看耗时在哪一段。
4.4 现象:菜单插入了但用户看不到,管理员能看到
原因:菜单权限表没有对应角色的记录。通达OA的菜单显示受角色权限控制,只插菜单表不够。
解决:查td_sys_menu_priv或类似权限表的结构,给对应角色插一条授权记录。角色ID一般在td_user或td_role表里。如果找不到权限表,用管理员账号在后台的「菜单管理」里手动给角色勾选,这样最稳,不直接改库。
4.5 现象:自定义模块在测试环境正常,上线后报「文件不存在」
原因:测试环境和生产环境的通达OA版本或目录结构不一致,或者生产环境有多个Web节点,代码只传了一个节点。
解决:上线前确认生产环境的通达OA版本号和测试环境一致,module/目录路径一致。如果是多节点部署,每个节点都要传代码。另外检查生产环境的PHP版本,PHP 7.4和PHP 8.0对某些函数的处理不同,比如mysql_fetch_array在PHP 7以上已经废弃,通达OA如果还在用,说明它自己封装了兼容层,但你的新代码如果直接调mysql_*函数,在PHP 8下会报错。用通达封装的exequery和mysql_fetch_array时,确认当前PHP版本下这些函数还能用,不能用就找通达的替代函数。
5. 进阶:用自定义模块对接外部系统与验证方法
5.1 用通达OA做数据出口:把流程数据推给外部接口
二次开发做到后面,最常见的进阶需求是把通达OA的数据推给外部系统,比如把审批通过的报销单推给财务系统。通达OA本身没有开放平台,但可以用自定义模块加定时任务实现。
思路是:在自定义模块里写一个export.php,查询指定流程的已完成数据,组装成JSON,用cURL推给外部接口。然后配置服务器crontab定时调用这个脚本。代码骨架:
<?php // export.php 放在module/custom_leave/下 // 这个脚本不走会话,用密钥校验,避免暴露在公网 include_once "../../inc/conn.php"; // 简单密钥校验,实际用更安全的方式 $secret = $_GET["secret"] ?? ""; if ($secret !== "your_export_key_here") { exit("forbidden"); } // 查最近1小时完成的报销流程 $sql = "SELECT r.RUN_ID, r.FLOW_ID, r.BEGIN_USER, f.AMOUNT FROM td_workflow_run r LEFT JOIN td_flow_run f ON r.RUN_ID = f.RUN_ID WHERE r.FLOW_ID = 123 AND r.END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)"; $cursor = exequery(TD::conn(), $sql); $data = []; while ($row = mysql_fetch_array($cursor)) { $data[] = [ "run_id" => $row["RUN_ID"], "flow_id" => $row["FLOW_ID"], "user" => $row["BEGIN_USER"], "amount" => $row["AMOUNT"] ]; } // 推给外部接口 $ch = curl_init("http://外部系统地址/api/receive"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); // 记录推送结果,方便排查 file_put_contents("/tmp/oa_export.log", date("Y-m-d H:i:s") . " " . $response . "\n", FILE_APPEND);逻辑说明:脚本不引入auth.php,因为要脱离会话运行,改用密钥校验。查询用DATE_SUB(NOW(), INTERVAL 1 HOUR)取最近一小时的数据,避免重复推送。cURL设了10秒超时,防止外部接口卡住导致脚本堆积。最后写日志到/tmp,排查时看这个文件。
参数说明:FLOW_ID = 123要换成实际的流程定义ID,在td_workflow_type表里查。your_export_key_here换成随机字符串,不要用简单密码。外部接口地址和字段映射按对方要求改。crontab配置:
# 每5分钟执行一次 */5 * * * * /usr/bin/php /path/to/webroot/module/custom_leave/export.php >> /tmp/oa_export_cron.log 2>&1>>把标准输出追加到日志,2>&1把错误也写进去。这样脚本报错时能在日志里看到。
5.2 验证二次开发是否真的生效:三个检查点
改完代码不能只看页面能不能打开,要验证数据流是否完整。我一般按三个检查点走:
第一,看数据库。自定义表里有没有新数据,字段值对不对。比如流程日志表,跑一条流程后查SELECT * FROM td_custom_flow_log ORDER BY ID DESC LIMIT 5;,看RUN_ID、END_TIME是不是刚生成的。
第二,看日志。通达OA的日志一般在attachment/log/或inc/log/下,PHP错误日志看Web服务器的error_log。如果代码里有error_log()输出,去对应文件找。
第三,看外部系统。如果对接了外部接口,去对方系统确认数据有没有收到,字段有没有乱码。乱码一般是字符集问题,通达OA的数据库字符集常见是utf8或gbk,外部接口如果是utf8,在json_encode前加iconv转一下。
5.3 一个具体技巧:用通达OA的调试模式快速定位
通达OA有些版本支持调试模式,在配置文件里把DEBUG打开,页面底部会显示SQL执行时间和错误信息。如果找不到这个开关,可以在inc/conn.php里临时加一行error_reporting(E_ALL); ini_set('display_errors', 1);,这样PHP错误会直接显示在页面上。测完立刻删掉,生产环境不能开。
我自己的习惯是:每次改通达OA的代码前,先cp一份原文件到/tmp/backup_日期/,改完测通再删备份。这个习惯救过我好几次,有一次改工作流钩子把流程提交搞挂了,直接还原备份,五分钟恢复。通达OA的二次开发不难,难的是对既有逻辑的敬畏,改之前先想清楚影响范围,改之后先在小流程上测,别拿主流程试。
希望帮到你。
本文还有配套的精品资源,点击获取