2026/9/19 8:57:46

通达OA二次开发入门:从权限模型到自定义业务模块的完整实践路径

通达OA二次开发入门:从权限模型到自定义业务模块的完整实践路径 简介《通达OA二次开发手册》是一份面向具备编程基础的技术人员的专业指导文档旨在帮助开发者针对Office Anywhere网络智能办公系统进行二次开发满足企业个性化管理需求。手册系统覆盖了开发环境配置、核心文件结构、数据库管理及模块创建全流程适合希望快速上手通达OA定制开发的实施工程师、PHP开发人员阅读。资源为单个PDF文档压缩包仅188KB内容紧凑便于随时查阅。第二章「数据库管理」讲解了phpMyAdmin的安装与使用第三章则以「如何创建一个模块」为主线具体展示了建立模块目录、创建菜单、分配菜单权限、编码与测试的完整步骤具有很强的实操参考价值。目前已有88人学习下载适合需要系统了解通达OA二次开发路径、并希望从零开始搭建定制模块的技术人员作为案头参考。1. 通达OA二次开发8.1 架构先立住再谈改代码拿到一套通达OA 8.1很多开发者的第一反应是直接改 index.php 或者把页面整体重写。这个思路在二次开发里最容易翻车通达OA 的升级包会直接覆盖 webroot 下的大部分文件你改过的地方会全部丢光而 auth.inc.php、conn.php 这类公共文件一旦被动过影响的是全系统所有模块的登录态和数据库连接。正确的打开方式是把它当成一个自带权限体系的 PHP 应用框架来用——官方提供 auth.inc.php 做登录校验、common.inc.php 做公共函数装载、phpMyAdmin 做数据层入口开发者只需要在 webroot 下建独立目录、注册菜单、分配权限再调内置类库和函数库完成业务逻辑。这套思路基于 2015 年的 V8.1 手册发布编号 150425虽然是老版本但它把 OA 类系统最朴素的权限模型和模块加载机制暴露得很干净今天接手的很多存量 OA 项目仍然是这套结构。2. 环境与核心文件OfficWeb、OfficeFPM、PHP、MySQL 四层如何协同2.1 OfficWeb 与 OfficeFPMFastCGI 处理模型通达OA 8.1 的运行时由四个组件组成OfficWeb、OfficeFPM、PHP、MySQL。OfficWeb 是 Web 服务入口负责接收 HTTP 请求、分发静态资源遇到 .php 请求时转交给 OfficeFPM 处理OfficeFPM 是通达定制的 FastCGI 进程管理器维护一批 PHP-CGI 进程PHP 解释器执行脚本最终由 MySQL 存取数据。这四层是串行关系任何一层配置不当都会直接表现为页面白屏、上传失败或数据库连接超时。实际调优时容易忽略的是 OfficeFPM 这一层。很多时候你改了 php.ini 里的 memory_limit重启 Apache 之后发现不生效原因就是处理 PHP 请求的是 OfficeFPM 托管的 CGI 进程而不是 OfficWeb。常见做法是先找到 OfficeFPM 对应的服务确认它加载的 php.ini 路径与 OfficWeb 是否一致。V8.1 默认安装目录为 MYOAwebroot 就是站点根目录服务配置都在 MYOA\bin 下改完配置必须重启 OfficeFPM 进程。2.2 PHP 与 MySQL 的关键配置参数二次开发中高频踩坑的参数集中在 PHP 的上传限制和 MySQL 的连接数。以下参数表是 8.1 环境里最常需要调整的几项配置文件参数建议值作用php.inimemory_limit128M 以上工作流表单较大时防止白屏php.iniupload_max_filesize视附件大小而定控制单文件上传上限php.inipost_max_size大于 upload_max_filesize表单 POST 总量上限php.inimax_execution_time60 或更高防止大数据导入脚本超时被杀my.inimax_connections100 以上并发高时报 too many connectionsmy.iniwait_timeout60空闲连接回收时间这里有个细节post_max_size必须大于upload_max_filesize否则大文件上传会报「表单数据超过限制」而不是明确的文件过大错误排查方向容易跑偏。另外8.1 的库表中大量使用 MyISAM 引擎innodb_buffer_pool_size只在单纯跑 InnoDB 表的场景下才值得调不要照搬网上针对 MySQL 5.7 的优化脚本。2.3 四个核心 include 文件的分工手册第一章点名的四个文件是二次开发的入口它们都在MYOA\webroot\inc\下。如果把这套系统比作一个框架这四个文件就相当于框架的 bootstrap。文件职责auth.inc.php登录态与权限校验未登录跳转 login.phpheader.inc.php页面公共头部输出字符集、公共 JS/CSS 引用common.inc.php定义常量、引入公共函数文件conn.php建立 MySQL 连接提供全局变量 $connection二次开发模块的文件头部必须 include auth.inc.php而不是自己写一段判断 Session 的逻辑。auth.inc.php 校验通过后会写入$_SESSION[LOGIN_UID]、$_SESSION[LOGIN_NAME]等会话变量。common.inc.php 不建议直接改想加公共函数时优先新建独立文件再在 common.inc.php 里增加 include升级版本时只处理这一个文件即可。conn.php 在 8.1 版本中使用的还是 PHP 5 时代的 mysql_* 系列函数典型代码结构如下?php // 8.1 版本使用 mysql_* 系列函数适用于 PHP 5.x $myoa_host 127.0.0.1; $myoa_user root; $myoa_pass td_pass; $myoa_db td_oa; $connection mysql_connect($myoa_host, $myoa_user, $myoa_pass); if (!$connection) { die(数据库连接失败: . mysql_error()); } mysql_select_db($myoa_db, $connection); mysql_query(SET NAMES utf8, $connection); ?这段代码说明三件事第一数据库账号密码直接写在配置变量里二次开发时可以在自己的模块里读取同一份配置不要另开一个连接第二mysql_*函数在 PHP 7 中已被移除接老环境前先确认 PHP 版本第三SET NAMES utf8不要改动8.1 的多数表按 utf8 存储改成其他字符集全站中文乱码。提示如果要在 PHP 7 环境跑老代码常见做法是写一个兼容层把 mysql_connect、mysql_query 封装成 mysqli 版本再在公共文件里先行 include这样手册中的老代码可以不修改直接运行。3. 数据库管理phpMyAdmin 装好后先摸清 usr、department、flow_run 的表关系3.1 phpMyAdmin 安装与版本匹配手册第二章要求开发者用 phpMyAdmin 管理数据库。通达OA 8.1 的 MySQL 版本普遍在 5.1 到 5.5 之间直接下载最新版 phpMyAdmin 去连老库大概率在登录页就报版本不兼容。常见做法是把 phpMyAdmin 解压到 webroot 下命名为 phpmyadmin然后修改libraries/config.default.php或config.inc.php里的$cfg[blowfish_secret]设置一个随机字符串避免 Cookie 加密报错。访问路径为http://OA地址/phpmyadmin/用安装时设置的 MySQL root 账号登录。注意通达OA 安装时创建的数据库名称不一定是 td_oa有的集成环境叫 office_anywhere登录后先执行SHOW DATABASES;确认实际库名不要照着手册里的示例名称硬套。3.2 核心表关系与联表查询二次开发最常用的表集中在用户体系、部门体系和工作流三条线上。V8.1 中用户主表为usr部门表为department菜单表为sys_menu权限表为sys_priv流程实例表为flow_run。它们的关联关系是二次开发查询的基础表名用途关键字段usr用户主表USER_ID登录名、UID内部数字主键、DEPT_IDdepartment部门表DEPT_ID主键、DEPT_NAME、DEPT_PARENTsys_menu菜单表MENU_ID、MENU_NAME、MENU_URL、MENU_MODELsys_priv权限定义表PRIV_ID、PRIV_NAME、PRIV_FILEflow_run工作流实例表RUN_ID、RUN_NAME、RUN_USER、STATUS这里最容易踩的坑是 UID 和 USER_ID 的区分。usr 表里 USER_ID 是登录账号字符串UID 是内部数字主键。工作流、附件、日志等表里存的关联字段基本都是 UID而不是登录名。用用户名直接去关联 flow_run结果一定是空。下面这条 SQL 覆盖了最典型的取数需求按部门过滤出用户及其完整信息。SELECT u.UID, u.USER_ID, u.USER_NAME, d.DEPT_ID, d.DEPT_NAME FROM usr u LEFT JOIN department d ON u.DEPT_ID d.DEPT_ID WHERE u.STATUS 1 ORDER BY d.DEPT_ID, u.UID;说明STATUS 1表示用户启用状态锁定的账号不会出现在结果里DEPT_ID在 usr 表中指向用户所属的叶子部门LEFT JOIN 保证即使用户没有分配部门也能查出来。实际字段名可能随版本微调先用DESCRIBE usr;和DESCRIBE department;确认字段名再写正式查询。3.3 二次开发前的备份策略改表结构之前必须备份这是存量 OA 系统开发的基本纪律。备份用 mysqldump 命令行不要用 phpMyAdmin 导出大库容易超时中断。# 导出整个 OA 库注意指定 utf8 字符集 mysqldump -u root -p td_oa --default-character-setutf8 td_oa_$(date %Y%m%d).sql # 恢复数据到指定数据库 mysql -u root -p td_oa td_oa_20250301.sql说明--default-character-setutf8不加导出文件里的中文在导入时可能乱码恢复前不要把原库直接 DROP建议先恢复到一个新库名验证数据完整后再切换连接配置。二次开发尽量避免改官方表结构优先用扩展表并以业务字段关联 UID这样升级时不会被覆盖。4. 从零新建业务模块目录、菜单、权限、编码四步闭环4.1 目录结构与菜单注册手册第三章给出了标准流程建立模块目录、创建菜单、分配权限、编码测试。在 webroot 下建立模块目录目录名建议全部小写并使用下划线分隔例如webroot/sale_report/。模块内部结构保持轻量目录/文件作用webroot/sale_report/index.php模块入口页面webroot/sale_report/action.php处理表单 POST 请求webroot/sale_report/inc/模块私有函数webroot/sale_report/tpl/页面模板菜单注册写入 sys_menu 表SQL 如下INSERT INTO sys_menu ( MENU_NAME, MENU_URL, MENU_MODEL, MENU_DISABLE, MENU_ORDER ) VALUES ( 销售报表, sale_report/index.php, sale_report, 1, 99 );说明MENU_URL 是相对于 webroot 的访问路径MENU_MODEL 建议填模块名方便后续按模块维度清理菜单MENU_DISABLE1 表示启用填 0 或空时菜单不显示MENU_ORDER 控制排序习惯用 10 的倍数预留插入空间。插入后刷新 OA 首页左侧菜单即可看到新入口。4.2 菜单权限分配菜单创建出来还不行sys_menu 只决定菜单项是否存在真正控制谁能看到菜单的是权限表。手册 3.3 节把这一步单列是有原因的菜单注册漏掉权限分配会出现「路径能直接访问、但菜单里看不到入口」的情况。INSERT INTO sys_priv (PRIV_NAME, PRIV_FILE, PRIV_NO) VALUES (销售报表, sale_report/index.php, 118);说明PRIV_NO 是权限编号不能和已有编号冲突插入前先查当前最大值SELECT MAX(PRIV_NO) FROM sys_priv;。PRIV_FILE 与 sys_menu 中的 MENU_URL 保持一致这样授权管理页面能正确关联到功能点。手动插完权限后还要到 OA 后台的角色管理里勾选对应权限并保存纯 SQL 插入不会自动同步角色。4.3 系统变量与会话数据auth.inc.php 校验完登录态后会写入一组会话变量。二次开发模块直接读取即可不需要重新查用户表。最常用的是这三个$_SESSION[LOGIN_UID]登录人内部 UID、$_SESSION[LOGIN_NAME]登录人姓名、$_SESSION[LOGIN_USER_ID]登录账号。数据库连接来自 conn.php 提供的全局变量$connection业务代码中不要自己再连一次。4.4 一个可运行的模块入口代码把上述流程合并成一个带权限校验、数据库查询和简单输出的模块入口?php // 模块入口先做登录校验再取会话变量和数据库连接 include_once(inc/auth.inc.php); include_once(inc/conn.php); include_once(inc/utility_org.php); $uid (int)$_SESSION[LOGIN_UID]; $uname GetUserNameById($uid); if ($uid 0) { exit(未登录或会话已过期); } $sql SELECT RUN_ID, RUN_NAME, BEGIN_TIME FROM flow_run WHERE RUN_USER {$uid} AND STATUS 1 ORDER BY BEGIN_TIME DESC; $result mysql_query($sql, $connection); ? !DOCTYPE html html head meta charsetutf-8 title我的在办流程/title /head body h3?php echo $uname; ? 的在办流程/h3 ?php if ($result mysql_num_rows($result) 0) { ? table border1 cellpadding6 cellspacing0 trth流程名称/thth发起时间/th/tr ?php while ($row mysql_fetch_array($result)) { ? trtd?php echo $row[RUN_NAME]; ?/tdtd?php echo $row[BEGIN_TIME]; ?/td/tr ?php } ? /table ?php } else { ? p当前没有在办流程。/p ?php } ? /body /html逻辑说明include 顺序固定为先 auth 再 connauth 校验通过后才能安全使用$_SESSIONconn 提供$connection连接。查询条件RUN_USER {$uid}使用内部 UID 关联而不是登录账号字符串这是第三章强调的关联规则。mysql_num_rows先判断结果集非空再渲染表格避免空数据时输出空表头。如果实际库中 flow_run 的字段名不一致先执行DESCRIBE flow_run;核对字段再调整 SQL。注意mysql_*函数在 PHP 7 环境下不存在老代码迁移时先确认php -v。常见做法是写一个 compatibility.php 封装函数在 conn.php 之后 include让老手册代码在新 PHP 版本下继续运行。5. 内置类库与函数库挑出真正常用的类与 utility_org 系列5.1 TD、PortalData、ExcelReader 的适用场景手册第四章列了三组类库二次开发时按场景选择不需要全部背下来。TD 类是通用工具类成员函数覆盖字符串处理、数组操作等基础能力适合在处理业务数据前的预处理阶段使用。PortalData 类是门户数据封装用于读取首页门户模块的数据源做门户定制时常见做法是实例化 PortalData 后读取指定门户模块配置再渲染到自定义页面上。需要提醒的是门户数据有缓存改完后台配置前台不刷新先清缓存再排查业务代码。ExcelReader 类专门用于读取 Excel 文件。V8.1 场景下多用于月度数据导入比如把线下的审批表格批量导入系统生成表单数据。使用时要确认手册对应版本支持的 Excel 格式早期版本对 .xlsx 支持有限遇到读取为空时先把文件另存为 .xls 再试。5.2 Workflow 相关类TworkForm 与 TworkRun工作流是通达OA 的核心手册单独给了两页给 TworkForm 和 TworkRun。TworkForm 负责表单定义读取表单设计器里配置的字段运行时生成字段 HTMLTworkRun 负责流程实例的运行控制包括发起流程、步骤流转、归档等核心操作。二次开发做流程定制时不建议直接改这两个类的源码。常见做法是在自己的模块中实例化 TworkRun调用它的公开方法完成流程操作再在业务层补充自己的校验逻辑。这样官方升级包覆盖类文件时不会冲掉你的代码。如果需要对流程按钮做扩展优先考虑在流程步骤的附加操作里挂自定义页面而不是改类库。5.3 utility_org.php组织架构取数函数utility_org.php 是组织架构相关函数集做报表和审批流必用。频繁用到的是这几个函数作用GetUserNameById按 UID 返回用户姓名GetDeptNameById按部门 ID 返回部门名称GetPrivNameById按权限 ID 返回权限名称GetUidByOther按其他业务字段反查 UIDdept_long_name返回部门完整路径如「总公司/华东事业部/研发部」get_code_name按代码类别和代码值返回字典名称调用示例include_once(inc/utility_org.php); include_once(inc/utility_all.php); $uid 5; $deptId 12; $uname GetUserNameById($uid); $deptPath dept_long_name($deptId); $sexName get_code_name(SEX, 1); if ($uname) { add_log(sale_report, 查询用户 {$uname} 的部门路径: {$deptPath}); } echo 用户{$uname}部门{$deptPath}性别{$sexName};说明dept_long_name比单层部门名实用得多它能把部门树的全路径拼接出来报表分组和审批流通知文案里直接可用get_code_name用于把代码值转成可读文本不要在 SQL 里把性别、状态这类字段写死成中文否则字典调整后要改代码add_log来自 utility_all.php第一个参数是模块标识第二个参数是日志内容二次开发模块里记录关键操作日志是排查问题的基本手段。5.4 日志、系统参数与短信函数utility_all.php 中的get_sys_para和set_sys_para用于读写系统参数表适合在模块里存自定义配置项。参数存取有缓存修改后要等待缓存刷新或主动触发刷新。短信相关的 send_sms 负责站内短消息send_mobile_sms 负责手机短信。send_mobile_sms 能不能真的发出去取决于系统里是否配置了短信网关二次开发联调时先确认网关配置再排查代码。短信内容拼接时如果包含用户输入的字符串注意转义防止短信接口被注入。6. 附件处理实战utility_file.php 的上传、落盘、签名与老数据兼容上传附件是 OA 二次开发绕不开的环节。utility_file.php 里有 46 个函数看起来多核心链路只有一条判断可上传、生成子目录、落盘、登记附件记录、生成访问 URL。?php include_once(inc/auth.inc.php); include_once(inc/conn.php); include_once(inc/utility_file.php); $module sale_report; $uid (int)$_SESSION[LOGIN_UID]; if (!is_uploadable($module)) { exit(当前模块不允许上传附件); } $subDir attach_sub_dir($module); $realPath attach_real_path($module, $subDir); if (!is_dir($realPath)) { mkdir($realPath, 0755, true); } if (is_array($_FILES[file])) { $attachInfo add_attach($module, $_FILES[file], $subDir, $uid); if ($attachInfo[attach_id]) { $url attach_url($module, $attachInfo[attach_id], $subDir); echo 附件地址{$url}; } else { echo 上传失败; } } ?说明is_uploadable判断模块是否开启上传attach_sub_dir按模块生成相对子目录如 2025/03attach_real_path把相对路径转换成服务器物理路径文件操作都用物理路径URL 生成用逻辑路径两者不要混用add_attach负责把临时文件移动到附件目录并写入附件记录表返回数据中的 attach_id 是后续所有操作的凭证attach_url用 attach_id 生成可访问的下载 URL直接输出给前端作为链接地址。带签名的附件 URL 是防止越权访问的关键。直接暴露原始 URL 时用户只要改了 URL 中的附件 ID 就可能遍历他人附件。使用签名函数可以规避$signKey attach_sign_key($module, $attachInfo[attach_id]); $signedUrl attach_url($module, $attachInfo[attach_id], $subDir) . sign . $signKey;说明attach_sign_key根据模块、附件 ID 和密钥生成签名参数URL 中带上 sign 后服务端校验通过才允许下载。网盘场景使用attach_sign_key_netdisk机制一致密钥参数独立管理。老数据迁移是另一个高频场景。upload_old、add_attach_old、delete_attach_old用于兼容早期版本的附件路径规则。老库迁移到新版本时旧记录的附件路径与新版目录规则不一致直接用新版函数读取会报文件不存在。常见做法是先写脚本把旧附件复制到新目录更新附件表路径字段再切换到新函数。迁移过程中不要混用新旧函数读写同一条记录会出现文件存在但记录查不到的问题。排错按这个顺序来上传报错先查 php.ini 的upload_max_filesize和post_max_sizeadd_attach返回 false 查 webroot 下附件目录的属主和写权限下载 404 先确认attach_real_path路径下文件真实存在再排查签名是否过期超过 2GB 的大文件建议拆包或走外部对象存储8.1 的附件表结构和文件系统处理超大文件都不稳定。本文还有配套的精品资源点击获取