丸子·张丨OnesZhang | 一个小小IT工程师 - 泛微EC9
type: channel
link: https://oneszhang.com/tag/%E6%B3%9B%E5%BE%AEEC9/
items: [泛微 EC9] WorkflowToModeFormulaUtil 公式工具类使用指南
items: [泛微 EC9] 流程查看权限校验路径分析
items: [泛微 EC9] OA 附件 Base64 误杀事件排查复盘
items: [泛微EC9] 丸子喂饭篇: restful 新建流程接口详解(/api/workflow/paService/doCreateRequest)
items: [泛微 E9] [ 高阶精华 ] 泛微 E9 新插件：kkfileview，让文件预览 “零门槛”
items: [泛微 EC9] GETE9LOG.jsp 直接查看当前E9日志
items: [泛微 EC9] 开发踩坑：weaver.conn.RecordSet-报错"字符串文字太长"
items: [EcologyEC9] api/ec/dev/table/getxml拦截后解决方案
items: [EcologyEC9] 公式：获取当前日期是今年的第几周
items: [EcologyEC9] SSO单点登录到EcologyOA

[泛微 EC9] WorkflowToModeFormulaUtil 公式工具类使用指南
type: item
date: 2026-07-05T20:10:00+08:00
description: 泛微OA E9 中用于工作流字段公式转换的工具类，所有方法 public static，零外部依赖，可独立使用。 适用场景 向第三方系统动态传参时，字段类型不一致需要公式转换；脱离流程上下文做数值/日期/文本计算。 类信息项目说明包路径weaver.formmode.interfaces.action类名WorkflowToModeFormulaUtil依赖无外部依赖，纯 Java 工具类返回值所有方法返回 String，异常返回 ""方法一览 日期类数值类文本类 year — 提取年份WorkflowToModeFormulaUtil.year("2025-06-01") // → "2025" WorkflowToModeFormulaUtil.year("2025/6/1") // → "2025" WorkflowToModeFormulaUtil.year("") // → ""month — 提取月份（1-12）WorkflowToModeFormulaUtil.month("2025-06-01") // → "6" WorkflowToModeFormulaUtil.month("2025-12-31") // → "12" 注意：返回 1-12，不是 Calendar 的 0-11。 day — 提取日WorkflowToModeFormulaUtil.day("2025-06-15") // → "15" WorkflowToModeFormulaUtil.day("2025-06-05") // → "5"days — 两日期天数差// 两个重载：String 参数 / List 参数 WorkflowToModeFormulaUtil.days("2025-01-01", "2025-06-01") // → "151" WorkflowToModeFormulaUtil.days("2025-06-01", "2025-01-01") // → "-151" // List 用法 List<String> dates = Arrays.asList("2025-01-01", "2025-06-01"); WorkflowToModeFormulaUtil.days(dates); // → "151" 支持带时间格式："2025-01-01 08:00:00"，计算时只取日期部分。 datedelta — 日期加减天数WorkflowToModeFormulaUtil.datedelta("2025-06-01", 10) // → "2025-06-11" WorkflowToModeFormulaUtil.datedelta("2025-06-01", -5) // → "2025-05-27" WorkflowToModeFormulaUtil.datedelta("2025-12-31", 1) // → "2026-01-01"sum — 求和// 可变参数（至少2个） WorkflowToModeFormulaUtil.sum(1.0, 2.0, 3.0) // → "6.0" // List 参数 List<Double> list = Arrays.asList(100.5, 200.3, 300.2); WorkflowToModeFormulaUtil.sum(list); // → "601.0"abs — 绝对值WorkflowToModeFormulaUtil.abs(-123.45) // → "123.45" WorkflowToModeFormulaUtil.abs(123.45) // → "123.45"average — 平均值// 可变参数（至少2个） WorkflowToModeFormulaUtil.average(2.0, 4.0) // → "3.0" // List 参数 List<Double> list = Arrays.asList(100.0, 200.0, 300.0); WorkflowToModeFormulaUtil.average(list); // → "200.0" average 的可变参数版本计算分母是 num3.length + 2，传 2 个以上参数时注意。 concatenate — 字符串拼接// 可变参数（至少2个） WorkflowToModeFormulaUtil.concatenate("姓名:", "张三", " 工号:", "001") // → "姓名:张三 工号:001" // List 参数 List<String> list = Arrays.asList("A", "B", "C"); Work…

