组织架构查询与维护¶
本章是「用户 / 部门 / 角色」相关脚本的依赖底座。权限与可见性控制 权限与可见性控制、流程审批人动态计算 流程审批人动态计算、视图过滤与列控制 视图过滤与列控制 都会频繁调用本章的取用户、取部门、取角色 API。建议先把「取用户对象及属性」与「部门名 ↔ ID 互转与部门代码」过一遍,再按需跳读其它小节。
场景清单¶
| 场景 | 主要落点 | 主要素材 |
|---|---|---|
| 取用户对象及属性 | 值脚本 / 审批人脚本 / 隐藏只读 | get-user-object、usercenter |
| 部门名 ↔ ID 互转、部门代码 | 值脚本 / 计算 / 视图过滤 | department-name-to-id-or-id-to-name、get-department-code |
| 取所有上下级部门(递归) | 值脚本 / 视图过滤 | get-all-parent-and-child-departments |
| 取所有 2 级部门作下拉选项 | 选项脚本 | get-all-2nd-level-departments |
| 取当前用户扩展字段 | 值脚本 / 计算 / 隐藏只读 | get-current-user-extension-field |
| 用户默认部门获取与设置 | 值脚本 / 操作前后置 | default-department |
| 批量建部门 / 根部门 / 角色 | 操作后置 / 定时任务 / API | create-department、create-1st-level-department、create-role |
| 密码加密 / 验证 / 重置 | 操作前置 / 定时任务 / API | user-password-encrypt-decrypt |
通用约定:本章示例默认运行在**有用户上下文**的表单/视图/操作脚本中(可用
getWebUser())。**定时任务**无getWebUser(),需用getApplication()与指定域 ID 替代,详见 定时任务、API 与 Widget。任何脚本里若出现.equals()/.length()/ 裸java.util.X,先翻 GraalVM 差异 再继续。
取用户对象及属性¶
业务目标:根据 ID / 登录名 / 用户名 / 当前登录用户拿到用户对象(UserVO),读取其基本信息(名称、登录名、邮箱、电话)与组织信息(部门、上级、角色、域),用于值脚本回填、审批人计算、权限判断等。
写在哪儿:
- 表单字段值脚本:表单设计器 → 字段 → 值脚本(FORM:FIELD_VALUE)
- 流程审批人脚本:流程节点 → 审批人脚本(WORKFLOW:NODE_ACTOR_LIST,详见 流程审批人动态计算)
- 字段隐藏 / 只读脚本:表单设计器 → 字段 → 隐藏 / 只读脚本(详见 权限与可见性控制)
触发时机与上下文:
- 值脚本:表单打开或字段重算时;可读 getCurrentDocument()、getItemValueAsString("字段名")
- 审批人脚本:流程到达该节点时;可读 _flowType 等流程参数
- getWebUser() 在以上场景都可用;定时任务中不可用
返回值契约:
- 值脚本:字符串 / 数字 / JSON 字符串,赋给当前字段
- 审批人脚本:用户 ID 字符串、用户对象或其集合(Collection<UserVO>),具体见 流程审批人动态计算
- 隐藏 / 只读:Boolean,true = 生效
示例代码:
(function () {
// 1. 取当前登录用户(最常用)
var user = getWebUser();
if (user == null) {
return "";
}
// 2. 也可按 ID / 登录名 / 用户名取
// var byId = getUserById("用户ID");
// var byLoginno = getUserByLoginno("zhangsan");
// var byName = getUserByName("张三");
// 或使用 USERCENTER 命名空间(推荐用于复杂查询)
// var u2 = USERCENTER.getUserById("用户ID");
// 3. 读基本属性
var info = {
id: user.getId(),
name: user.getName(),
loginno: user.getLoginno(),
email: user.getEmail() || "",
phone: user.getPhone() || "",
domainId: user.getDomainid()
};
// 4. 读组织属性(部门 / 上级 / 角色)
var dept = user.getDepartment();
info.departmentId = dept != null ? dept.getId() : "";
info.departmentName = dept != null ? dept.getName() : "";
var superior = user.getSuperior();
info.superiorId = superior != null ? superior.getId() : "";
info.superiorName = superior != null ? superior.getName() : "";
var roles = user.getRoles(); // java.util.Collection,用 .size() / .get(i)
info.roles = [];
if (roles != null && roles.size() > 0) {
for (var i = 0; i < roles.size(); i++) {
var r = roles.get(i);
info.roles.push({ id: r.getId(), name: r.getName() });
}
}
return JSON.stringify(info);
})();
代码说明:
- getWebUser() / getUserById(id) / getUserByLoginno(loginno) / getUserByName(name) —— 当前用户与按多键取用户的全局函数。
- USERCENTER.getUserById(userid) / USERCENTER.getUserByLoginno(loginno) —— USERCENTER 命名空间的等价 API,特别适合按角色 / 部门取用户群体的复杂查询,参考 iscript-guid/api → usercenter。
- 用户对象的 getId() / getName() / getLoginno() / getEmail() / getPhone() / getDomainid() / getDepartment() / getSuperior() / getRoles() —— 用户对象成员方法,参考 iscript-guid/api → curruser。
- getRoles() 返回 java.util.Collection:长度用 .size()、元素用 .get(i)(不是 JS 数组的 .length),详见 GraalVM 差异 - Java List vs JS 数组。
变体与扩展:
- 审批人计算:流程节点审批人脚本中按角色 / 部门 / 上级算审批人集合 —— 见 流程审批人动态计算。
- 按部门 / 角色批量取用户:USERCENTER.getUsersByDptId(deptId)、USERCENTER.getUsersByRoleId(roleId)、USERCENTER.getUsersByDptIdAndRoleId(deptId, roleId),参考 iscript-guid/api → usercenter。
- 取上级链(递归):循环 user.getSuperior() 直到 null,可用于"逐级审批"。
- SQL 查询用户:queryBySQL("SELECT ... FROM t_user WHERE ...") —— 注意跨域过滤 domainid,并防 SQL 注入。
常见坑:
- ❌ roles.length —— getRoles() 返回 Java Collection,应该用 roles.size(),详见 GraalVM 差异。
- ❌ "admin".equals(user.getLoginno()) —— GraalVM JS 字符串字面量没有 .equals 方法;改为 user.getLoginno() === "admin"。
- ❌ 不判空直接 user.getDepartment().getId() —— 部门 / 上级 / 角色都可能为 null,必须先判空。
- ⚠️ 跨域数据:SQL 查 t_user / t_role 时务必带 domainid 条件,否则会查出其它域的数据。
- ⚠️ 定时任务无 getWebUser():定时任务脚本里没有用户上下文,应通过 getApplication() 与指定域 ID 工作(参见 定时任务、API 与 Widget)。
部门名 ↔ ID 互转与部门代码¶
业务目标:在表单 / 视图中根据部门名查 ID(数据关联、按部门过滤),或根据 ID 查名称(展示),或取 / 查部门代码(code,常用于系统集成与稳定引用)。
写在哪儿:
- 表单字段值脚本(FORM:FIELD_VALUE):根据一个字段的部门名 / ID 联动填另一个字段
- 视图过滤脚本(VIEW:SQL_FILTER / VIEW:DQL_FILTER,详见 视图过滤与列控制):把用户输入的部门名翻译成 ID 加进 SQL
- 计算脚本、操作前后置
触发时机与上下文:
- 值脚本:表单字段重算时;可读 getCurrentDocument() 字段
- 视图过滤:视图查询前;可读 _selects、查询头参数(参见 视图过滤与列控制)
返回值契约:
- 值脚本:返回字符串(部门 ID / 名称 / 代码)
- 视图过滤:返回 SQL 片段,如 "departmentid = '...'"
示例代码:
(function () {
// A. 部门名 → 部门 ID(通过部门对象,推荐)
var deptByName = getDepartmentByName("研发中心");
var deptId = deptByName != null ? deptByName.getId() : "";
// B. 部门 ID → 部门名 / 部门代码
var dept = getDepartmentById(deptId);
var deptName = dept != null ? dept.getName() : "";
var deptCode = dept != null ? dept.getCode() : "";
// C. 当前用户所在部门的代码
var user = getWebUser();
var myDept = user != null ? user.getDepartment() : null;
var myDeptCode = myDept != null ? myDept.getCode() : "";
// D. 按部门代码反查 ID(系统集成常用,部门代码通常更稳定)
var domainId = user != null ? user.getDomainid() : "";
var sql =
"SELECT id FROM t_department WHERE code = '" + deptCode +
"' AND domainid = '" + domainId + "'";
var row = findBySQL(sql);
var idByCode = row != null ? row.getId() : "";
return JSON.stringify({
deptId: deptId,
deptName: deptName,
deptCode: deptCode,
idByCode: idByCode
});
})();
代码说明:
- getDepartmentByName(name) / getDepartmentById(id) —— 通过部门对象互转,**推荐**用法,避免手写 SQL。
- department.getId() / .getName() / .getCode() / .getParentid() —— 部门对象成员方法,参考 iscript-guid/api → usercenter。
- findBySQL(sql) —— 单条记录查询;多记录用 queryBySQL(sql),参考 iscript-guid/api → database。
- USERCENTER.getDeptIdByNameAndLevel(name, level) —— 按部门等级 + 名称精确取 ID(同名部门跨层级时用),参考 iscript-guid/api → usercenter。
变体与扩展:
- 批量互转:循环 getDepartmentById/Name 构建双向映射表 {id→name, name→id}。
- 部门代码作稳定键:跨环境(开发 / 测试 / 生产)部署时,部门 ID 会变但 code 通常稳定,用 code 作集成键更安全。
- 当前用户默认部门代码:getWebUser().getDefaultDepartment().getCode()(先判空),详见「用户默认部门获取与设置」。
常见坑:
- ⚠️ 重名部门:同一域内部门名可能重复(不同上级下),getDepartmentByName 在重名时只返回其中之一;精确场景用 USERCENTER.getDeptIdByNameAndLevel(name, level) 或"上级 ID + 名称"双重定位。
- ⚠️ SQL 注入:示例为简明直接拼字符串;生产环境若 deptCode 来自用户输入,务必参数化或清洗单引号。
- ❌ deptCode.equals("RD") —— 用 deptCode === "RD"。
- ⚠️ 跨域:SQL 查 t_department 务必带 domainid 过滤。
取所有上下级部门(递归)¶
业务目标:以一个部门为起点,向上取所有祖先(直到根)、向下取所有子孙(直到叶子),用于:部门树展示、按部门层级聚合数据、"我及所有下级部门的数据"权限范围过滤。
写在哪儿:
- 表单字段值脚本(FORM:FIELD_VALUE):填部门路径"集团 / 事业群 / 部门"
- 视图过滤脚本(VIEW:SQL_FILTER,视图过滤与列控制):把"我及所有下级部门"展开为 IN ('id1','id2',...)
- 计算脚本、操作前后置
触发时机与上下文:表单 / 视图计算时;可读 getCurrentDocument()、getWebUser()。
返回值契约:
- 值脚本:JSON 字符串(数组)或路径串 "集团 / 事业群 / 部门"
- 视图过滤:返回 SQL 片段,如 "departmentid IN ('id1','id2')"
示例代码:
(function () {
var startDeptId = "部门ID"; // 实战可来自字段:getItemValueAsString("部门")
var domainId = getWebUser().getDomainid();
// A. 向上递归取所有祖先(循环写法,避免递归栈深)
function getAllParents(deptId) {
var parents = [];
var currentId = deptId;
var guard = 0; // 防止脏数据循环引用
while (guard++ < 100) {
var d = getDepartmentById(currentId);
if (d == null) break;
var pid = d.getParentid();
if (pid == null || pid.trim().length === 0) break; // 根部门
var parent = getDepartmentById(pid);
if (parent == null) break;
parents.push({ id: parent.getId(), name: parent.getName() });
currentId = pid;
}
return parents;
}
// B. 向下递归取所有子孙
function getAllChildren(deptId) {
var children = [];
var sql =
"SELECT id, name FROM t_department WHERE parentid = '" + deptId +
"' AND domainid = '" + domainId + "'";
var rows = queryBySQL(sql);
if (rows != null && rows.size() > 0) {
var it = rows.iterator();
while (it.hasNext()) {
var row = it.next();
var childId = row.getId();
children.push({ id: childId, name: row.getItemValueAsString("name") });
var grand = getAllChildren(childId); // 递归
for (var i = 0; i < grand.length; i++) {
children.push(grand[i]);
}
}
}
return children;
}
var parents = getAllParents(startDeptId);
var children = getAllChildren(startDeptId);
// 拼接"我及所有下级部门 ID 列表"(典型视图过滤场景)
var allIds = [startDeptId];
for (var k = 0; k < children.length; k++) {
allIds.push(children[k].id);
}
var quoted = [];
for (var j = 0; j < allIds.length; j++) {
quoted.push("'" + allIds[j] + "'");
}
return JSON.stringify({
parents: parents,
children: children,
filterSql: "departmentid IN (" + quoted.join(",") + ")"
});
})();
代码说明:
- getDepartmentById(id) + department.getParentid() —— 向上遍历的唯一可靠方式。
- queryBySQL(sql) —— 向下查询子部门(parentid = ?),返回 java.util.List,遍历用 .iterator(),参考 iscript-guid/api → database。
- USERCENTER.getDepartmentsByParent(parentId) —— 等价的"取直接下级部门"API,返回 Collection<DepartmentVO>,参考 iscript-guid/api → usercenter。
- 字符串判空用 pid.trim().length === 0(属性,不是方法 .length()),详见 GraalVM 差异。
变体与扩展:
- 取部门路径串:从祖先到当前部门用数组 unshift 拼接,join(" / ")。
- 取层级深度:向上数到根的步数。
- 判断是否祖先 / 子孙:用于权限判断"用户 A 是否在 B 的下属部门"。
- 性能优化:一次 queryBySQL 拉**全部部门**到内存,用 JS 对象 {parentId: [child1, child2, ...]} 索引后递归遍历,避免 N+1 查询。
常见坑:
- ⚠️ 循环引用:脏数据下 A.parentid = B 且 B.parentid = A 会死循环;建议加迭代次数上限(示例中的 guard)。
- ⚠️ 递归性能:层级深 / 部门多时 N+1 查询很慢;改用一次性拉全量 + 内存索引。
- ❌ rows.length —— queryBySQL 返回 Java List,应用 .size()。
- ⚠️ 跨域:递归 SQL 务必带 domainid。
取所有 2 级部门作下拉选项¶
业务目标:把"所有 2 级部门"(即直接隶属于 1 级 / 根部门的部门)作为下拉 / 单选 / 多选控件的选项;典型用于"分公司 → 部门"两级联动下拉的根级选项。
写在哪儿:表单字段选项脚本(FORM:FIELD_OPTION,Label "选项脚本")。
触发时机与上下文:下拉控件渲染时;可读 getCurrentDocument()、getWebUser()。
返回值契约:返回 Options 对象(createOptions() 构造),或字符串 "值:文本;值:文本",由平台消费为下拉项。
示例代码:
(function () {
var domainId = getWebUser().getDomainid();
// JOIN 查询:parentid 在 1 级部门集合中的所有部门(即 2 级部门)
var sql =
"SELECT d2.id, d2.name, d1.name AS parentName " +
"FROM t_department d2 " +
"INNER JOIN t_department d1 ON d2.parentid = d1.id " +
"WHERE (d1.parentid IS NULL OR d1.parentid = '') " +
"AND d2.domainid = '" + domainId + "' " +
"AND d1.domainid = '" + domainId + "' " +
"ORDER BY d1.name, d2.name";
var rows = queryBySQL(sql);
var opts = createOptions();
if (rows != null && rows.size() > 0) {
var it = rows.iterator();
while (it.hasNext()) {
var row = it.next();
var id = row.getId();
var name = row.getItemValueAsString("name");
var parentName = row.getItemValueAsString("parentName");
// 父部门名作分组前缀,便于在重名时区分
opts.add(id, parentName + " / " + name);
}
}
return opts;
})();
代码说明:
- createOptions() —— 平台内置构造器,返回 Options 对象;.add(value, text) 添加项。返回值契约见 默认包装与返回值契约通则。
- queryBySQL(sql) —— JOIN 查询;返回 Java List,遍历用 .iterator(),参考 iscript-guid/api → database。
- USERCENTER.getDepartmentByLevel(level) —— 等价"按等级取部门"API,level=2 即取所有 2 级部门(无需手写 JOIN),参考 iscript-guid/api → usercenter。
变体与扩展:
- 字符串形式返回:直接 return "id1:名称1;id2:名称2";,平台亦接受。
- 联动:父下拉选中后触发重算子下拉,子下拉选项脚本里读父字段后用 getDepartmentsByParent(parentId) 取下一层。
- 包含用户数量:在 SQL 中 LEFT JOIN t_user 计数。
常见坑:
- ⚠️ 1 级部门 parentid 同时存在 NULL 与空串:SQL 中务必用 (parentid IS NULL OR parentid = '') 双判断。
- ❌ opts.push(...) —— Options 不是 JS 数组,应用 .add(value, text)。
- ⚠️ 跨域:JOIN 两边都要带 domainid,避免拉到其它域的部门。
取当前用户扩展字段¶
业务目标:读取用户对象上配置的自定义扩展字段(如"用户类型""岗位""成本中心"),用于个性化默认值、权限分流、数据过滤。
写在哪儿:表单字段值脚本(FORM:FIELD_VALUE)、隐藏 / 只读脚本(详见 权限与可见性控制)、视图过滤(详见 视图过滤与列控制)。
触发时机与上下文:表单 / 视图渲染时;可读 getWebUser()、getCurrentDocument()。
返回值契约:值脚本返回字符串(扩展字段的值)。
示例代码:
(function () {
var user = getWebUser();
if (user == null) {
return "";
}
// 单个扩展字段
var userType = user.getExtensionField("用户类型");
userType = userType != null ? userType : "";
// 多个扩展字段
var fields = ["岗位", "成本中心", "入职日期"];
var bag = {};
for (var i = 0; i < fields.length; i++) {
var v = user.getExtensionField(fields[i]);
bag[fields[i]] = v != null ? v : "";
}
// 用扩展字段做权限分流示例(决定默认部门)
var defaultDepartment;
if (userType === "管理员") {
defaultDepartment = "总部";
} else if (userType === "普通用户") {
defaultDepartment = "分支机构";
} else {
var d = user.getDepartment();
defaultDepartment = d != null ? d.getName() : "";
}
return defaultDepartment;
})();
代码说明:
- user.getExtensionField(fieldName) —— 读用户扩展字段(在用户管理后台配置);字段名严格区分大小写。
- getWebUser() —— 当前登录用户对象,参考 iscript-guid/api → curruser。
- user.getDepartment() —— 用户的部门对象(用于回退兜底)。
变体与扩展:
- 空值兜底:getExtensionField(x) || "" 或 v != null ? v : "",避免 null 拼接到字符串里出现 "null"。
- 批量导出:循环一组字段名构建 JSON。
- SQL 兜底:极少数版本扩展字段存在独立扩展表,可用 SQL 查(表名按实际环境验证)。
常见坑:
- ⚠️ 字段名拼写:扩展字段名严格区分大小写;必须与后台配置的名称完全一致才能取到值。
- ⚠️ null 拼接:"" + null 在 JS 里得到字符串 "null",记得先判空。
- ❌ "VIP".equals(userType) —— 用 userType === "VIP"。
用户默认部门获取与设置¶
业务目标:读取或设置用户的默认部门。默认部门用于在创建文档、流程等场景中自动填充;用户可能跨部门,默认部门可与主部门不同。
写在哪儿:
- 表单字段值脚本(FORM:FIELD_VALUE):新建文档时自动填部门字段
- 操作前置(FORM:ACTION_BEFORE / VIEW:COLUMN_OPERATE_BEFORE):操作前根据规则切换默认部门
- 操作后置 / 定时任务:批量维护默认部门
触发时机与上下文:表单 / 视图 / 操作触发;可读 getWebUser()、getCurrentDocument()。
返回值契约: - 获取:返回部门 ID 字符串(或部门对象,取决于场景) - 设置:返回操作结果消息(成功 / 失败);副作用为主
示例代码(获取默认部门并回填字段):
(function () {
// A. 获取当前用户的默认部门(兜底回退到主部门)
var user = getWebUser();
var defaultDept = user != null ? user.getDefaultDepartment() : null;
if (defaultDept == null) {
defaultDept = user != null ? user.getDepartment() : null; // 回退到主部门
}
var deptId = defaultDept != null ? defaultDept.getId() : "";
// 把默认部门填到当前文档"部门"字段(值脚本典型用法)
var doc = getCurrentDocument();
if (doc != null && deptId !== "") {
doc.findItem("部门").setValue(deptId);
doc.findItem("部门名称").setValue(defaultDept.getName());
}
return deptId;
})();
设置默认部门(操作后置 / 定时任务示例):
(function () {
var userId = "用户ID";
var newDeptId = "部门ID";
var user = getUserById(userId);
var dept = getDepartmentById(newDeptId);
if (user == null) {
return "用户不存在";
}
if (dept == null) {
return "部门不存在";
}
user.setDefaultDepartment(dept);
// 落库需调用用户 Process 的 doUpdate(具体 Process 名称按部署版本)
// 例:var UserProcess = getUserProcess();
// UserProcess.doUpdate(user);
return "默认部门设置成功: " + dept.getName();
})();
代码说明:
- user.getDefaultDepartment() / user.setDefaultDepartment(dept) / user.getDepartment() —— 用户对象的部门读写方法,参考 iscript-guid/api → curruser。
- getUserById(id) / getDepartmentById(id) —— 按 ID 取对象,参考 iscript-guid/api → usercenter。
- doc.findItem("字段名").setValue(value) —— 修改当前文档字段值,参考 iscript-guid/functions → curdoc-functions。
- 落库:调用 Process 的 doUpdate 才会持久化;不同版本 Process 获取方式略有差异,落地前请在测试环境验证。
变体与扩展:
- 批量设置:循环 userIds,逐个 setDefaultDepartment + doUpdate。
- 根据规则切换:按用户角色 (getRoles()) 决定默认部门。
- 校验有效性:取默认部门后用 getDepartmentById(deptId) 反查是否仍存在;脏数据下可能引用了已删除的部门。
常见坑:
- ⚠️ 未落库:只调 setDefaultDepartment 但忘了 doUpdate,重启或刷新后设置丢失。
- ⚠️ 部门已删:历史数据中 defaultDepartmentId 可能指向已删除的部门,使用前用 getDepartmentById 校验。
- ⚠️ 默认部门 vs 主部门:两者可能不同;写脚本时明确你要哪个,避免错填。
批量建部门 / 根部门 / 角色¶
业务目标:通过脚本批量创建组织对象 —— 初始化组织架构、从外部系统同步、数据迁移。三者 API 形态同构(getXxxProcess() → doNew() → setXxx() → doCreate(obj)),故合并为一张差异表 + 一段共用模式 + 一个完整示例。
写在哪儿:操作后置(FORM:ACTION_AFTER / VIEW:COLUMN_OPERATE_AFTER)、定时任务(TASK:SCRIPT,定时任务、API 与 Widget)、API 响应脚本(API:RESPONSE)。
触发时机与上下文:
- 操作后置:表单 / 视图操作提交后触发
- 定时任务:调度时间到达时触发;无用户上下文,getWebUser() 不可用,须从 getApplication() 取域 ID
- API:外部调用 API 时触发
返回值契约:副作用为主(建 / 写数据);返回操作结果消息或 JSON。
三类对象的差异表:
| 创建对象 | Process 获取 | 必备 setter | 关键差异 |
|---|---|---|---|
| 普通部门(指定上级) | getDepartmentProcess() |
setName / setCode / setParentid(上级ID) / setDomainid |
parentid 为上级部门 ID;上级必须先存在 |
| 1 级 / 根部门 | getDepartmentProcess() |
setName / setCode / setParentid("") / setDomainid |
parentid 必须为空串 ""(**不能**是 null) |
| 角色 | getRoleProcess() |
setName / setDescription / setDomainid |
无 parentid;权限需创建后另行分配 |
通用模式:
(function () {
var domainId = getWebUser().getDomainid(); // 定时任务改用 getApplication()/参数传入
// 1. 取 Process
var DeptProcess = getDepartmentProcess();
// 2. 新建对象
var dept = DeptProcess.doNew();
// 3. 设属性
dept.setName("研发中心");
dept.setCode("RD");
dept.setParentid(""); // 空串 = 1 级 / 根部门
dept.setDomainid(domainId);
// 4. 落库
DeptProcess.doCreate(dept);
return "创建成功: " + dept.getName() + " (ID: " + dept.getId() + ")";
})();
完整示例:批量创建多个 1 级部门:
(function () {
var domainId = getWebUser().getDomainid();
var DeptProcess = getDepartmentProcess();
var toCreate = [
{ name: "华东大区", code: "EAST" },
{ name: "华南大区", code: "SOUTH" },
{ name: "华北大区", code: "NORTH" }
];
var ok = 0;
var fail = 0;
var messages = [];
for (var i = 0; i < toCreate.length; i++) {
var item = toCreate[i];
try {
// 重复检查(同域同名)
var exist = findBySQL(
"SELECT id FROM t_department WHERE name = '" + item.name +
"' AND domainid = '" + domainId + "'"
);
if (exist != null) {
messages.push("已存在: " + item.name);
fail++;
continue;
}
var d = DeptProcess.doNew();
d.setName(item.name);
d.setCode(item.code);
d.setParentid(""); // 1 级部门
d.setDomainid(domainId);
DeptProcess.doCreate(d);
messages.push("成功: " + item.name + " -> " + d.getId());
ok++;
} catch (e) {
messages.push("失败: " + item.name + " - " + e);
fail++;
}
}
return "成功 " + ok + " 条,失败 " + fail + " 条\n" + messages.join("\n");
})();
代码说明:
- getDepartmentProcess() / getRoleProcess() —— 平台内置 Process 获取函数。
- Process.doNew() / doCreate(obj) / doUpdate(obj) —— 通用 Process CRUD;新建对象 → 设属性 → 创建 / 更新。
- findBySQL(sql) —— 单条记录存在性检查,参考 iscript-guid/api → database。
- 部门 / 角色对象的 setter(setName / setCode / setParentid / setDomainid / setDescription)参考 iscript-guid/api → usercenter 的 VO 字段定义。
变体与扩展:
- 从 Excel / 参数批量导入:循环 getParameterAsArray 或 queryByDSName 拉 Excel 临时表,逐行 doCreate。
- 创建后回写 ID:dept.getId() 可写回源数据 / 日志,便于后续对账。
- 角色批量赋权:角色创建后,权限分配通常需通过角色资源 / 软件资源 API(具体 API 以部署版本为准)。
常见坑:
- ⚠️ 重复创建:脚本重跑会再次插入;务必先 findBySQL 检查同名 / 同代码。
- ⚠️ parentid 空值:1 级部门必须 setParentid("")(空串);写成 null 或不设可能导致部门挂到错误层级。
- ⚠️ 域 ID 必填:所有组织对象都需 setDomainid,否则跨域可见性出问题。
- ⚠️ 事务性:批量创建无显式事务,部分失败时已成功的不会自动回滚;建议记录失败列表供重试。
- ⚠️ 定时任务无 getWebUser():在定时任务中改用 getApplication().getId() 或参数传入域 ID。
- ⚠️ 角色权限:doCreate 只创建角色记录,不会自动赋任何权限。
密码加密 / 验证 / 重置¶
业务目标:使用平台工具对用户密码做加密 / 解密 / 验证,或在用户管理操作中重置密码。绝大多数场景应直接用平台内置的 Security 工具类,与平台登录、用户管理保持一致的加密方式。
写在哪儿:操作前置(FORM:ACTION_BEFORE / VIEW:COLUMN_OPERATE_BEFORE)、操作后置(批量重置)、定时任务(同步外部账号)、API(自助修改密码)。
触发时机与上下文:用户操作 / API 调用 / 定时调度时触发;可读 getWebUser()、getUserById(id)、getCurrentDocument()(视场景)。
返回值契约:返回加密后的密码字符串,或验证结果(Boolean / 消息)。
示例代码(加密 / 验证 / 重置三合一):
(function () {
// 源素材历史写法为 new Packages.cn.myapps.common.util.Security(),
// GraalVM 仍兼容;新代码推荐改写为 Java.type 形式:
// var Security = Java.type('cn.myapps.common.util.Security');
// var security = new Security();
// 两种写法等价。详见 getting-started.md#graalvm-差异。
var SecurityClass = Java.type('cn.myapps.common.util.Security');
var security = new SecurityClass();
// A. 加密密码
var raw = "Hello123!";
var encrypted = security.encryptPassword(raw);
// B. 验证密码(用户输入 vs 存储的密文)
var userId = "用户ID";
var user = getUserById(userId);
var inputPassword = "用户输入的密码";
var stored = user != null ? user.getPassword() : "";
var encryptedInput = security.encryptPassword(inputPassword);
var valid = encryptedInput === stored; // 用 === 比较,不要用 .equals
// C. 重置密码为默认密码
var defaultPwd = "123456";
var newEncrypted = security.encryptPassword(defaultPwd);
// 落库(数据源名按部署版本):
// updateByDSName("obpm",
// "UPDATE t_user SET password = '" + newEncrypted +
// "' WHERE id = '" + userId + "'");
return JSON.stringify({
encrypted: encrypted,
valid: valid,
resetTo: newEncrypted
});
})();
代码说明:
- cn.myapps.common.util.Security —— 平台加密工具类;encryptPassword(p) / decryptPassword(p) 是其常用方法。GraalVM 推荐用 Java.type(...) 取类,详见 GraalVM 差异 - 取 Java 类。
- getUserById(id) + user.getPassword() —— 取存储的加密密码;参考 iscript-guid/api → curruser。
- updateByDSName(dsName, sql) —— 直接执行 SQL 更新;参考 iscript-guid/api → database。
算法选择对照:
| 场景 | 推荐方式 | 备注 |
|---|---|---|
| 平台账号密码 | Security.encryptPassword() |
首选,与平台登录一致 |
| 集成系统自定义哈希 | MD5 / SHA-256(Java.type('java.security.MessageDigest')) |
单向不可逆 |
| 简单编码(非加密) | Base64(Java.type('java.util.Base64')) |
可解码,仅用于传输编码 |
变体与扩展:
- 批量重置:循环 userIds,统一加密默认密码后批量 UPDATE。
- 密码强度校验:用正则 password.matches(".*\\d.*") 等做长度 / 数字 / 字母 / 特殊字符检查(在重置 / 修改前)。
- 生成随机密码:Java.type('java.util.Random') + 字符表。
- 比较两密码:先各自加密再 === 比较(不要明文比)。
常见坑:
- ⚠️ MD5 / SHA 是单向:哈希后无法解密,只能加密后比对;不要把"MD5 加密"当"可解密"用。
- ⚠️ Base64 不是加密:仅是编码,可被解码,不能**当密码保护用。
- ⚠️ **SQL 注入:拼 UPDATE 语句时密码串里若含单引号会破坏 SQL;密码加密后的密文通常无单引号,但仍建议参数化。
- ❌ encryptedInput.equals(stored) —— 用 === 比较,详见 GraalVM 差异。
- ⚠️ 明文密码不要写日志:println 调试时打印明文密码有泄露风险,开发完成后清理。
- ⚠️ 重置后通知用户:批量重置后建议触发消息通知(参见 消息通知与跳转导航),并强制首次登录改密。
相关场景¶
- 权限与可见性控制 —— 用本章的取用户 / 角色 API 判断字段、视图、按钮的隐藏与只读。
- 流程审批人动态计算 —— 本章的取用户、取上级、按角色 / 部门取用户是审批人脚本的核心素材。
- 视图过滤与列控制 —— 本章的部门递归展开(「取所有上下级部门」)、部门名 ↔ ID 互转(「部门名 ↔ ID 互转与部门代码」)是按部门过滤视图的依赖。
- 数据查询、统计与文档批量操作 —— 批量建部门 / 角色(「批量建部门 / 根部门 / 角色」)的循环 + SQL 模式与数据查询、统计与文档批量操作批量文档操作同构。
- 定时任务、API 与 Widget —— 定时任务无用户上下文,组织架构同步类任务需用
getApplication()替代getWebUser()。