简介《通达OA二次开发手册》是一份面向具备编程基础的技术人员的Office Anywhere网络智能办公系统定制化开发指南旨在帮助读者厘清系统架构、掌握扩展开发的关键路径。手册以2015年发布的V8.1版本为例系统讲解开发环境配置、FastCGI进程管理器与PHP解释器参数调整、OfficWeb前端服务器设置、MySQL数据库连接及调优等基础环节并逐个剖析auth.inc.php、header.inc.php、common.inc.php、conn.php等核心文件的作用为实际开发提供明确落点。压缩包内含单个PDF文件大小约188KB内容按章节划分目录完整便于快速检索。目前已有88人学习下载。手册后续部分进一步讲解phpMyAdmin的安装使用与数据库管理策略并以建立模块目录、创建菜单、分配菜单权限、编码测试为主线完整演示新模块的创建流程适合在企业OA二开项目中作为案头参考也适合准备从事协同办公系统定制的开发者系统学习。1. 通达OA二次开发为什么说会配界面不等于会开发通达OA的二次开发说难不难说简单也不简单。很多企业上OA都会走到这一步标准流程跑顺之后业务部门马上提新需求——要加一张统计报表、改一条审批分支、把某个表单字段同步到外部系统。这点事不值得动大版本官方后台又做不了只能自己动手写代码。《通达OA二次开发手册》解决的就是这个断层它把开发环境怎么配、核心文件怎么加载、数据库怎么连、模块怎么挂菜单一次讲清楚。适合谁有一定PHP和MySQL基础、平时负责实施或维护OA、经常被业务追问“能不能改一下”的技术人员。手册是2015年的V8版本界面旧但底层思路到今天依然能打。2. 开发环境与核心文件先把inc目录里四个文件的加载顺序理清2.1 本地开发环境配置OfficeFPM、OfficWeb和PHP各自该调什么手册第一章把环境拆成四块OfficeFPM、OfficWeb、PHP、MySQL。很多人第一次看会懵以为这是四个独立服务其实它们是一条链上的四个环节。OfficWeb负责接收HTTP请求OfficeFPM是FastCGI进程管理器PHP是实际执行代码的解释器MySQL是数据落点。页面请求进来后OfficWeb把请求转交给OfficeFPMOfficeFPM拉起PHP进程PHP执行代码再通过conn.php连接MySQL取数。这里最值得调的是内存限制、错误报告等级和进程数量。进程配置给多了机器内存扛不住给少了并发一高页面就排长队。错误报告等级在上线阶段必须收紧否则PHP警告会直接打在页面上用户看到一段黄字报错其实不算大问题但如果是数据库密码或SQL片段被带出来那就是安全事故。配置项常见值说明注意点OfficeFPM进程数816处理PHP请求的并发能力调大前先看内存余量php.ini memory_limit128M单个PHP进程可用内存做数据导入时要临时调高error_reportingE_ALL开发期全开上线收紧上线改成 E_ALL ~E_NOTICEmax_execution_time30脚本最长执行时间批量任务建议提高到300MySQL wait_timeout28800连接空闲超时短连接场景建议调小强调一下关系OfficWeb和OfficeFPM的通讯配置不一致时页面会报502或504。常见做法是先确认端口和socket路径两边都对得上再检查PHP扩展是否齐全。手册里写的OfficeFPM配置在实际部署中通常对应PHP-CGI或PHP-FPM版本不同参数名会有差异但排查思路一致页面卡死先看OfficeFPM日志SQL慢再看MySQL慢查询日志。PHP版本是这里最大的暗坑比任何参数都容易翻车。这份手册基于V8.1.150425对应PHP 5.x 时代。后来我用PHP 7调试过老的OA模块大量函数直接报致命错误连页面都打不开。所以本地环境最好直接装与OA版本匹配的PHP解释器不要图新。2.2 四个核心文件的职责边界auth、header、common、conn怎么配合手册1.4节把webroot\inc目录下的四个核心文件单独拎出来讲这是新写一个模块页面时必须先摸清的骨架。auth.inc.php负责登录态校验和用户身份解析header.inc.php输出公共头部和样式common.inc.php装全局常量和公共函数conn.php负责创建MySQL连接。文件路径职责新页面的引入策略auth.inc.phpwebroot\inc\会话校验、用户信息初始化必引没它等于裸奔header.inc.phpwebroot\inc\输出公共样式与导航按需引入common.inc.phpwebroot\inc\公共函数、常量定义通常随auth自动加载conn.phpwebroot\inc\创建数据库连接查库页面必用加载顺序是有讲究的。模块页面开头第一行必须是auth.inc.php因为它内部会加载common和conn还会把当前登录用户的信息解析成全局变量。如果先引header再引auth页面可能出现头部已经渲染、权限校验却失败的半截页面。?php // mymodule/list.php – 模块入口页的标准头部 define(MYOA_IN, true); // 标识OA内部请求部分老函数依赖此常量 require_once(inc/auth.inc.php); // 校验登录态并完成系统变量初始化 require_once(inc/header.inc.php); // 输出公共样式和头部导航 // 到这里已可以安全使用 $LOGIN_USER当前登录用户名 echo 当前用户 . $LOGIN_USER; ?逻辑说明auth.inc.php会先检查会话状态未登录直接跳转到登录页。MYOA_IN这个常量在部分老版本里用来过滤外部直接访问写成true表示这是个经过入口的合法请求。header没引只是样式缺失auth没引则整个页面等于把权限校验跳过了。我一般会在写完页面入口后手动在浏览器地址栏输入完整URL测试一次确认它不能绕过登录直接访问这是二次开发最容易被忽略的安全底线。3. 新建一个模块的标准流程从建立目录到分配菜单权限3.1 建立模块目录、创建菜单、分配权限的三步操作手册第三章把创建模块压缩成了四个步骤建目录、建菜单、分配权限、编码测试。看起来简单实际上每一步都有取舍。第一步是在webroot下创建自己的模块目录比如mymodule所有PHP文件都放这里不要塞进别人模块的目录里。目录名全小写、不带空格这能避免Linux环境下的大小写敏感问题。第二步是登录管理员后台进入“系统管理→菜单管理”新增一个菜单项填上模块名称和URL保存后系统会返回一个menu_id。第三步是分配权限在角色管理里勾选新菜单或者调用手册utility_org.php里set_priv_menu_priv这类函数来完成批量授权。如果对后台操作不熟也可以直接用phpMyAdmin往菜单表插数据。需要注意菜单表名在不同版本里有差异先执行SHOW TABLES LIKE %menu%确认一下再做。-- 新增一个菜单项父菜单ID按实际情况调整 INSERT INTO menu (menu_name, menu_url, parent_id, sort_order, is_show) VALUES (我的报表, mymodule/report.php, 1, 99, 1); -- 给角色ID为3的角色分配菜单权限按实际表结构调整表名 INSERT INTO role_menu_priv (role_id, menu_id) VALUES (3, LAST_INSERT_ID());参数说明menu_name是菜单显示名menu_url要写相对webroot的路径parent_id决定菜单位于哪一级目录下sort_order是同一层级里的排列顺序is_show为1表示显示。直接操作数据库有个好处可以一次批量导入几十个菜单项后台手点会疯掉。坏处是表结构记错一个字段名整条SQL就崩了所以先查表结构再动手。菜单建好、权限分配好这还不够。URL直接访问的拦截由第2章的auth.inc.php负责所以菜单权限是“入口可见性”问题auth校验是“访问合法性”问题两层必须同时到位。3.2 写第一个可运行模块拿到当前用户、部门和组织名称多数新模块的第一行有效代码都是读取当前登录用户是谁、属于哪个部门。手册第五章里utility_org.php整批提供了这类函数GetUserNameById、GetDeptNameById、GetPrivNameById入参基本是ID返回值是名称字符串。用一个简单的页面把它们串起来?php define(MYOA_IN, true); require_once(inc/auth.inc.php); require_once(inc/header.inc.php); // utility_org.php 封装了组织架构相关函数 $uid $LOGIN_UID; // 系统变量当前用户UID $name GetUserNameById($uid); // 取用户真实姓名 $deptId GetDeptIdByUid($uid); // 取用户主部门ID $dept GetDeptNameById($deptId); // 取部门名称 ? h3?php echo $name; ?/h3 p所属部门?php echo $dept; ?/p逻辑说明auth.inc.php把登录态解析成$LOGIN_UID、$LOGIN_USER等系统变量后续页面直接引用即可不需要自己从$_SESSION里猜键名。GetUserNameById和GetDeptNameById是手册明确列出的函数GetDeptIdByUid不一定在每个版本都存在如果函数不存在就改成从用户表里查dept_id字段这一步改动很常见。参数说明uid是整型ID不是用户名注意不要把$LOGIN_USER当成uid传给函数。部门层级如果是多级组织GetDeptNameById只返回当前部门名要返回完整路径可以看utility_org.php里dept_long_name这个函数我一般在正式项目里直接用后者省得自己拼层级。4. 避坑指南最容易翻车的五个位置和对应处置4.1 数据库连接与权限校验翻车现场现象一页面报“Too many connections”原因很直接每个页面开头都用mysql_connect新建连接用完整又不关闭前端一有并发连接数瞬间打满数据库上限。手册里其实把TD类当成统一数据入口但很多人图省事直接写连接代码血泪经验就是不要自己造连接轮子。解决方法是把连接做成单例或者直接复用手册自带的数据库访问类。?php // 一个简单的单例连接封装 class Db { private static $conn null; public static function getConn() { if (self::$conn null) { self::$conn mysql_connect(localhost, root, pass); mysql_select_db(oa_db, self::$conn); } return self::$conn; } } $conn Db::getConn(); ?现象二菜单分配了用户却看不到新模块原因后台角色权限和菜单表数据不一致或者角色权限里勾了菜单但没同步system里的权限缓存。最常见的解法是让用户退出重新登录因为OA的菜单权限是在登录时缓存进会话的。如果重新登录还不行就去检查role_menu_priv关联表的数据是否真的写进去了。4.2 附件、路径与PHP版本翻车现场现象三附件上传成功列表里查不到关联数据原因是只调了upload函数把文件存进附件表没有把附件ID和业务表绑定。手册utility_file.php里的add_attach_module就是干这个事的它把module名和attach_id写入附件关联表列表查询才能找得到。?php $attachId upload(file, mymodule, 0); if ($attachId 0) { add_attach_module(mymodule, $attachId, 0, 0); } ?现象四本地用PHP 7/8调试旧模块全报错原因手册基于PHP 5.x很多函数和行为在PHP 7以后变了。解决办法不是改代码去适配新版本而是把本地环境切到与OA配套的PHP版本。不要指望把老系统的代码全部升级到新语法代价太大且容易引入新问题。现象五Windows下开发好的模块部署到Linux路径全乱原因Windows用反斜杠Linux用正斜杠代码里写死路径分隔符就会翻车。正确做法是使用手册里的attach_real_path函数解析附件真实路径。从那以后我所有涉及路径的代码一律不手拼全部交给系统函数处理。5. 内置类库与函数用最小代价搞定取数、附件和门户5.1 TD类与PortalData类门户取数不再猜表结构手册第四章把内置类库分成四块TD类、PortalData类、ExcelReader类、Workflow相关类。TD类是基础封装很多全局方法都挂在它下面实际开发里最常用到的是它统一管理数据库访问和系统设置。PortalData类是门户数据源的读取入口做门户定制时几乎绕不开。我一般会先通过“门户管理→数据源”里配好数据源编码然后在代码里用PortalData类读取。这一段可以替代自己写SQL查表而且能复用OA本身的权限过滤逻辑风险小很多。?php $portal new PortalData(); $rows $portal-getDataByCode(report_sale_daily); // 数据源编码 foreach ($rows as $row) { echo $row[amount] . br/; } ?逻辑说明getDataByCode的入参是数据源编码在系统后台配置返回值是数据行数组。需要提醒的是不同版本方法名可能略有差异编码前先查一下类文件里的真实函数签名不要照抄手册不验证。参数说明数据源编码通常是英文字母加下划线配置错误时PortalData类会返回空数组页面不报错但取不到数调试时可以先把返回值print_r出来看一眼。5.2 utility_file.php附件处理函数的上传、校验、下载闭环附件处理是OA二次开发里最繁琐的一块但手册把常用函数列得很全。upload负责处理上传请求is_uploadable做类型白名单校验attach_url生成下载链接delete_attach删除附件attach_size拿文件大小copy_attach复制附件。其中attach_real_path是排查问题的钥匙它把附件ID转成磁盘真实路径配合文件管理工具直接看落盘结果。函数名入参返回值典型场景upload表单字段名、模块名、业务ID附件ID处理上传请求is_uploadable文件数组true/false上传前类型校验attach_url附件IDURL字符串生成下载地址delete_attach附件IDtrue/false删除附件记录attach_real_path附件ID磁盘路径定位真实文件下面是一个完整的附件上传闭环用is_uploadable先挡掉危险类型再执行上传最后输出下载链接。这个顺序不能反否则恶意文件先进了盘再被校验就晚了。?php if (is_uploadable($_FILES[file])) { $attachId upload(file, mymodule, 0); if ($attachId 0) { echo 下载地址 . attach_url($attachId); } else { echo 上传失败请检查目录写入权限; } } else { echo 不允许的文件类型; } ?参数说明upload第二个参数是模块名用于分组管理附件目录第三个参数是业务ID如果暂时不知道该填什么就传0之后通过add_attach_module补绑。is_uploadable会根据系统设置的文件类型白名单做判断默认情况下office文档、图片、文本都能过脚本文件会被挡掉。实际部署中经常遇到上传到一半失败的情况优先查attachment目录的写入权限和磁盘空间。6. 验证与调试把错误日志打开是省钱省力的第一件事6.1 打开错误日志别让PHP把警告吞掉很多二次开发人员拿到老代码第一件事是改页面我拿到新环境第一件事是改php.ini。把日志开关打开后续能省掉大量和业务部门来回拉扯的时间。开发机可以开display_errors直接看报错正式环境一定要关掉显示、只记录日志。display_errors Off log_errors On error_reporting E_ALL error_log /myoa/logs/php_error.log这样配置后PHP的警告、通知、致命错误全都会落到日志文件里不会在用户页面露出任何痕迹。OA根目录下也有自己的log目录可以配合系统设置里的日志开关使用。6.2 用add_log埋点把“黑匣子”变成可见链路手册utility_all.php里提供了一个add_log函数入参是模块名和日志内容写入系统操作日志。我第一次用它是因为业务人员反馈“点导入按钮没反应”页面不报错、数据没变化完全像个黑匣子。加了日志之后才发现脚本在导入第三行数据时因为字段格式问题中断了。从那以后我养成了一个习惯每个新模块在入口、关键操作、异常分支各埋一行日志交付前先确认日志开关是打开的再写业务代码。这个习惯让我少加了至少一半的班。?php add_log(mymodule_import, 开始导入用户: . $LOGIN_UID . ,文件: . $_FILES[file][name]); // 业务逻辑... add_log(mymodule_import, 导入完成共 . $cnt . 条记录); ?这段代码看起来简单但它的价值在于把用户的操作链路完整记录到了系统日志表里。以后业务再反馈问题直接查这张表就能定位到人、时间、操作内容和结果不需要一遍遍让用户复现。希望帮到你。本文还有配套的精品资源点击获取