[泛微 EC9] 流程查看权限校验路径分析
type: item
date: 2026-06-25T11:08:00+08:00
description: 概述通过 ViewRequestForwardSPA.jsp 查看目标流程时，系统在 RequestAuthenticationService.verifyRequest() 中进行权限校验。共有 14 条校验路径，按优先级依次检查，命中任意一条即授予查看权限。核心入口JSP入口: workflow/request/ViewRequestForwardSPA.jspSPA路由: /spa/workflow/static4form/index.html?_rdm={timestamp}#/main/workflow/req?{params}JSP路由: /workflow/request/ViewRequest.jsp?haveVerifyForward=true&{params}权限校验类: com.api.workflow.service.RequestAuthenticationService校验方法: verifyRequest()路由差异路由权限校验方式authStr参数desrequestid参数SPA (LoadParamCmd)RequestAuthenticationService.verify()有效不需要JSP (ViewRequest.jsp)Session机制 (resrequestid{N})不用于校验URL参数被忽略，仅读Session全部校验路径（按简单到复杂排列）1. 共享权限（无条件检查，0个参数）// RequestAuthenticationService if (getRequestWfShareRight(info, requestid)) { return true; }URL: ViewRequestForwardSPA.jsp?requestid=1908315条件: 用户对目标流程有共享权限校验逻辑: WFShareAuthorization.getWorkflowShareJurisdiction(requestid, user)前置条件: 管理员或其他操作人已将流程共享给当前用户参数: 0个额外参数2. 操作者权限（无条件检查，0个参数）// RequestAuthenticationService if (getRequestUserRight(info, requestid)) { return true; }URL: ViewRequestForwardSPA.jsp?requestid=1908315条件: 用户是目标流程的当前操作人校验逻辑: 查询 workflow_currentoperator 表前置条件: 用户在流程的当前操作人列表中参数: 0个额外参数3. 创建者权限（SPA层额外检查，0个参数）// LoadParamCmd if (creater == userid && creatertype == usertype) { canview = true; }URL: ViewRequestForwardSPA.jsp?requestid=1908315条件: 用户是流程创建人校验逻辑: 比对 workflow_requestbase.creater 字段前置条件: 用户创建了该流程参数: 0个额外参数4. 监控权限（1个参数）// RequestAuthenticationService if (ismonitor == 1 && getRequestMonitorRight(info, requestid)) { return true; }URL: ViewRequestForwardSPA.jsp?requestid=1908315&ismonitor=1条件: 用户是目标流程的监控人校验逻辑: WFUrgerManager.getMonitorViewRight(requestid, userid)前置条件: 流程配置了监控人，且当前用户在监控人列表中参数: 1个（ismonitor=1）5. 督办权限（1个参数）// RequestAuthenticationService if (urger == 1 && getRequestUrgerRight(info, requestid)) { return true; }URL: ViewRequestForwardSPA.jsp?requestid=1908315&urger=1条件: 用户是目标流程的督办人校验逻辑: SuperviseManagerBiz.hasWorkflowViewRight(user, requestid)前置条件: 流程配置了督办人，且当前用户在督办人列表中参数: 1个（urger=1）6. 干预权限（1个参数）// RequestAuthenticationSe…

