跳转至

组织架构查询与维护

本章是「用户 / 部门 / 角色」相关脚本的依赖底座。权限与可见性控制 权限与可见性控制、流程审批人动态计算 流程审批人动态计算、视图过滤与列控制 视图过滤与列控制 都会频繁调用本章的取用户、取部门、取角色 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>),具体见 流程审批人动态计算 - 隐藏 / 只读:Booleantrue = 生效

示例代码

(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 = BB.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 / 参数批量导入:循环 getParameterAsArrayqueryByDSName 拉 Excel 临时表,逐行 doCreate。 - 创建后回写 IDdept.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()