定时任务(Task)定义编写指南¶
目标:Agent 直接生成/修改 workspace 定时任务。知识以本文为准。不写原理;不依赖外链。
iScript:写 taskScript / terminateScript 时,先读 iscript-usage(含 GraalVM 差异) → task.md;本文只定调度字段与落盘(无文档/用户上下文)。
术语:实体类 cn.myapps.core.common.model.task.Task;产品「任务 / 定时任务」;设计器「应用-常用工具-任务」。是软件级 iScript 定时调度,**不是**流程待办(TaskInfo)、**不是**流程节点定时审批 Job、**不是**表单名偶然叫 task 的业务单据。
依赖:挂在**软件(Application)**下;软件须 activated=true 且企业域已绑定该软件,job 进程才会真正执行。startupType≠禁止 时由 Quartz(IscriptTaskJob)按 cron/runningTime 触发。
产出物一览¶
| 部分 | 落盘 | 形态 |
|---|---|---|
| 定时任务 | task/{任务名}.task |
单文件;JAXB 根 task;id 为根属性 |
路径相对 storage/workspace。ModelSuffix:TASK_PATH_SUFFIX/TASK_FILE_SUFFIX=task;TASK_FILE_GROUP=/task。
新建最低配置:
- 目录已存在:
{软件}.application/task/ - 写
{name}.task:id、name、parentId=软件 id、type=1、startupType、period、非空taskScript - 按
period填runningTime/rTime/rDate/daysOfWeek/dayOfMonth/interval(见 §3) terminateScript可空 CDATA;常用计数字段置0- 保存到设计器/job 侧后:
startupType决定是否注册 Quartz(纯落盘文件在 job 进程扫描/设计态保存 后才会调度)
约定:
- JAXB;根
task;id在 根属性 - 脚本用 CDATA:
taskScript、terminateScript(及基类description/remark) - 文件名 =
name+.task;同软件task/下 name 不重名(设计态校验「任务名称已存在!」) name勿含/%\(落盘替换为=47/=37/=92)parentId= 软件 id;applicationid基类**无**@XmlElement,落盘常缺;靠路径归属 / API 保存时 set- **勿**放到
module/下;永远在应用级task/ - 布尔以外字段用整型字面量(
type/period/startupType等) runningTime:ISO-8601 或带时区的xs:dateTime串,如2025-10-21T12:10:00.248+08:00
1. 心智模型¶
软件 Application(activated)
└─ task/*.task ← 本文;设计态定义 + iScript
│
▼ 保存 / InitJobScheduler.start(appId)
Quartz:jobName=taskId,jobGroup=applicationId
IscriptTaskJob
├─ 软件未激活 → return
├─ terminateScript 返回 Boolean true → deleteJob
└─ Task.execute() → type=SCRIPT 跑 taskScript
企业域 Domain 管理端「定时任务」
→ 汇总域已绑定软件下各 .task 的运行态(监控/启停入口)
| 概念 | 说明 |
|---|---|
任务内容 taskScript |
触发时执行的 iScript;无 Document / 无 WebUser 业务会话(runner 用空 Document + params) |
终止条件 terminateScript |
每次触发前跑;返回 Boolean true 则 删 Job 并跳过本次 execute |
启动类型 startupType |
手动 / 自动 / 禁止 → 决定是否注册 Job、是否立刻按 cron 挂起 |
重复 period |
映射 Quartz cron 或一次性;决定 UI 要哪些时间字段 |
运行态 state |
设计盘常见 0;设计器 start API 会设 1;不作为「是否调度」主开关(主开关是 startupType) |
| 累计次数 | runtimes/totalRuntimes/executedCount 历史字段;现行 Job **不再回写**文件累加 |
与其它调度区分:
| 对象 | 用途 |
|---|---|
.task / IscriptTaskJob |
应用级定时脚本(本文) |
| 流程定时审批 / 跳过审批 Job | 流程节点,不落 task/*.task |
NotificationJob |
通知类,不同实体 |
T_TRIGGER 等库表 |
历史/其它触发器;现行 Task 文件系统 |
2. Task 属性 → .task XML¶
属性表¶
| 属性 | XML | 类型 | 默认/样例 | 说明 |
|---|---|---|---|---|
id |
根属性 | string | 须生成 | __ + 短 UUID;亦为 Quartz jobName/triggerName |
name |
子元素 | string | — | 必填;文件名;应用内唯一 |
parentId |
子元素 | string | — | = 软件 id |
applicationid |
常不落盘 | string | API 侧 = 软件 id | |
description |
CDATA | string | 可空 | 列表描述 |
remark |
CDATA | string | 基类备注;少用 | |
type |
子元素 | int | 1 |
仅脚本:TASK_TYPE_SCRIPT=1(0x1);UI 只有此项 |
runningTime |
子元素 | dateTime | 调度锚点;由 rDate+rTime 或当前时刻合成(见 §3) |
|
interval |
子元素 | int | 1 |
period 为每分/每时时:间隔分钟数或小时数 |
period |
子元素 | int | — | 重复类型;见 §3 常量表 |
runtimes |
子元素 | int | 0 |
历史「运行次数」;≥0(JSR @Min(0)) |
terminateScript |
CDATA | string | 可空 | 终止条件脚本;返回 Boolean |
taskScript |
CDATA | string | — | 任务正文 iScript |
modifyTime |
子元素 | dateTime | 修改时间;设计器保存刷新 | |
creator |
子元素 | string | 创建人显示名 | |
creatorid |
子元素 | string | 创建人用户 id | |
totalRuntimes |
子元素 | int | 0 |
总运行次数(文件可不更新) |
state |
子元素 | int | 0 |
运行状态标记;start API→1 |
startupType |
子元素 | int | 0 手动 / 1 自动 / 2 禁止 |
|
daysOfWeek |
重复元素 | int | period=32 每周;Calendar:1周日…7周六;可多个 |
|
dayOfMonth |
子元素 | int | 1 |
period=512 每月第几天 1–31;其它 period 样例常仍写 1 |
repeatTimes |
子元素 | int | 0 |
历史「一天中运行次数」;生成可 0 |
frequency |
子元素 | int | 0 |
历史频率;生成可 0 |
executedCount |
子元素 | int | 0 |
历史当日已执行次数;生成可 0 |
rDate |
子元素 | string | yyyy-MM-dd;仅不重复 period=0 用;其它常空串 |
|
rTime |
子元素 | string | HH:mm:ss;日/周/月/不重复必填 |
daysOfWeek XML 形态¶
JAXB @XmlElement + Collection<Integer>,每周勾选时多个同名元素:
空集合:元素可省略。设计器 checkbox label:1=周日 … 7=周六(与 java.util.Calendar.DAY_OF_WEEK 一致)。cron 生成时映射:1→Sun … 7→Sat。
id 生成¶
统一规则:__ + 短 UUID。示例:__MpEzTToulqZtNEFisw6。改 id 等于换 Job 键;勿与其它任务冲突。
设计态校验¶
| 条件 | 文案/效果 |
|---|---|
name 空 |
{*[page.name.notexist]*} |
| 同应用其它任务同名 | 任务名称已存在! |
日/周/月且 rTime 空 |
{*[core.task.choosetime]*} |
不重复且 rDate 空 |
{*[cn.myapps.core.task.select_running_time]*} |
不重复且 rTime 空 |
{*[core.task.choosetime]*} |
每分 interval 不在 0–60(前端) |
提示输入 0–60 |
每时 interval 不在 0–3600(前端) |
提示 |
设计器创建 API 会强制:parentId=applicationId、applicationid=applicationId、新建时 dayOfMonth=1(即使非每月)。手写包应自己保证 parentId 正确。
3. period / startupType / 时间字段¶
startupType(TaskConstants)¶
| 值 | 常量 | 行为 |
|---|---|---|
0 |
STARTUP_TYPE_MANUAL |
注册 Job,addObpmJob(..., startNow=false):按 cron 挂,不立刻 startNow |
1 |
STARTUP_TYPE_AUTO |
注册 Job,默认 startNow=true |
2 |
STARTUP_TYPE_BANNED |
deleteJob;不调度 |
自动运维:job 模块 InitJobScheduler.start(applicationId) 对软件下全部任务按上表注册/删除。
period(重复类型;注意是十六进制常量,十进制原样写入 XML)¶
| 十进制 | 十六进制 | 常量 | UI 文案 | loop | cron 要点 | 必填时间字段 |
|---|---|---|---|---|---|---|
0 |
— | REAPET_TYPE_NOTREAPET / REPEAT_TYPE_NONE |
不重复 | false | ss mm HH dd MM ? yyyy(YEAR,一次性) |
rDate+rTime → 合成 runningTime |
8192 |
0x2000 |
REPEAT_TYPE_IMMEDIATE |
立刻 | false | 无 cron(getCron 返回 null)→ 用当前毫秒触发后删 |
设计器不采 rDate/rTime;runningTime≈now |
34 |
0x22 |
REPEAT_TYPE_DAILY_MINUTES |
每分 | true | 0 0/interval * * * ? |
interval(分钟);runningTime≈now |
546 |
0x222 |
REPEAT_TYPE_DAILY_HOURS |
每时 | true | 0 0 0/interval * * ? |
interval(小时);runningTime≈now |
2 |
0x2 |
REPEAT_TYPE_DAILY |
每日 | true | ss mm HH * * ?(取 runningTime 的时分秒) |
rTime |
32 |
0x20 |
REPEAT_TYPE_WEEKLY |
每周 | true | ss mm HH ? * Sun,Mon,... |
rTime + daysOfWeek |
512 |
0x200 |
REPEAT_TYPE_MONTHLY |
每月 | true | ss mm HH dayOfMonth * ? |
rTime + dayOfMonth |
loop=false(不重复/立刻):TriggerExecutor 执行完后 deleteJob。
setDate 合成规则(设计器/API):
| period | runningTime 算法 |
|---|---|
| 2 / 32 / 512 | 解析 rTime=HH:mm:ss,套到「今天」日历的时分秒 |
| 0 | 解析 rDate++rTime = yyyy-MM-dd HH:mm:ss |
| 34 / 546 / 8192 | runningTime = 当前时刻格式化回解析 |
手写文件:直接写对齐的 runningTime + rTime(及需要的 rDate/daysOfWeek/dayOfMonth/interval)。日/周/月样例常 rDate 为空串。
interval 与前端限制¶
- 仅
period∈{34,546}有意义;其它保存时前端常强制写1 - 每分:建议
1–60 - 每时:建议
1–合理小时数;前端上限校验按 3600
4. 执行与脚本环境¶
触发链¶
- Quartz 触发 →
TriggerExecutor→IscriptTaskJob.execute findById任务;null → deleteJob- 软件 null 或
!activated→ return(不删 Job) terminateScript非空:runner.initBSFManager(null, params, null, null);Boolean true→ deleteJob returntask.execute():目前仅type==TASK_TYPE_SCRIPT(1)JavaScriptFactory.getInstance(applicationid);ParamsTable:sessionid=TaskScriptInfo_yyyy-MM-dd,application=applicationidinitBSFManager(new Document(), params, null, [])— 空文档runner.run(ScriptLabel TYPE.TASK.SCRIPT, taskScript)- finally 关 session/connection
脚本约束(相对表单/视图脚本)¶
| 项 | 定时任务 |
|---|---|
| 环境变量(产品表) | 无(无 doc、无 user、无字段上下文) |
| 返回值 | **无**要求(不消费 return;副作用执行即可) |
getApplication() |
可用(绑定当前软件 id) |
| Document API | 可用 getDocumentProcess() / getDocProcess(appId) 等自行查建文档 |
| 数据源 | queryByDSName / updateByDSName / DB.* / sys_function.DATASOURCENAME |
| 设计态 SQL 服务 | 亦可见 DataSourceDesignTimeService*(文档示例);与运行态 DB.* 场景不同,按环境选用 |
#include |
常见 #include "basic" / "baselibrary" |
| 推荐外壳 | IIFE:(function(){ ... })() |
terminateScript¶
必须能求值为 Boolean;其它类型不触发停止。空 CDATA / 空白 → 跳过终止检查。
最小任务脚本¶
常用模式(样例浓缩)¶
按数据源名改业务表:
(function(){
var sql = "update tlk_xxx set item_status='5' where ...";
updateByDSName("数据源name", sql);
})()
查文档 / 建文档(须自己设 domainid、WebUser):
(function(){
var process = getDocumentProcess();
var formProcess = getFormProcess();
var Form = formProcess.doViewByFormName("表单名", getApplication());
var params = createParamsTable();
var userProcess = getUserProcess();
var userv = userProcess.getUserByLoginno("loginno", domainid);
var webuser = new (Java.type('cn.myapps.core.web.WebUser'))(userv);
var zdoc = process.doNew(Form, webuser, params);
zdoc.setDomainid(domainid);
zdoc.addStringItem("字段名", "值");
process.doCreate(zdoc);
})()
发邮件:
var WebUser = new (Java.type('cn.myapps.core.web.WebUser'))(getUserById(userId));
var EmailUtil = new (Java.type('cn.myapps.core.util.mail.EmailUtil'))(WebUser);
EmailUtil.sendEmailBySystemUser(email, subject, html);
设计态 DS 执行(文档用例,定时场景偶用):
var process = Packages.cn.myapps.designtime.common.service.DesignTimeServiceFactory
.resolve("cn.myapps.designtime.datasource.service.DataSourceDesignTimeService");
process.queryInsert(dsName, sql, getApplication());
const DataSourceDesignTimeServiceImpl =
new Packages.cn.myapps.designtime.datasource.service.DataSourceDesignTimeServiceImpl();
DataSourceDesignTimeServiceImpl.createOrUpdate(dataSourceName, sql, applicationId);
注意:定时任务脚本里若硬编码 domainid,多域部署会漏域;样例有「扫 t_domain + 每域执行」写法。
5. 完整 XML 模板¶
每日自动(最常见)¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<task id="__YourTaskIdHere0001">
<name>每日示例任务</name>
<parentId>软件UUID</parentId>
<description></description>
<type>1</type>
<runningTime>2026-07-15T08:00:00.000+08:00</runningTime>
<interval>1</interval>
<period>2</period>
<runtimes>0</runtimes>
<terminateScript><![CDATA[]]></terminateScript>
<taskScript><![CDATA[(function () {
println("daily task");
})()]]></taskScript>
<modifyTime>2026-07-15T08:00:00.000+08:00</modifyTime>
<creator>admin</creator>
<creatorid>用户UUID</creatorid>
<totalRuntimes>0</totalRuntimes>
<state>0</state>
<startupType>1</startupType>
<dayOfMonth>1</dayOfMonth>
<repeatTimes>0</repeatTimes>
<frequency>0</frequency>
<executedCount>0</executedCount>
<rDate></rDate>
<rTime>08:00:00</rTime>
</task>
每周(工作日 09:30)¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<task id="__WeeklyTaskId0000001">
<name>每周工作日任务</name>
<parentId>软件UUID</parentId>
<description></description>
<type>1</type>
<runningTime>2026-07-15T09:30:00.000+08:00</runningTime>
<interval>1</interval>
<period>32</period>
<runtimes>0</runtimes>
<terminateScript><![CDATA[]]></terminateScript>
<taskScript><![CDATA[(function () {
println("weekday task");
})()]]></taskScript>
<modifyTime>2026-07-15T09:30:00.000+08:00</modifyTime>
<creator>admin</creator>
<creatorid>用户UUID</creatorid>
<totalRuntimes>0</totalRuntimes>
<state>0</state>
<startupType>1</startupType>
<daysOfWeek>2</daysOfWeek>
<daysOfWeek>3</daysOfWeek>
<daysOfWeek>4</daysOfWeek>
<daysOfWeek>5</daysOfWeek>
<daysOfWeek>6</daysOfWeek>
<dayOfMonth>1</dayOfMonth>
<repeatTimes>0</repeatTimes>
<frequency>0</frequency>
<executedCount>0</executedCount>
<rDate></rDate>
<rTime>09:30:00</rTime>
</task>
每月 1 号 12:10¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<task id="__MonthlyTaskId000001">
<name>每月汇总</name>
<parentId>软件UUID</parentId>
<description></description>
<type>1</type>
<runningTime>2026-07-01T12:10:00.000+08:00</runningTime>
<interval>1</interval>
<period>512</period>
<runtimes>0</runtimes>
<terminateScript><![CDATA[]]></terminateScript>
<taskScript><![CDATA[(function () {
println("monthly");
})()]]></taskScript>
<modifyTime>2026-07-01T12:10:00.000+08:00</modifyTime>
<creator>admin</creator>
<creatorid>用户UUID</creatorid>
<totalRuntimes>0</totalRuntimes>
<state>0</state>
<startupType>1</startupType>
<dayOfMonth>1</dayOfMonth>
<repeatTimes>0</repeatTimes>
<frequency>0</frequency>
<executedCount>0</executedCount>
<rDate></rDate>
<rTime>12:10:00</rTime>
</task>
不重复(指定日期时刻一次)¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<task id="__OnceTaskId000000001">
<name>单次任务</name>
<parentId>软件UUID</parentId>
<description></description>
<type>1</type>
<runningTime>2026-12-31T23:00:00.000+08:00</runningTime>
<interval>1</interval>
<period>0</period>
<runtimes>0</runtimes>
<terminateScript><![CDATA[]]></terminateScript>
<taskScript><![CDATA[(function () {
println("once");
})()]]></taskScript>
<modifyTime>2026-07-15T10:00:00.000+08:00</modifyTime>
<creator>admin</creator>
<creatorid>用户UUID</creatorid>
<totalRuntimes>0</totalRuntimes>
<state>0</state>
<startupType>1</startupType>
<dayOfMonth>1</dayOfMonth>
<repeatTimes>0</repeatTimes>
<frequency>0</frequency>
<executedCount>0</executedCount>
<rDate>2026-12-31</rDate>
<rTime>23:00:00</rTime>
</task>
每 5 分钟 / 每 2 小时¶
period=34,interval=5,cron:0 0/5 * * * ?period=546,interval=2,cron:0 0 0/2 * * ?startupType按需;rTime/rDate可空;runningTime写当前合理时刻即可
禁止调度¶
同一文件改 <startupType>2</startupType>。设计器保存会 deleteJob;仅改文件需 job 重载/进程侧再次 init。
6. 设计态 / Job API(便于对照,非必须手调)¶
设计器基路径(典型):/api/designtime/applications/{applicationId}/tasks
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | .../tasks |
列表;searchword、pageNo、linesPerPage |
| GET | .../tasks/{taskId} |
详情 |
| POST | .../tasks?rTime=&rDate= |
新建;body=Task JSON;daysOfWeek 数组;随后 insertOrUpdate Job |
| PUT | .../tasks/{taskId} |
更新;同步 Job |
| DELETE | .../tasks |
body=id 数组;先 deleteJob 再删文件 |
| PUT | .../tasks/{id}/start |
state=1(历史启停 UI) |
Quartz 键:jobName=taskId,jobGroup=applicationId,triggerName=taskId,triggerGroup=applicationId。Job XML 序列化 IscriptTaskJob(含 taskId/applicationId/loop/period/startupType)。
7. 部署与生效清单¶
- 软件目录存在且
parentId指向该软件 id - 文件在
…/task/{name}.task,文件名=name - 企业域 绑定 该软件;软件 activated=true
- **job 服务**运行(
obpm-job/ Quartz);保存任务或InitJobScheduler扫描后 Job 入库 startupType≠2;否则不会跑- 脚本依赖的默认库 / 具名数据源、表单名、角色、邮件、第三方 Token 等在目标环境可用
- 多节点:以 Quartz 集群/共享调度为准,避免重复执行需集群配置正确
仅复制 .task 到 workspace 而不**触发设计态保存/init:**可能不进调度。运维侧需重启 job 或走 API 保存。
8. 与其它技能边界¶
| 内容 | 本文 | 其它 |
|---|---|---|
.task 字段、period/startupType、脚本外壳、cron 对应 |
✓ | |
软件目录骨架、task/ 分组存在性 |
交汇 | APPLICATION_SKILL |
数据源 name、*ByDSName、设计态 DS 服务 |
脚本消费 | DATASOURCE_SKILL |
| 表单字段/Activity | 脚本里 doViewByFormName 创建文档时 |
FORM_SKILL |
| 角色 id/roleNo | 脚本选人发信等 | ROLE_SKILL |
| 流程待办 / TaskInfo | 否 | 流程运行时 |
Widget system_workflow 待办摘要 |
否 | WIDGET_SKILL |
附录 A:TaskConstants 速查¶
TASK_TYPE_SCRIPT = 1 (0x1)
STARTUP_TYPE_MANUAL = 0
STARTUP_TYPE_AUTO = 1
STARTUP_TYPE_BANNED = 2
REAPET_TYPE_NOTREAPET = 0
REPEAT_TYPE_NONE = 0
REPEAT_TYPE_DAILY = 2 (0x2)
REPEAT_TYPE_DAILY_MINUTES = 34 (0x22)
REPEAT_TYPE_DAILY_HOURS = 546 (0x222)
REPEAT_TYPE_WEEKLY = 32 (0x20)
REPEAT_TYPE_MONTHLY = 512 (0x200)
REPEAT_TYPE_IMMEDIATE = 8192 (0x2000)
RUNNING / STOPPING = 字符串文案 "Running" / "Stopped"(展示用)
附录 B:后缀常量(ModelSuffix)¶
| 常量 | 值 |
|---|---|
| TASK_PATH_SUFFIX / TASK_FILE_SUFFIX | task |
| TASK_FILE_GROUP | /task |
附录 C:常见失败¶
| 现象 | 原因 |
|---|---|
| 文件在但不执行 | 软件未激活 / 域未绑定 / job 未加载 / startupType=2 |
| 保存报名称已存在 | 同应用另一 .task 的 name 冲突 |
| 保存报选时间 | 日周月缺 rTime;或不重复缺 rDate/rTime |
| 立刻/不重复只跑一次后消失 | loop=false,执行后 deleteJob(预期) |
| terminate 后不再调度 | terminateScript 返回了 true |
| 脚本 NPE / 无用户 | 定时无登录态;须自建 WebUser / 写死或查用户 |
| SQL 影响 0 行 / 错库 | *ByDSName 用的是数据源 name;动态表在默认库 |
| 每周不触发 | daysOfWeek 缺失或值不是 1–7;runningTime 时分秒与 rTime 不一致 |
| 每月日错 | dayOfMonth 未改(创建 API 默认 1) |
| 改名找不到 | 文件名与 <name> 不一致 |
| 根元素错误 | 必须小写 task |
| id 写成子元素 | 改为根属性 |
| 放进 module | 移到应用级 task/ |
| 与流程「任务」混淆 | 本文是定时脚本;流程待办不是 .task |
附录 D:生成检查清单¶
- 路径:
storage/workspace/{软件}.application/task/{name}.task - 根
<task id="...">;name=文件名去后缀;parentId=软件 id -
type=1;startupType∈{0,1,2};period为附录 A 合法十进制值 -
taskScriptCDATA 可执行;建议 IIFE;依赖表/源/用户已存在 - 按 period 填齐
rTime/rDate/daysOfWeek/dayOfMonth/interval,且与runningTime一致 - 计数字段可全
0;terminateScript可空 CDATA - 同应用 name 不冲突;生效路径已考虑 job 注册与软件激活