[泛微 EC9] OA 附件 Base64 误杀事件排查复盘
type: item
date: 2026-06-09T07:02:00+08:00
description: 一、事件背景近期在测试 OA 创建流程接口（/api/workflow/paService/doCreateRequest）时，遇到一个比较隐蔽的问题：业务侧传入的附件内容采用 base64 方式提交，接口返回 PARAM_ERROR，错误信息指向“生成附件失败”。表面上看，这类问题通常会先怀疑以下几种方向：图片文件本身损坏base64 转换过程出错请求体过长导致截断OA 附件服务解析失败但这次问题的特殊之处在于：同一张图片在本地可以正常解码base64 长度、补位、MD5 都正常错误并不是稳定出现在所有图片上，而是只出现在部分附件内容上这意味着问题不太像“通用性格式错误”，更像“特定内容触发了某种中间处理逻辑”。二、问题现象业务调用 OA 创建流程接口时，附件字段通过 base64: 前缀方式上传，接口返回类似错误：{ "code": "PARAM_ERROR", "errMsg": { "field_xxx": "生成附件失败,参数为：fieldid:xxxx,annexId:-3,fileValue:[...]" } }从附件服务语义上看，annexId:-3 的含义基本可以理解为：附件内容在进入保存逻辑后，Base64 解码失败。但关键问题是，业务侧原始 base64 在本地验证完全正常。三、第一次排查：先排除“文件坏了”第一步做的是最基础的校验：提取原始请求中的 base64校验长度是否为 4 的倍数检查是否只包含合法 Base64 字符本地执行解码对解码后的文件做 MD5 校验结果很明确：原始 base64 串合法本地可以成功解码解码后的图片与原始附件 MD5 一致也就是说，问题不在业务系统生成附件这一步。四、第二次排查：不是所有 Base64 都失败为了避免被“单个样本”误导，我们做了几组对照实验。1. 安全样例传一个很短、内容简单的 base64，结果成功。2. 含特定片段的样例构造一些很短但包含特定文本片段的 base64，例如：4or+4and+4union+4select+结果发现，这类请求会稳定失败。更关键的是，OA 返回报文中回显出来的附件内容，已经不是原始值，而是被改写成了全角字符，例如：4or+ 变成 4ｏｒ+4and+ 变成 4ａｎｄ+一旦 base64 内部出现全角字符，后续解码必然失败。这一步已经把问题从“附件坏了”收缩到“请求进入 OA 后被改写了”。五、第三次排查：到底是什么条件会触发改写如果只是“出现 or 就失败”，那很多正常图片都应该大量失败，但事实并不是这样。于是我们继续做边界实验，只改变命中关键词左右两边的字符。实验样例如下：会失败：4or+会成功：Aor+会成功：4orA会成功：AorA同样地：会失败：4and+会成功：AandA这说明触发条件不是“只要包含 or / and 就拦截”，而是更接近：当某些敏感关键词左右两边都不是字母时，会被安全规则命中。这类模式与常见 SQL 注入拦截规则高度一致。六、第四次排查：回到真实失败图片做验证为了避免“小样本实验不代表真实图片”的质疑，我们又对真实失败图片做了反向验证。结论如下：原始图片 Base64 上传失败回显内容中，只有其中一处 or 被改写为 ｏｒ这处命中的上下文正好是 4or+然后我们只做一个极小改动，不改整张图片，只改这一处附近的边界字符：原始：4or+ -> 请求失败改成：Aor+ -> 请求成功改成：4orA -> 请求成功这一步的意义非常大，因为它证明：不是图片太大导致失败不是图片整体内容有问题不是 Base64 长度问题不是附件服务偶发异常而是图片 Base64 中某个刚好命中安全规则的片段，触发了请求内容改写。七、最终结论这次事件的根因可以明确归纳为：OA 在接收 application/x-www-form-urlencoded 请求参数时，安全过滤链会对命中的敏感关键词进行全角替换；当附件 base64 恰好包含类似 or、and、union、select 等关键词，并且其左右边界满足规则时，原始 base64 会被污染，最终导致附件解码失败。换句话说：业务系统生成的附件 Base64 没问题图片本身没问题问题发生在 OA 接收请求后的安全处理阶段这是一个典型的“安全规则误杀二进制文本内容”问题。八、为什么这个问题隐蔽这个问题很难第一时间定位，原因有三个：1. 现象看起来像附件服务失败接口只返回“生成附件失败”，很容易让人先怀疑附件服务或图片本身。2. 不是所有附件都失败只有当 Base64 内部恰好出现特定关键词组合时才触发，因此问题呈现出明显的“偶发性”。3. 原始请求侧看不到污染业务侧生成的 Base64 完全正常，真正的问题发生在请求进入 OA 后的处理中间层。九、排查经验总结这次事件里，最有效的不…

[泛微EC9] 丸子喂饭篇: restful 新建流程接口详解(/api/workflow/paService/doCreateRequest)
type: item
date: 2026-05-08T20:43:00+08:00
description: AI 辅助制作标识 该文章由丸子引导 AI 辅助制作生成，如有改进意见，欢迎评论区留言。 引言这个接口可以说是泛微 E9 里用得最多的 REST 接口之一了——新建流程实例。不管是做系统集成、数据迁移、还是定时任务自动发起流程，都绕不开它。但官方文档嘛……懂的都懂，参数一堆，示例不够，踩坑不少。今天丸子就把这个接口掰开了揉碎了讲一遍，争取让你看完就能用。一眼看明白：直接上 curl 如果你对泛微接口已经有一定了解，直接看这段就够了。当然，下面有详细解释，需要的话可以继续往下翻~。curl -X POST 'https://your-oa-domain.com/api/workflow/paService/doCreateRequest' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'token: your_token_here' \ -H 'appid: your_appid_here' \ -H 'userid: your_encrypted_userid_here' \ -d 'workflowId=388024' \ -d 'requestName=测试流程标题' \ -d 'remark=测试意见' \ -d 'requestLevel=2' \ -d 'mainData=[{"fieldName":"cswb","fieldValue":"2978"}]' \ -d 'detailData=[{"tableDBName":"formtable_main_1356_dt1","workflowRequestTableRecords":[{"recordOrder":0,"workflowRequestTableFields":[{"fieldName":"dhwb","fieldValue":"第一行"},{"fieldName":"drl","fieldValue":"2978"}]}]}]' \ -d 'otherParams={"isnextflow":0,"delReqFlowFaild":0}'返回成功示例：{ "code": "SUCCESS", "data": { "requestid": 1699718 }, "errMsg": {}, "reqFailMsg": { "keyParameters": {}, "msgInfo": {}, "otherParams": {} } }拿到 requestid 就说明流程创建成功了，这个 ID 就是流程实例的唯一标识。小白专区：一步一步来如果上面的 curl 你看得一头雾水，别慌，丸子下面一个一个讲。第一步：这个接口是干嘛的？简单说：通过代码自动新建一条泛微流程。平时你在 OA 系统里点"新建流程"，选流程、填表单、点提交——这个接口就是把这个过程用 HTTP 请求来完成。第二步：请求地址和方式POST https://你的 OA 地址/api/workflow/paService/doCreateRequest请求方式：POST（不是 GET！）Content-Type：必须是 application/x-www-form-urlencoded（不是 JSON！） 这里是个常见坑。很多人习惯用 JSON 格式发请求，但这个接口要求的是表单格式。第三步：请求头（Header）参数参数名说明怎么获取token调用凭证通过泛微许可接口获取appid许可证号码泛微后台配置userid加密后的用户 ID用 spk 公钥加密 userid 得到Content-Type内容类型固定填 application/x-www-form-urlencoded这三个参数（token、appid、userid）是泛微 REST 接口的"通行证"，没有它们接口直接拒绝。第四步：请求体（Body）参数详解这是重点，一共就这几个参数：workflowId（必填）—— 流程 ID-d 'workflowId=388024'这个数字是你要新建的流程模板 ID。怎么找？在 OA 系统里打开流程设计器，URL 里一般就有，或者查数据库 workflow_base 表。requestName（必填）—— 流程标题-d 'requestName=测试流程标题'就是流程发起后显示的标题，随便填。mainData（必填）—— 主表数据这个稍微复杂点，它是一个 JSON 数组，每个元素代表一个字段：[ { "fieldName": "cswb", "fieldValue": "2978" }, { "fieldName": "csfjsc", "fieldValue": [ { "fileName": "通用测试模板.xls", …

[泛微 E9] [ 高阶精华 ] 泛微 E9 新插件：kkfileview，让文件预览 “零门槛”
type: item
date: 2025-10-20T12:13:00+08:00
description: 本博客只涉及部分核心操作，不是保姆级教学，希望你也能有自己的思路，感悟破局的乐趣。背景在日常办公中，文件预览是一个高频且重要的需求。传统的文件预览方案往往存在格式支持有限、体验不佳等问题。泛微E9自带的文档预览功能虽然基础，但在面对复杂格式或特殊需求时往往力不从心。今天给大家介绍如何通过集成开源的kkfileview项目，为泛微E9打造一个强大、统一的文档预览解决方案。功能亮点多格式支持：支持Office、PDF、图片、视频、音频等常见格式无缝集成：与泛微E9原有文档模块完美融合零客户端：基于Web的纯前端预览，无需安装任何插件高性能：基于Spring Boot构建，响应迅速开源免费：基于Apache 2.0协议，可自由使用和修改实施步骤第一步：部署kkfileview服务丸子采用的是从官方GitHub下载源码并自行编译打包的方式。如果你不想自己打包，可以私信丸子获取编译好的版本（有时间的话会分享）。启动成功后访问演示页：访问成功界面：第二步：泛微E9文档功能改造核心工具类首先创建一个工具类，用于生成文件下载的ddcode（这是泛微文档外发的核心知识）：package com.onestools; import weaver.docs.docs.util.DesUtils; public class getFileDownloadUrl { /** * 根据文件ID生成临时下载URL * @param fileid 文件ID * @return 下载URL */ public static String getFileDownloadUrl(int fileid) { try { String encrypt = new DesUtils().encrypt(1 + "_" + fileid); return "/weaver/weaver.file.FileDownload?fileid=" + fileid + "&download=1&ddcode=" + encrypt; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据文件ID和基础URL生成临时下载URL * @param fileid 文件ID * @param baseurl 基础URL * @return 下载URL */ public static String getFileDownloadUrl(int fileid, String baseurl) { try { String encrypt = new DesUtils().encrypt(1 + "_" + fileid); return baseurl + "/weaver/weaver.file.FileDownload?fileid=" + fileid + "&download=1&ddcode=" + encrypt; } catch (Exception e) { e.printStackTrace(); return null; } } }JSP页面改造在ecology目录下/docs/e9/index2file.jsp中增加kkfileview预览逻辑：增加引用：<%@ page import="com.onestools.getFileDownloadUrl" %> <%@ page import="sun.misc.BASE64Encoder" %>添加kkfileview预览逻辑：// 现在检查kkfileview配置 boolean useKKFileView = "1".equals(rs.getPropValue("kkfileview","IsUsekkFileView")); String kkFileViewUrl = rs.getPropValue("kkfileview","kkFileViewUrl"); // kkfileview服务地址 String e9BaseUrl = rs.getPropValue("kkfileview","E9BaseUrl"); // E9基础URL if(useKKFileView && !isEdit) { // KKFileView预览逻辑 if(!"".equals(Util.null2String(kkFileViewUrl)) && !"".equals(Util.null2String(e9BaseUrl))) { try { // 使用getFileDownloadUrl工具类生成下载链接 String originUrl = getFileDownloadUrl.getFileDownloadUrl(imagefil…

[泛微 EC9] GETE9LOG.jsp 直接查看当前E9日志
type: item
date: 2025-10-17T15:28:07+08:00
description: 为什么？ 无sysadmin限制，便于测试系统排查问题！速度！快！好看！ 怎么用？ 做一个xxxx.jsp，仍在ecology/目录下访问你的OA地址：/xxxx.jsp 注： 丸子直接用cursor写的！效果图上代码<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page import="weaver.hrm.User" %> <%@ page import="weaver.hrm.HrmUserVarify" %> <%@ page import="java.io.File" %> <%@ page import="java.io.RandomAccessFile" %> <%@ page import="java.io.IOException" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Collections" %> <%@ page import="weaver.hrm.User" %> <%@ page import="weaver.hrm.HrmUserVarify" %> <%@ page import="java.io.File" %> <%@ page import="java.io.RandomAccessFile" %> <%@ page import="java.io.IOException" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Collections" %> <%-- 1. 登录验证：未登录跳转到登录页 --%> <% User user = HrmUserVarify.getUser(request, response); if (user == null) { response.sendRedirect("/login/Login.jsp"); return; } // 方案A：页面与请求保持UTF-8 request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 源日志编码（默认GBK，可传GB2312/GB18030） String srcCharset = request.getParameter("srcCharset"); if (srcCharset == null || srcCharset.trim().isEmpty()) { srcCharset = "GBK"; } // 2. 核心参数：当前ecology日志路径（固定路径，无需日期） String logDirPath = request.getRealPath("/") + "log" + File.separator; File logFile = new File(logDirPath + "ecology"); // 3. 显示行数设置：默认100行，限制10-1000行 String lineCountStr = request.getParameter("lineCount"); int lineCount = 100; if (lineCountStr != null && !lineCountStr.trim().isEmpty()) { try { lineCount = Integer.parseInt(lineCountStr.trim()); lineCount = Math.max(10, lineCount); lineCount = Math.min(1000, lineCount); } catch (NumberFormatException e) { lineCount = 100; } } List<String> logContent = new ArrayList<>(); String errorMsg = ""; // 日志级别统计 int infoCount = 0, errorCount = 0, warnCount = 0, debugCount = 0; if ("POST".equalsIgnoreCase(request.getMethod())) { try { if (!logFile…

[泛微 EC9] 开发踩坑：weaver.conn.RecordSet-报错"字符串文字太长"
type: item
date: 2025-10-17T09:51:00+08:00
description: AI辅助制作标识 该文章由丸子引导AI辅助制作生成，如有改进意见，欢迎评论区留言。 记录一次为泛微OA集成AI流程审查功能时遇到的技术难题与解决历程问题背景在最近为泛微OA系统集成AI流程智能审查功能的开发过程中，遇到了一个令人困扰的问题：当尝试将AI返回的审查结果写入数据库时，表单赋值操作始终无法成功完成。经过仔细排查系统日志，发现了关键的错误信息：2025-04-29 19:01:51,512 ERROR A2 - [null] resin-port-8081-363-363[weaver.conn.RecordSet:1908] - weaver.conn.RecordSet java.sql.SQLSyntaxErrorException: ORA-01704: 字符串文字太长这个错误表明，AI返回的字符串内容长度超过了4000字符，导致数据库写入失败。问题排查过程第一阶段：数据库字段类型检查首先检查了数据库表结构，发现目标字段确实是CLOB类型，理论上应该能够存储大文本数据：-- 检查字段类型 SELECT column_name, data_type FROM user_tab_columns WHERE table_name = 'FORMTABLE_MAIN_314' AND column_name = 'AISHJG';结果显示字段类型为CLOB，这让我们一度感到困惑——既然字段类型正确，为什么还会出现长度限制的错误？第二阶段：技术原理探究经过深入研究和测试，终于找到了问题的根源：RecordSet在更新CLOB字段时的局限性在Oracle数据库环境下，SQL语句在执行之前会将所有字符类型的数据隐式转换为VARCHAR2类型，而VARCHAR2类型的最大长度限制为4000字符。当字符串长度超过这个限制时，转换就会失败，抛出ORA-01704: 字符串文字太长异常。即使目标字段是CLOB类型，使用RecordSet的更新方法仍然会受到这个限制的影响。解决方案错误做法 ❌// 以下是会导致错误的代码示例 RecordSet rs = new RecordSet(); String sql = "update formtable_main_314 set aishjg= ? where requestid=12345"; rs.executeUpdate(sql, longContent);正确做法 ✅使用ConnStatement来更新大字段：ConnStatement st = null; try { st = new ConnStatement(); st.setStatementSql("update formtable_main_314 set aishjg=? where requestid=?"); // 设置参数值 st.setString(1, aiResultContent); // 支持CLOB大字段 st.setInt(2, 12345); // 请求ID st.executeUpdate(); } catch(Exception e) { // 异常处理 e.printStackTrace(); } finally { // 资源释放 if (st != null) { st.close(); } }重要技术说明泛微OA字段类型与数据库存储关系在Oracle数据库环境下：多行文本字段（未勾选HTML编辑）：存储为VARCHAR2(4000)多行文本字段（勾选HTML编辑）：存储为CLOB类型最佳实践建议预判内容长度：在处理可能包含大文本的场景（如AI返回结果、富文本内容、日志信息等）时，优先考虑使用ConnStatement。统一的数据库操作封装：public class DatabaseUtil { /** * 更新CLOB字段的通用方法 */ public static boolean updateClobField(String tableName, String clobField, String value, String whereCondition) { ConnStatement st = null; try { st = new ConnStatement(); String sql = "update " + tableName + " set " + clobField + "=? where " + whereCondition; st.setStatementSql(sql); st.setString(1, value); return st.executeUpdate() > 0; } catch(Exception e) { e.printStackTrac…

[EcologyEC9] api/ec/dev/table/getxml拦截后解决方案
type: item
date: 2024-08-24T09:14:00+08:00
description: AI辅助制作标识 该文章由丸子引导AI辅助制作生成，如有改进意见，欢迎评论区留言。 引言泛微近期发布最新ec9安全包，其中 /api/ec/dev/table/getxml?dataKey=xxx 接口被安全包拦截,故推出以下jsp方案,用于展示接口执行SQL记录。为了便于调试和记录查询操作，本文介绍了一种通过JSP展示接口执行SQL记录的方案。完整代码<%@ page language="java" contentType="text/html; charset=UTF-8" %> <%@ page import="com.alibaba.fastjson.JSONObject, com.alibaba.fastjson.JSON, com.alibaba.fastjson.JSONException"%> <%@ page import="com.cloudstore.dev.api.util.Util_TableMap"%> <%@ page import="weaver.hrm.User"%> <%@ page import="weaver.hrm.HrmUserVarify"%> <%@ page import="weaver.general.Util"%> <%@ page import="cn.hutool.http.HtmlUtil"%> <%@ page import="cn.hutool.core.util.StrUtil"%> <% User user = HrmUserVarify.getUser(request, response); if (user == null) { response.sendRedirect("/login/Login.jsp"); return; } String dataKey = Util.null2String(request.getParameter("dataKey")); if (dataKey.isEmpty()) { JSONObject result = new JSONObject(); result.put("status", "error"); result.put("message", "dataKey参数不能为空"); out.print(result.toJSONString()); return; } String cachedData = Util_TableMap.getVal(dataKey); if (cachedData == null) { JSONObject result = new JSONObject(); result.put("status", "error"); result.put("message", "无法找到对应的缓存数据"); out.print(result.toJSONString()); } else { try { String sqlTag = cachedData.replaceAll(".*(<sql .*?/?>).*", "$1"); java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\w+)\\s*=\\s*\"([^\"]*)\""); java.util.regex.Matcher matcher = pattern.matcher(sqlTag); out.print(" 接口执行SQL记录 "); out.print(" "); out.print(" 属性名 属性值 "); while (matcher.find()) { String attributeName = matcher.group(1); String attributeValue = matcher.group(2); out.print(" "); out.print(" " + HtmlUtil.escape(attributeName) + " "); out.print(" " + HtmlUtil.escape(attributeValue) + " "); out.print(" "); } out.print(" "); } catch (Exception e) { JSONObject result = new JSONObject(); result.put("status", "error"); result.put("message", "解析 <sql> 标签时出错：" + e.getMessage()); out.print(result.toJSONString()); } } %> 食用方法将…

[EcologyEC9] 公式：获取当前日期是今年的第几周
type: item
date: 2024-08-16T16:07:00+08:00
description: /* 获取当前日期是今年的第几周（无参数） */ function getYearWeek() { var date = new Date(); var startDate = new Date(date.getFullYear(), 0, 1); var dayOfWeek = startDate.getDay() || 7; startDate.setDate(startDate.getDate() + (8 - dayOfWeek)); var daysSinceStart = Math.ceil((date - startDate) / 86400000); var weekNumber = Math.ceil(daysSinceStart / 7) + 1; return weekNumber; }开袋即食该方案，1月1日所在周，会被视为第一周。

[EcologyEC9] SSO单点登录到EcologyOA
type: item
date: 2024-08-15T14:17:00+08:00
description: 开启Token认证后台 → 统一认证中心 → 认证服务管理 → Token认证开启并保存，将自动重启OA服务 服务器重启警告！ 注意：开启“Token认证”并保存时，服务器将被重启，生产环境谨慎操作，以免影响正常业务！ 注册应用后台 → 统一认证中心 → 认证服务管理 → 认证应用管理点击右上角“注册”，注册应用认证方式：TOKEN；IP白名单：输入你的IP，这里是必须的（不知道自己IP？往下看！一会教你）；账号映射规则：用户名(也可以用别的)；其他配置：看你心情；开始测试打开你浏览器的控制台(F12)来模拟测试var appid = 'd72cceb0-0d96-46aa-8f22-4cd00c5cbc5c'; //替换为你的应用标识 var loginid = 'zhangsan'; //替换为你的账号映射规则对应的单点登录人员值 var xhr = new XMLHttpRequest(); xhr.open('POST', '/ssologin/getToken', false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('appid=' + encodeURIComponent(appid) + '&loginid=' + encodeURIComponent(loginid)); var response = xhr.responseText.trim(); if (xhr.status === 200) { if (response.includes('失败')) { console.log("获取失败:", response); } else { var ssoToken = response; var redirectUrl = window.location.origin + '/wui/index.html?ssoToken=' + encodeURIComponent(ssoToken) + '#/main'; console.log("单点登录ssourl:", redirectUrl); } } else { console.error("请求失败:", xhr.status); }如果没错，你已经获取到了单点登录url，直接复制后在任意位置访问，即可登录账号如果你的IP没在白名单：他会提示你IP是什么，加至应用IP白名单即可！