@mldong/jeeflow 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.d.ts CHANGED
@@ -31,6 +31,8 @@ export declare class EngineImpl implements Engine {
31
31
  setExtensions(ext: EngineExtensions): void;
32
32
  setRegistry(reg: HandlerRegistry): void;
33
33
  private firePre;
34
+ /** 表达式求值(v1.5.0,门面 highLight 决策分支过滤用) */
35
+ evalExpr(expr: string, vars: Record<string, any>): Promise<any>;
34
36
  private firePost;
35
37
  private fireEvent;
36
38
  startProcessInstanceById(defineId: number, operator: string, args?: Record<string, any>): Promise<ProcessInstance>;
package/dist/engine.js CHANGED
@@ -36,6 +36,12 @@ export class EngineImpl {
36
36
  return false;
37
37
  return true;
38
38
  }
39
+ /** 表达式求值(v1.5.0,门面 highLight 决策分支过滤用) */
40
+ async evalExpr(expr, vars) {
41
+ if (!this.exprEval)
42
+ throw new Error('ExpressionEvaluator 未配置');
43
+ return this.exprEval.eval(expr, vars);
44
+ }
39
45
  async firePost(node, inst) {
40
46
  if (!this.ext?.interceptors)
41
47
  return;
package/dist/facade.d.ts CHANGED
@@ -24,6 +24,8 @@ export declare class JeeflowFacade {
24
24
  private getLastByName;
25
25
  private highLight;
26
26
  private collectPath;
27
+ /** 决策输出边表达式求值(args = 实例变量 + 决策节点前置任务变量) */
28
+ private evalDecisionExpr;
27
29
  private approvalRecord;
28
30
  private getAssigneeTextData;
29
31
  private createCCInstance;
@@ -36,4 +38,14 @@ export declare class JeeflowFacade {
36
38
  private taskAddActor;
37
39
  private taskLatest;
38
40
  private ext;
41
+ private definePage;
42
+ private defineDetail;
43
+ private instancePage;
44
+ private instanceDetail;
45
+ /** 流程 JSON 中第一个任务节点 id(issues/05-4 isFirstTaskNode 用) */
46
+ private firstTaskNodeId;
47
+ private todoList;
48
+ private doneList;
49
+ /** 定义 content 解析为 LogicFlow JSON(issues/05 jsonObject) */
50
+ private parseGraph;
39
51
  }
package/dist/facade.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // 集成方只实现一个转发端点:把 body JSON 转成对象传入 flow(),所有流程能力按
4
4
  // action(boot2/boot3 端点短名)路由。返回统一结构 {code, msg, data}
5
5
  // (code=0 成功 / 99999999 失败)。操作人约定:args.operator 显式传入。
6
+ import { TaskState, } from './model.js';
6
7
  // submitType 枚举(对齐 boot3)
7
8
  const SUBMIT_APPLY = 0;
8
9
  const SUBMIT_AGREE = 1;
@@ -37,6 +38,10 @@ export class JeeflowFacade {
37
38
  }
38
39
  async dispatch(action, args) {
39
40
  switch (action) {
41
+ case 'processDefine/page':
42
+ return this.definePage(args);
43
+ case 'processDefine/detail':
44
+ return this.defineDetail(args);
40
45
  case 'processDefine/startAndExecute':
41
46
  case 'processInstance/startAndExecute':
42
47
  return this.startAndExecute(args);
@@ -49,8 +54,16 @@ export class JeeflowFacade {
49
54
  return this.repo.removeDefine(toId(args.id));
50
55
  case 'processDefine/upAndDown':
51
56
  return this.repo.updateDefineState(toId(args.id), toInt(args.state));
57
+ case 'processInstance/page':
58
+ return this.instancePage(args);
59
+ case 'processInstance/detail':
60
+ return this.instanceDetail(args);
52
61
  case 'processInstance/withdraw':
53
62
  return this.withdraw(args);
63
+ case 'processTask/todoList':
64
+ return this.todoList(args);
65
+ case 'processTask/doneList':
66
+ return this.doneList(args);
54
67
  case 'processTask/execute':
55
68
  return this.execute(args);
56
69
  case 'processDesign/page':
@@ -231,7 +244,7 @@ export class JeeflowFacade {
231
244
  }
232
245
  // ── 流程设计(需扩展仓储) ───────────────────────────────────────────────
233
246
  async designPage(args) {
234
- const [rows, total] = await this.ext().pageDesigns(toInt(args.pageNum ?? 1), toInt(args.pageSize ?? 10));
247
+ const [rows, total] = await this.ext().pageDesigns(toInt(args.pageNum ?? 1), toInt(args.pageSize ?? 10), undefined, parseMQuery(args));
235
248
  return { rows, recordCount: total };
236
249
  }
237
250
  async designDetail(args) {
@@ -244,12 +257,25 @@ export class JeeflowFacade {
244
257
  type: design.type, icon: design.icon, isDeployed: design.isDeployed, remark: design.remark,
245
258
  };
246
259
  const hisList = await ext.listDesignHis(design.id);
260
+ let jsonObject;
247
261
  if (hisList.length > 0) {
248
262
  try {
249
- data.jsonObject = JSON.parse(toStr(hisList[0].content));
263
+ jsonObject = JSON.parse(toStr(hisList[0].content));
250
264
  }
251
265
  catch { /* ignore */ }
252
266
  }
267
+ // issues/07:jsonObject 缺失基本信息时从设计表补齐(对齐 boot3 ProcessDesignServiceImpl.findById)
268
+ if (!jsonObject || typeof jsonObject !== 'object')
269
+ jsonObject = {};
270
+ if (!(jsonObject.name))
271
+ jsonObject.name = design.name;
272
+ if (!(jsonObject.displayName))
273
+ jsonObject.displayName = design.displayName;
274
+ if (!(jsonObject.type))
275
+ jsonObject.type = design.type;
276
+ if (!(jsonObject.processDesignId))
277
+ jsonObject.processDesignId = design.id;
278
+ data.jsonObject = jsonObject;
253
279
  data.his = hisList;
254
280
  return data;
255
281
  }
@@ -296,7 +322,7 @@ export class JeeflowFacade {
296
322
  // ── 委托代理(需扩展仓储) ───────────────────────────────────────────────
297
323
  async surrogatePage(args) {
298
324
  const filters = args.operator != null ? { operator: String(args.operator) } : undefined;
299
- const [rows, total] = await this.ext().pageSurrogates(toInt(args.pageNum ?? 1), toInt(args.pageSize ?? 10), filters);
325
+ const [rows, total] = await this.ext().pageSurrogates(toInt(args.pageNum ?? 1), toInt(args.pageSize ?? 10), filters, parseMQuery(args));
300
326
  return { rows, recordCount: total };
301
327
  }
302
328
  async surrogateSave(args) {
@@ -353,26 +379,33 @@ export class JeeflowFacade {
353
379
  for (const t of his)
354
380
  if (!active.includes(t.taskName) && !history.includes(t.taskName))
355
381
  history.push(t.taskName);
356
- // 路径补全:start 沿边递归(遇活跃节点停止)
382
+ // 路径补全:start 沿边递归(遇活跃节点停止);决策分支按表达式求值过滤(issues/06)
357
383
  const def = await this.repo.findDefineById(inst.defineId);
358
384
  if (def) {
359
385
  try {
360
386
  const flow = JSON.parse(toStr(def.content));
361
- this.collectPath(flow, 'start', '', active, history, edges, new Set());
387
+ await this.collectPath(flow, 'start', '', active, history, edges, new Set(), inst.variables ?? {}, his);
362
388
  }
363
389
  catch { /* ignore */ }
364
390
  }
365
391
  return { activeNodeNames: active, historyNodeNames: history, historyEdgeNames: edges };
366
392
  }
367
- collectPath(flow, nodeId, edgeName, active, history, edges, visited) {
393
+ async collectPath(flow, nodeId, edgeName, active, history, edges, visited, vars, historyTasks) {
368
394
  if (visited.has(nodeId))
369
395
  return;
370
396
  visited.add(nodeId);
371
397
  if (edgeName && !edges.includes(edgeName))
372
398
  edges.push(edgeName);
399
+ const src = (flow.nodes ?? []).find((n) => n.id === nodeId);
373
400
  for (const e of flow.edges ?? []) {
374
401
  if (e.sourceNodeId !== nodeId)
375
402
  continue;
403
+ // 决策节点:输出边表达式求值过滤(对齐 boot3 recursionModel,issues/06)
404
+ if (src?.type === 'snaker:decision') {
405
+ const expr = e.properties?.expr;
406
+ if (expr && !await this.evalDecisionExpr(flow, src, expr, vars, historyTasks))
407
+ continue;
408
+ }
376
409
  const target = (flow.nodes ?? []).find((n) => n.id === e.targetNodeId);
377
410
  if (!target)
378
411
  continue;
@@ -381,7 +414,25 @@ export class JeeflowFacade {
381
414
  history.push(tid);
382
415
  if (active.includes(tid))
383
416
  continue;
384
- this.collectPath(flow, tid, e.id, active, history, edges, visited);
417
+ await this.collectPath(flow, tid, e.id, active, history, edges, visited, vars, historyTasks);
418
+ }
419
+ }
420
+ /** 决策输出边表达式求值(args = 实例变量 + 决策节点前置任务变量) */
421
+ async evalDecisionExpr(flow, decision, expr, vars, historyTasks) {
422
+ const args = { ...(vars ?? {}) };
423
+ for (const e of flow.edges ?? []) {
424
+ if (e.targetNodeId === decision.id) {
425
+ const t = (historyTasks ?? []).find((x) => x.taskName === e.sourceNodeId);
426
+ if (t?.variables)
427
+ Object.assign(args, t.variables);
428
+ break;
429
+ }
430
+ }
431
+ try {
432
+ return Boolean(await this.engine.evalExpr(expr, args));
433
+ }
434
+ catch {
435
+ return false;
385
436
  }
386
437
  }
387
438
  async approvalRecord(args) {
@@ -424,8 +475,8 @@ export class JeeflowFacade {
424
475
  const pageNum = toInt(args.pageNum ?? 1);
425
476
  const pageSize = toInt(args.pageSize ?? 10);
426
477
  const actorId = String(args.operator ?? 'user1');
427
- const { rows, total } = await this.repo.pageCcInstances(pageNum, pageSize, actorId);
428
- return { rows, recordCount: total };
478
+ const { rows, total } = await this.repo.pageCcInstances(pageNum, pageSize, actorId, parseMQuery(args));
479
+ return { rows: rows.map(r => ccRowToMap(r)), recordCount: total };
429
480
  }
430
481
  async taskDetail(args) {
431
482
  const taskId = toId(args.id);
@@ -446,6 +497,7 @@ export class JeeflowFacade {
446
497
  if (inst) {
447
498
  const def = await this.repo.findDefineById(inst.defineId);
448
499
  if (def) {
500
+ vo.jsonObject = this.parseGraph(def.content); // issues/05
449
501
  try {
450
502
  const flow = JSON.parse(toStr(def.content));
451
503
  for (const n of flow.nodes ?? []) {
@@ -558,8 +610,206 @@ export class JeeflowFacade {
558
610
  throw new Error('未配置 ProcessExtRepository(扩展仓储)');
559
611
  return this.extRepo;
560
612
  }
613
+ // ═══ 基础分页/详情(v1.5.0 补齐,对齐 Java 门面)═══
614
+ async definePage(args) {
615
+ const pageNum = toInt(args.pageNum ?? 1);
616
+ const pageSize = toInt(args.pageSize ?? 10);
617
+ const { rows, total } = await this.repo.pageDefines(pageNum, pageSize, parseMQuery(args));
618
+ return { rows: rows.map(r => defineRowToMap(r)), recordCount: total };
619
+ }
620
+ async defineDetail(args) {
621
+ const id = toId(args.id);
622
+ const def = await this.repo.findDefineById(id);
623
+ if (!def)
624
+ throw new Error('流程定义不存在');
625
+ return {
626
+ id: def.id, name: def.name, displayName: def.displayName,
627
+ type: def.type, state: def.state, version: def.version,
628
+ jsonObject: this.parseGraph(def.content), // issues/05
629
+ };
630
+ }
631
+ async instancePage(args) {
632
+ const pageNum = toInt(args.pageNum ?? 1);
633
+ const pageSize = toInt(args.pageSize ?? 10);
634
+ const operator = String(args.operator ?? 'user1');
635
+ const { rows, total } = await this.repo.pageInstances(pageNum, pageSize, operator, parseMQuery(args));
636
+ return { rows: rows.map(r => instanceRowToMap(r)), recordCount: total };
637
+ }
638
+ async instanceDetail(args) {
639
+ const id = toId(args.id);
640
+ const inst = await this.repo.findInstanceById(id);
641
+ if (!inst)
642
+ throw new Error('流程实例不存在');
643
+ const def0 = await this.repo.findDefineById(inst.defineId);
644
+ const graph = def0 ? this.parseGraph(def0.content) : undefined;
645
+ // 任务列表(issues/05-4):全量 tasks + activeTaskList(仅 DOING)+ 任务行 ext/isFirstTaskNode
646
+ const firstTaskNodeId = this.firstTaskNodeId(graph);
647
+ const tasks = [];
648
+ const activeTaskList = [];
649
+ for (const t of inst.tasks ?? []) {
650
+ const vo = {
651
+ id: t.id, processInstanceId: t.processInstanceId, taskName: t.taskName,
652
+ displayName: t.displayName, taskType: t.taskType ?? null,
653
+ performType: t.performType ?? null, taskState: t.taskState,
654
+ operator: t.actorId ?? '', finishTime: t.finishTime,
655
+ expireTime: t.expireTime, formKey: t.formKey ?? '', taskParentId: t.parentTaskId ?? null,
656
+ variable: JSON.stringify(t.variables ?? {}),
657
+ createTime: t.createTime, createUser: t.createUser,
658
+ updateTime: t.updateTime, updateUser: t.updateUser,
659
+ taskActorIdList: t.actorIds ?? [],
660
+ };
661
+ const ext = { ...(t.variables ?? {}) };
662
+ const doing = t.taskState === TaskState.Doing;
663
+ ext.isFirstTaskNode = doing && t.taskName === firstTaskNodeId;
664
+ vo.ext = ext;
665
+ tasks.push(vo);
666
+ if (doing)
667
+ activeTaskList.push(vo);
668
+ }
669
+ return {
670
+ id: inst.id, parentId: inst.parentId, processDefineId: inst.defineId,
671
+ state: inst.state, parentNodeName: inst.parentNodeName,
672
+ businessNo: inst.businessNo, operator: inst.operator,
673
+ variables: inst.variables, createTime: inst.createTime, createUser: inst.createUser,
674
+ jsonObject: graph, // issues/05
675
+ tasks,
676
+ activeTaskList,
677
+ };
678
+ }
679
+ /** 流程 JSON 中第一个任务节点 id(issues/05-4 isFirstTaskNode 用) */
680
+ firstTaskNodeId(graph) {
681
+ for (const n of graph?.nodes ?? []) {
682
+ if (n?.type === 'snaker:task')
683
+ return n.id;
684
+ }
685
+ return '';
686
+ }
687
+ async todoList(args) {
688
+ const pageNum = toInt(args.pageNum ?? 1);
689
+ const pageSize = toInt(args.pageSize ?? 10);
690
+ const actorId = String(args.operator ?? 'user1');
691
+ const { rows, total } = await this.repo.pageTodoTasks(pageNum, pageSize, actorId, parseMQuery(args));
692
+ return { rows: rows.map(r => taskRowToMap(r)), recordCount: total };
693
+ }
694
+ async doneList(args) {
695
+ const pageNum = toInt(args.pageNum ?? 1);
696
+ const pageSize = toInt(args.pageSize ?? 10);
697
+ const operator = String(args.operator ?? 'user1');
698
+ const { rows, total } = await this.repo.pageDoneTasks(pageNum, pageSize, operator, parseMQuery(args));
699
+ return { rows: rows.map(r => taskRowToMap(r)), recordCount: total };
700
+ }
701
+ /** 定义 content 解析为 LogicFlow JSON(issues/05 jsonObject) */
702
+ parseGraph(content) {
703
+ try {
704
+ const str = typeof content === 'string' ? content : new TextDecoder().decode(content);
705
+ const obj = JSON.parse(str);
706
+ return obj && typeof obj === 'object' ? obj : undefined;
707
+ }
708
+ catch {
709
+ return undefined;
710
+ }
711
+ }
712
+ }
713
+ // ── 行转 Map(issues/05-2 列表字段契约 + 05-3 时间格式)─────────────────────
714
+ /** Date → 'yyyy-MM-dd HH:mm:ss'(null/undefined → null) */
715
+ function fmtTime(v) {
716
+ if (v == null)
717
+ return null;
718
+ const p = (n) => String(n).padStart(2, '0');
719
+ return `${v.getFullYear()}-${p(v.getMonth() + 1)}-${p(v.getDate())} ${p(v.getHours())}:${p(v.getMinutes())}:${p(v.getSeconds())}`;
720
+ }
721
+ /** JSON 字符串 → 对象(坏 JSON / 空返回空对象) */
722
+ function parseVarMap(json) {
723
+ if (!json)
724
+ return {};
725
+ try {
726
+ const o = JSON.parse(json);
727
+ return o && typeof o === 'object' ? o : {};
728
+ }
729
+ catch {
730
+ return {};
731
+ }
732
+ }
733
+ /** 定义行:时间格式化 */
734
+ function defineRowToMap(r) {
735
+ return {
736
+ id: r.id, name: r.name, displayName: r.displayName, type: r.type,
737
+ state: r.state, version: r.version,
738
+ createTime: fmtTime(r.createTime), createUser: r.createUser,
739
+ updateTime: fmtTime(r.updateTime), updateUser: r.updateUser,
740
+ };
741
+ }
742
+ /** 实例行:ext(实例变量对象)+ displayName/version(定义) */
743
+ function instanceRowToMap(r) {
744
+ return {
745
+ id: r.id, parentId: r.parentId ?? null, processDefineId: r.defineId,
746
+ state: r.state, parentNodeName: r.parentNodeName, businessNo: r.businessNo,
747
+ operator: r.operator, expireTime: fmtTime(r.expireTime),
748
+ variable: r.variables, createTime: fmtTime(r.createTime), createUser: r.createUser,
749
+ updateTime: fmtTime(r.updateTime), updateUser: r.updateUser,
750
+ processDefineName: r.defineName, processDefineDisplayName: r.defineDisplayName,
751
+ processDefineVersion: r.defineVersion,
752
+ ext: r.variables, displayName: r.defineDisplayName, version: r.defineVersion,
753
+ };
754
+ }
755
+ /** 抄送行:ext(实例变量对象)+ displayName/version(定义) */
756
+ function ccRowToMap(r) {
757
+ return {
758
+ id: r.id, parentId: r.parentId ?? null, processDefineId: r.defineId,
759
+ state: r.state, parentNodeName: r.parentNodeName, businessNo: r.businessNo,
760
+ operator: r.operator, expireTime: fmtTime(r.expireTime),
761
+ variable: r.variables, createTime: fmtTime(r.createTime), createUser: r.createUser,
762
+ updateTime: fmtTime(r.updateTime), updateUser: r.updateUser,
763
+ processDefineName: r.defineName, processDefineDisplayName: r.defineDisplayName,
764
+ processDefineVersion: r.defineVersion,
765
+ ext: r.variables, displayName: r.defineDisplayName, version: r.defineVersion,
766
+ };
767
+ }
768
+ /** 任务行:ext(任务变量,空回退实例变量)+ instanceExt + version */
769
+ function taskRowToMap(r) {
770
+ const instanceExt = parseVarMap(r.instanceVariable);
771
+ const ext = Object.keys(r.variables ?? {}).length > 0 ? r.variables : instanceExt;
772
+ return {
773
+ id: r.id, processInstanceId: r.processInstanceId, taskName: r.taskName,
774
+ displayName: r.displayName, taskType: r.taskType, performType: r.performType,
775
+ taskState: r.taskState, operator: r.operator, finishTime: fmtTime(r.finishTime),
776
+ expireTime: fmtTime(r.expireTime), formKey: r.formKey, taskParentId: r.taskParentId ?? null,
777
+ variable: r.variables, createTime: fmtTime(r.createTime), createUser: r.createUser,
778
+ updateTime: fmtTime(r.updateTime), updateUser: r.updateUser,
779
+ processDefineName: r.processDefineName, processDefineDisplayName: r.processDefineDisplayName,
780
+ instanceVariable: r.instanceVariable, instanceCreateTime: fmtTime(r.instanceCreateTime),
781
+ ext, instanceExt, version: r.defineVersion,
782
+ };
561
783
  }
562
784
  // ── 工具 ──────────────────────────────────────────────────────────────────────
785
+ /** m_ 前缀查询参数解析(issues/05-5,对齐 Java JeeflowQueryParser):
786
+ * m_EQ_taskName → t.task_name EQ;m_pd_LIKE_displayName → pd.display_name LIKE */
787
+ function parseMQuery(args) {
788
+ const out = [];
789
+ for (const [key, value] of Object.entries(args)) {
790
+ if (!key.startsWith('m_') || value == null || value === '')
791
+ continue;
792
+ const parts = key.slice(2).split('_');
793
+ if (parts.length < 2)
794
+ continue;
795
+ let column;
796
+ let operator;
797
+ if (parts.length === 2) {
798
+ // 无别名 → 默认主表别名 t(对齐 Java,白名单列均带表别名)
799
+ operator = parts[0];
800
+ column = 't.' + toUnderscore(parts[1]);
801
+ }
802
+ else {
803
+ operator = parts[1];
804
+ column = parts[0] + '.' + toUnderscore(parts[2]);
805
+ }
806
+ out.push({ column, operator: operator.toUpperCase(), value });
807
+ }
808
+ return out;
809
+ }
810
+ function toUnderscore(camel) {
811
+ return camel.replace(/[A-Z]/g, c => '_' + c.toLowerCase());
812
+ }
563
813
  function toStr(v) {
564
814
  if (v == null)
565
815
  return '';
@@ -1,5 +1,5 @@
1
1
  import { ProcessDesign, ProcessDesignHis, ProcessSurrogate } from '../model.js';
2
- import type { IDGenerator, ProcessExtRepository } from '../spi.js';
2
+ import type { IDGenerator, ProcessExtRepository, QueryCondition } from '../spi.js';
3
3
  import { type SqlAdapter } from './shared.js';
4
4
  export declare class JdbcProcessExtRepository implements ProcessExtRepository {
5
5
  private readonly adapter;
@@ -13,7 +13,10 @@ export declare class JdbcProcessExtRepository implements ProcessExtRepository {
13
13
  saveDesign(d: ProcessDesign): Promise<void>;
14
14
  updateDesign(d: ProcessDesign): Promise<void>;
15
15
  removeDesign(id: number): Promise<void>;
16
- pageDesigns(pageNum?: number, pageSize?: number, filters?: Record<string, any>): Promise<[ProcessDesign[], number]>;
16
+ pageDesigns(pageNum?: number, pageSize?: number, filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessDesign[], number]>;
17
+ private buildExtWhere;
18
+ private static readonly DESIGN_WHITELIST;
19
+ private static readonly SURROGATE_WHITELIST;
17
20
  saveDesignHis(his: ProcessDesignHis): Promise<void>;
18
21
  listDesignHis(designId: number): Promise<ProcessDesignHis[]>;
19
22
  private static SURROGATE_COLS;
@@ -21,7 +24,7 @@ export declare class JdbcProcessExtRepository implements ProcessExtRepository {
21
24
  saveSurrogate(s: ProcessSurrogate): Promise<void>;
22
25
  updateSurrogate(s: ProcessSurrogate): Promise<void>;
23
26
  removeSurrogate(id: number): Promise<void>;
24
- pageSurrogates(pageNum?: number, pageSize?: number, filters?: Record<string, any>): Promise<[ProcessSurrogate[], number]>;
27
+ pageSurrogates(pageNum?: number, pageSize?: number, filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessSurrogate[], number]>;
25
28
  getSurrogate(operator: string, processName: string, at?: Date): Promise<ProcessSurrogate | null>;
26
29
  private querySurrogate;
27
30
  private mapDesign;
package/dist/jdbc/ext.js CHANGED
@@ -76,7 +76,7 @@ export class JdbcProcessExtRepository {
76
76
  await this.done(conn);
77
77
  }
78
78
  }
79
- async pageDesigns(pageNum = 1, pageSize = 10, filters) {
79
+ async pageDesigns(pageNum = 1, pageSize = 10, filters, conditions) {
80
80
  let sql = `SELECT ${JdbcProcessExtRepository.DESIGN_COLS} FROM wf_process_design t WHERE 1=1`;
81
81
  let countSql = 'SELECT COUNT(*) FROM wf_process_design t WHERE 1=1';
82
82
  const args = [];
@@ -89,6 +89,12 @@ export class JdbcProcessExtRepository {
89
89
  args2.push(val);
90
90
  }
91
91
  }
92
+ // m_ 条件(issues/05-5):LIKE/EQ 等走白名单
93
+ const cond = this.buildExtWhere(conditions ?? [], JdbcProcessExtRepository.DESIGN_WHITELIST);
94
+ sql += cond.sql;
95
+ countSql += cond.sql;
96
+ args.push(...cond.params);
97
+ args2.push(...cond.params);
92
98
  const conn = await this.c();
93
99
  try {
94
100
  const countRow = await conn.fetchOne(this.sql(countSql), args2);
@@ -102,6 +108,53 @@ export class JdbcProcessExtRepository {
102
108
  await this.done(conn);
103
109
  }
104
110
  }
111
+ // m_ 条件 WHERE 构建(issues/05-5,白名单 + 参数化)
112
+ buildExtWhere(conditions, whitelist) {
113
+ let sql = '';
114
+ const params = [];
115
+ for (const c of conditions) {
116
+ if (!whitelist.has(c.column))
117
+ continue;
118
+ const val = c.value;
119
+ if (val == null || val === '')
120
+ continue;
121
+ switch (c.operator.toUpperCase()) {
122
+ case 'EQ':
123
+ sql += ` AND ${c.column} = ?`;
124
+ params.push(val);
125
+ break;
126
+ case 'LIKE':
127
+ sql += ` AND ${c.column} LIKE ?`;
128
+ params.push(`%${val}%`);
129
+ break;
130
+ case 'LLIKE':
131
+ sql += ` AND ${c.column} LIKE ?`;
132
+ params.push(`%${val}`);
133
+ break;
134
+ case 'RLIKE':
135
+ sql += ` AND ${c.column} LIKE ?`;
136
+ params.push(`${val}%`);
137
+ break;
138
+ case 'IN': {
139
+ if (Array.isArray(val) && val.length > 0) {
140
+ const marks = val.map(() => '?').join(',');
141
+ sql += ` AND ${c.column} IN (${marks})`;
142
+ params.push(...val);
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ return { sql, params };
149
+ }
150
+ static DESIGN_WHITELIST = new Set([
151
+ 't.id', 't.name', 't.display_name', 't.type', 't.is_deployed', 't.remark',
152
+ 't.create_time', 't.update_time',
153
+ ]);
154
+ static SURROGATE_WHITELIST = new Set([
155
+ 't.id', 't.process_name', 't.operator', 't.surrogate', 't.enabled',
156
+ 't.start_time', 't.end_time', 't.create_time', 't.update_time',
157
+ ]);
105
158
  // ── 设计历史 ─────────────────────────────────────────────────────────────
106
159
  async saveDesignHis(his) {
107
160
  if (!his.id)
@@ -182,7 +235,7 @@ export class JdbcProcessExtRepository {
182
235
  await this.done(conn);
183
236
  }
184
237
  }
185
- async pageSurrogates(pageNum = 1, pageSize = 10, filters) {
238
+ async pageSurrogates(pageNum = 1, pageSize = 10, filters, conditions) {
186
239
  let sql = `SELECT ${JdbcProcessExtRepository.SURROGATE_COLS} FROM wf_process_surrogate t WHERE 1=1`;
187
240
  let countSql = 'SELECT COUNT(*) FROM wf_process_surrogate t WHERE 1=1';
188
241
  const args = [];
@@ -195,6 +248,12 @@ export class JdbcProcessExtRepository {
195
248
  args2.push(val);
196
249
  }
197
250
  }
251
+ // m_ 条件(issues/05-5)
252
+ const cond = this.buildExtWhere(conditions ?? [], JdbcProcessExtRepository.SURROGATE_WHITELIST);
253
+ sql += cond.sql;
254
+ countSql += cond.sql;
255
+ args.push(...cond.params);
256
+ args2.push(...cond.params);
198
257
  const conn = await this.c();
199
258
  try {
200
259
  const countRow = await conn.fetchOne(this.sql(countSql), args2);
@@ -1,5 +1,5 @@
1
- import { ProcessInstance, ProcessTask, type ProcessDefine, type CcInstanceRow } from '../model.js';
2
- import type { IDGenerator, ProcessRepository } from '../spi.js';
1
+ import { ProcessInstance, ProcessTask, type ProcessDefine, type CcInstanceRow, type DefineRow, type InstanceRow, type TaskRow } from '../model.js';
2
+ import type { IDGenerator, ProcessRepository, QueryCondition } from '../spi.js';
3
3
  /** 默认 ID 生成器:时间戳毫秒 + 同毫秒递增序号(对齐 Java nextId 默认实现) */
4
4
  export declare class TsIDGenerator implements IDGenerator {
5
5
  private last;
@@ -63,7 +63,30 @@ export declare class JdbcRepository implements ProcessRepository {
63
63
  removeTaskActor(taskId: number, actors: string[]): Promise<void>;
64
64
  createCcInstance(instanceId: number, creator: string, ...actorIds: string[]): Promise<void>;
65
65
  updateCcStatus(instanceId: number, actorId: string): Promise<void>;
66
- pageCcInstances(pageNum: number, pageSize: number, actorId: string): Promise<{
66
+ pageDefines(pageNum: number, pageSize: number, conditions?: QueryCondition[]): Promise<{
67
+ rows: DefineRow[];
68
+ total: number;
69
+ }>;
70
+ pageInstances(pageNum: number, pageSize: number, operator: string, conditions?: QueryCondition[]): Promise<{
71
+ rows: InstanceRow[];
72
+ total: number;
73
+ }>;
74
+ pageTodoTasks(pageNum: number, pageSize: number, actorId: string, conditions?: QueryCondition[]): Promise<{
75
+ rows: TaskRow[];
76
+ total: number;
77
+ }>;
78
+ pageDoneTasks(pageNum: number, pageSize: number, operator: string, conditions?: QueryCondition[]): Promise<{
79
+ rows: TaskRow[];
80
+ total: number;
81
+ }>;
82
+ private pageTasks;
83
+ private mapInstanceRow;
84
+ protected buildWhere(conditions: QueryCondition[], whitelist: Set<string>): {
85
+ sql: string;
86
+ params: any[];
87
+ };
88
+ private mapTaskRow;
89
+ pageCcInstances(pageNum: number, pageSize: number, actorId: string, conditions?: QueryCondition[]): Promise<{
67
90
  rows: CcInstanceRow[];
68
91
  total: number;
69
92
  }>;
@@ -11,6 +11,30 @@
11
11
  import { AsyncLocalStorage } from 'node:async_hooks';
12
12
  import { ProcessInstance, ProcessTask } from '../model.js';
13
13
  import { TaskState } from '../model.js';
14
+ // ═══ 列白名单(issues/05-5,与 mldong-boot2 别名一致) ═══
15
+ const TASK_WHITELIST = new Set([
16
+ 't.id', 't.task_name', 't.display_name', 't.task_type', 't.perform_type', 't.task_state',
17
+ 't.operator', 't.form_key', 't.create_time', 't.finish_time', 't.expire_time',
18
+ 't.process_instance_id', 't.task_parent_id', 't.variable',
19
+ 'pi.id', 'pi.business_no', 'pi.operator', 'pi.create_time', 'pi.state',
20
+ 'pd.name', 'pd.display_name', 'pd.type',
21
+ 'pta.actor_id', 'pta.process_task_id',
22
+ ]);
23
+ const INSTANCE_WHITELIST = new Set([
24
+ 't.id', 't.parent_id', 't.process_define_id', 't.state', 't.business_no',
25
+ 't.operator', 't.create_time', 't.expire_time', 't.variable',
26
+ 'pd.name', 'pd.display_name', 'pd.type', 'pd.version',
27
+ ]);
28
+ const CC_WHITELIST = new Set([
29
+ 't.id', 't.process_define_id', 't.state', 't.business_no', 't.operator',
30
+ 't.create_time', 't.variable',
31
+ 'pd.name', 'pd.display_name', 'pd.type', 'pd.version',
32
+ 'cc.actor_id', 'cc.state',
33
+ ]);
34
+ const DEFINE_WHITELIST = new Set([
35
+ 't.id', 't.name', 't.display_name', 't.type', 't.state', 't.version',
36
+ 't.create_time', 't.update_time',
37
+ ]);
14
38
  // 当前异步上下文绑定的事务连接
15
39
  const txStore = new AsyncLocalStorage();
16
40
  /** 默认 ID 生成器:时间戳毫秒 + 同毫秒递增序号(对齐 Java nextId 默认实现) */
@@ -399,20 +423,193 @@ export class JdbcRepository {
399
423
  await this.done(conn);
400
424
  }
401
425
  }
426
+ // ── 核心表分页(v1.5.0,对齐 Java pageDefines/pageInstances/pageTodoTasks/pageDoneTasks)──
427
+ async pageDefines(pageNum, pageSize, conditions) {
428
+ const cond = this.buildWhere(conditions ?? [], DEFINE_WHITELIST);
429
+ const where = ' FROM wf_process_define t WHERE 1=1' + cond.sql;
430
+ const conn = await this.c();
431
+ try {
432
+ const countRow = await conn.fetchOne(this.sql('SELECT COUNT(*) ' + where), cond.params);
433
+ const total = Number(Object.values(countRow)[0] ?? 0);
434
+ const rows = await conn.fetchAll(this.sql('SELECT id, name, display_name, type, state, version, create_time, create_user, update_time, update_user' +
435
+ where + ' ORDER BY t.id DESC LIMIT ? OFFSET ?'), [...cond.params, pageSize, (pageNum - 1) * pageSize]);
436
+ return {
437
+ rows: rows.map(r => ({
438
+ id: Number(r.id), name: r.name, displayName: r.display_name, type: r.type,
439
+ state: Number(r.state), version: Number(r.version),
440
+ createTime: r.create_time, createUser: r.create_user ?? '',
441
+ updateTime: r.update_time, updateUser: r.update_user ?? '',
442
+ })),
443
+ total,
444
+ };
445
+ }
446
+ finally {
447
+ await this.done(conn);
448
+ }
449
+ }
450
+ async pageInstances(pageNum, pageSize, operator, conditions) {
451
+ const cond = this.buildWhere(conditions ?? [], INSTANCE_WHITELIST);
452
+ const where = ' FROM wf_process_instance t' +
453
+ ' LEFT JOIN wf_process_define pd ON t.process_define_id = pd.id' +
454
+ ' WHERE t.operator = ?' + cond.sql;
455
+ const conn = await this.c();
456
+ try {
457
+ const countRow = await conn.fetchOne(this.sql('SELECT COUNT(*) ' + where), [operator, ...cond.params]);
458
+ const total = Number(Object.values(countRow)[0] ?? 0);
459
+ const cols = 't.id, t.parent_id, t.process_define_id, t.state, t.parent_node_name, t.business_no,' +
460
+ ' t.operator, t.expire_time, t.variable, t.create_time, t.create_user, t.update_time, t.update_user,' +
461
+ ' pd.name, pd.display_name, pd.version';
462
+ const rows = await conn.fetchAll(this.sql(`SELECT ${cols}${where} ORDER BY t.id DESC LIMIT ? OFFSET ?`), [operator, ...cond.params, pageSize, (pageNum - 1) * pageSize]);
463
+ return { rows: rows.map(r => this.mapInstanceRow(r)), total };
464
+ }
465
+ finally {
466
+ await this.done(conn);
467
+ }
468
+ }
469
+ async pageTodoTasks(pageNum, pageSize, actorId, conditions) {
470
+ return this.pageTasks(pageNum, pageSize, false, actorId, conditions);
471
+ }
472
+ async pageDoneTasks(pageNum, pageSize, operator, conditions) {
473
+ return this.pageTasks(pageNum, pageSize, true, operator, conditions);
474
+ }
475
+ async pageTasks(pageNum, pageSize, done, filter, conditions) {
476
+ const cond = this.buildWhere(conditions ?? [], TASK_WHITELIST);
477
+ const where = ' FROM wf_process_task t' +
478
+ ' LEFT JOIN wf_process_instance pi ON t.process_instance_id = pi.id' +
479
+ ' LEFT JOIN wf_process_define pd ON pi.process_define_id = pd.id' +
480
+ ' LEFT JOIN wf_process_task_actor pta ON t.id = pta.process_task_id' +
481
+ (done ? ' WHERE t.task_state <> 10 AND t.operator = ?' : ' WHERE t.task_state = 10 AND pta.actor_id = ?') + cond.sql;
482
+ const conn = await this.c();
483
+ try {
484
+ const countRow = await conn.fetchOne(this.sql('SELECT COUNT(DISTINCT t.id) ' + where), [filter, ...cond.params]);
485
+ const total = Number(Object.values(countRow)[0] ?? 0);
486
+ const cols = 'DISTINCT t.id, t.process_instance_id, t.task_name, t.display_name, t.task_type, t.perform_type,' +
487
+ ' t.task_state, t.operator, t.finish_time, t.expire_time, t.form_key, t.task_parent_id, t.variable,' +
488
+ ' t.create_time, t.create_user, t.update_time, t.update_user,' +
489
+ ' pd.name, pd.display_name, pd.version AS process_define_version,' +
490
+ ' pi.variable AS instance_variable, pi.create_time AS instance_create_time';
491
+ const rows = await conn.fetchAll(this.sql(`SELECT ${cols}${where} ORDER BY t.id DESC LIMIT ? OFFSET ?`), [filter, ...cond.params, pageSize, (pageNum - 1) * pageSize]);
492
+ return { rows: rows.map(r => this.mapTaskRow(r)), total };
493
+ }
494
+ finally {
495
+ await this.done(conn);
496
+ }
497
+ }
498
+ mapInstanceRow(r) {
499
+ let variables = {};
500
+ if (r.variable) {
501
+ try {
502
+ variables = JSON.parse(r.variable);
503
+ }
504
+ catch { /* 忽略坏 JSON */ }
505
+ }
506
+ return {
507
+ id: Number(r.id), parentId: r.parent_id != null ? Number(r.parent_id) : undefined,
508
+ defineId: Number(r.process_define_id), state: r.state,
509
+ parentNodeName: r.parent_node_name ?? '', businessNo: r.business_no ?? '', operator: r.operator ?? '',
510
+ expireTime: r.expire_time ?? undefined, variables,
511
+ createTime: r.create_time, createUser: r.create_user ?? '',
512
+ updateTime: r.update_time, updateUser: r.update_user ?? '',
513
+ defineName: r.name ?? '', defineDisplayName: r.display_name ?? '',
514
+ defineVersion: Number(r.version ?? 0),
515
+ };
516
+ }
517
+ // ═══ m_ 条件 WHERE 构建(issues/05-5,白名单 + 参数化,对齐 Java buildWhere) ═══
518
+ buildWhere(conditions, whitelist) {
519
+ let sql = '';
520
+ const params = [];
521
+ for (const c of conditions) {
522
+ if (!whitelist.has(c.column))
523
+ continue; // 不在白名单,丢弃
524
+ const val = c.value;
525
+ if (val == null || val === '')
526
+ continue;
527
+ switch (c.operator.toUpperCase()) {
528
+ case 'EQ':
529
+ sql += ` AND ${c.column} = ?`;
530
+ params.push(val);
531
+ break;
532
+ case 'NE':
533
+ sql += ` AND ${c.column} <> ?`;
534
+ params.push(val);
535
+ break;
536
+ case 'LIKE':
537
+ sql += ` AND ${c.column} LIKE ?`;
538
+ params.push(`%${val}%`);
539
+ break;
540
+ case 'LLIKE':
541
+ sql += ` AND ${c.column} LIKE ?`;
542
+ params.push(`%${val}`);
543
+ break;
544
+ case 'RLIKE':
545
+ sql += ` AND ${c.column} LIKE ?`;
546
+ params.push(`${val}%`);
547
+ break;
548
+ case 'GT':
549
+ sql += ` AND ${c.column} > ?`;
550
+ params.push(val);
551
+ break;
552
+ case 'GE':
553
+ sql += ` AND ${c.column} >= ?`;
554
+ params.push(val);
555
+ break;
556
+ case 'LT':
557
+ sql += ` AND ${c.column} < ?`;
558
+ params.push(val);
559
+ break;
560
+ case 'LE':
561
+ sql += ` AND ${c.column} <= ?`;
562
+ params.push(val);
563
+ break;
564
+ case 'IN':
565
+ case 'NIN': {
566
+ if (Array.isArray(val) && val.length > 0) {
567
+ const marks = val.map(() => '?').join(',');
568
+ sql += ` AND ${c.column} ${c.operator.toUpperCase() === 'IN' ? 'IN' : 'NOT IN'} (${marks})`;
569
+ params.push(...val);
570
+ }
571
+ break;
572
+ }
573
+ }
574
+ }
575
+ return { sql, params };
576
+ }
577
+ mapTaskRow(r) {
578
+ let variables = {};
579
+ if (r.variable) {
580
+ try {
581
+ variables = JSON.parse(r.variable);
582
+ }
583
+ catch { /* 忽略坏 JSON */ }
584
+ }
585
+ return {
586
+ id: Number(r.id), processInstanceId: Number(r.process_instance_id), taskName: r.task_name,
587
+ displayName: r.display_name, taskType: Number(r.task_type), performType: Number(r.perform_type),
588
+ taskState: r.task_state, operator: r.operator ?? '', finishTime: r.finish_time ?? undefined,
589
+ expireTime: r.expire_time ?? undefined, formKey: r.form_key ?? '',
590
+ taskParentId: r.task_parent_id != null ? Number(r.task_parent_id) : undefined,
591
+ variables, createTime: r.create_time, createUser: r.create_user ?? '',
592
+ updateTime: r.update_time, updateUser: r.update_user ?? '',
593
+ processDefineName: r.name ?? '', processDefineDisplayName: r.display_name ?? '',
594
+ defineVersion: Number(r.process_define_version ?? 0),
595
+ instanceVariable: r.instance_variable ?? '', instanceCreateTime: r.instance_create_time,
596
+ };
597
+ }
402
598
  // pageCcInstances 我的抄送分页(v1.3.0):cc 表 join 实例 + 定义,按抄送人过滤(对齐 Java pageCcInstances)
403
- async pageCcInstances(pageNum, pageSize, actorId) {
599
+ async pageCcInstances(pageNum, pageSize, actorId, conditions) {
600
+ const cond = this.buildWhere(conditions ?? [], CC_WHITELIST);
404
601
  const where = ' FROM wf_process_instance t' +
405
602
  ' LEFT JOIN wf_process_define pd ON t.process_define_id = pd.id' +
406
603
  ' LEFT JOIN wf_process_cc_instance cc ON t.id = cc.process_instance_id' +
407
- ' WHERE cc.actor_id = ?';
604
+ ' WHERE cc.actor_id = ?' + cond.sql;
408
605
  const cols = 't.id, t.parent_id, t.process_define_id, t.state, t.parent_node_name, t.business_no,' +
409
606
  ' t.operator, t.expire_time, t.variable, t.create_time, t.create_user, t.update_time, t.update_user,' +
410
607
  ' pd.name, pd.display_name, pd.version';
411
608
  const conn = await this.c();
412
609
  try {
413
- const countRow = await conn.fetchOne(this.sql('SELECT COUNT(*) ' + where), [actorId]);
610
+ const countRow = await conn.fetchOne(this.sql('SELECT COUNT(*) ' + where), [actorId, ...cond.params]);
414
611
  const total = Number(Object.values(countRow)[0] ?? 0);
415
- const rows = await conn.fetchAll(this.sql(`SELECT ${cols}${where} ORDER BY t.id ASC LIMIT ? OFFSET ?`), [actorId, pageSize, (pageNum - 1) * pageSize]);
612
+ const rows = await conn.fetchAll(this.sql(`SELECT ${cols}${where} ORDER BY t.id ASC LIMIT ? OFFSET ?`), [actorId, ...cond.params, pageSize, (pageNum - 1) * pageSize]);
416
613
  return {
417
614
  rows: rows.map(r => this.mapCcRow(r)),
418
615
  total,
@@ -1,5 +1,5 @@
1
1
  import { ProcessDesign, ProcessDesignHis, ProcessSurrogate } from './model.js';
2
- import type { ProcessExtRepository } from './spi.js';
2
+ import type { ProcessExtRepository, QueryCondition } from './spi.js';
3
3
  export declare class MemoryExtRepository implements ProcessExtRepository {
4
4
  private designs;
5
5
  private designHis;
@@ -9,13 +9,13 @@ export declare class MemoryExtRepository implements ProcessExtRepository {
9
9
  saveDesign(d: ProcessDesign): Promise<void>;
10
10
  updateDesign(d: ProcessDesign): Promise<void>;
11
11
  removeDesign(id: number): Promise<void>;
12
- pageDesigns(_pageNum?: number, _pageSize?: number, _filters?: Record<string, any>): Promise<[ProcessDesign[], number]>;
12
+ pageDesigns(_pageNum?: number, _pageSize?: number, _filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessDesign[], number]>;
13
13
  saveDesignHis(his: ProcessDesignHis): Promise<void>;
14
14
  listDesignHis(designId: number): Promise<ProcessDesignHis[]>;
15
15
  findSurrogateById(id: number): Promise<ProcessSurrogate | null>;
16
16
  saveSurrogate(s: ProcessSurrogate): Promise<void>;
17
17
  updateSurrogate(s: ProcessSurrogate): Promise<void>;
18
18
  removeSurrogate(id: number): Promise<void>;
19
- pageSurrogates(_pageNum?: number, _pageSize?: number, _filters?: Record<string, any>): Promise<[ProcessSurrogate[], number]>;
19
+ pageSurrogates(_pageNum?: number, _pageSize?: number, filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessSurrogate[], number]>;
20
20
  getSurrogate(operator: string, processName: string, at?: Date): Promise<ProcessSurrogate | null>;
21
21
  }
@@ -1,4 +1,24 @@
1
1
  // 扩展仓储内存实现(v1.1.0,测试/演示用)
2
+ import { matchConditions } from './memory.js';
3
+ // ═══ 条件匹配基建(issues/05-5) ═══
4
+ const DESIGN_FIELDS = {
5
+ 't.id': 'id', 't.name': 'name', 't.display_name': 'displayName', 't.type': 'type',
6
+ 't.is_deployed': 'isDeployed', 't.remark': 'remark',
7
+ 't.create_time': 'createTime', 't.update_time': 'updateTime',
8
+ };
9
+ const SURROGATE_FIELDS = {
10
+ 't.id': 'id', 't.process_name': 'processName', 't.operator': 'operator',
11
+ 't.surrogate': 'surrogate', 't.enabled': 'enabled',
12
+ 't.start_time': 'startTime', 't.end_time': 'endTime',
13
+ 't.create_time': 'createTime', 't.update_time': 'updateTime',
14
+ };
15
+ function pickFields(row, map) {
16
+ const fields = {};
17
+ for (const [col, key] of Object.entries(map)) {
18
+ fields[col] = row[key];
19
+ }
20
+ return fields;
21
+ }
2
22
  export class MemoryExtRepository {
3
23
  designs = new Map();
4
24
  designHis = new Map();
@@ -24,8 +44,9 @@ export class MemoryExtRepository {
24
44
  this.designs.delete(id);
25
45
  this.designHis.delete(id);
26
46
  }
27
- async pageDesigns(_pageNum = 1, _pageSize = 10, _filters) {
28
- return [[...this.designs.values()], this.designs.size];
47
+ async pageDesigns(_pageNum = 1, _pageSize = 10, _filters, conditions) {
48
+ const rows = [...this.designs.values()].filter(d => matchConditions(conditions, pickFields(d, DESIGN_FIELDS)));
49
+ return [rows, rows.length];
29
50
  }
30
51
  // ── 设计历史 ──
31
52
  async saveDesignHis(his) {
@@ -61,8 +82,18 @@ export class MemoryExtRepository {
61
82
  async removeSurrogate(id) {
62
83
  this.surrogates.delete(id);
63
84
  }
64
- async pageSurrogates(_pageNum = 1, _pageSize = 10, _filters) {
65
- return [[...this.surrogates.values()], this.surrogates.size];
85
+ async pageSurrogates(_pageNum = 1, _pageSize = 10, filters, conditions) {
86
+ const rows = [...this.surrogates.values()].filter(s => {
87
+ for (const [col, val] of Object.entries(filters ?? {})) {
88
+ if (val == null || val === '')
89
+ continue;
90
+ const k = col === 'process_name' ? 'processName' : col;
91
+ if (String(s[k]) !== String(val))
92
+ return false;
93
+ }
94
+ return matchConditions(conditions, pickFields(s, SURROGATE_FIELDS));
95
+ });
96
+ return [rows, rows.length];
66
97
  }
67
98
  async getSurrogate(operator, processName, at = new Date()) {
68
99
  let fallback = null;
package/dist/memory.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import type { CcInstanceRow, ProcessDefine } from './model.js';
1
+ import type { CcInstanceRow, DefineRow, InstanceRow, TaskRow, ProcessDefine } from './model.js';
2
2
  import { type ProcessInstance, type ProcessTask } from './model.js';
3
- import type { ProcessRepository } from './spi.js';
3
+ import type { ProcessRepository, QueryCondition } from './spi.js';
4
+ /** 条件全匹配(操作符对齐 JDBC buildWhere;列不在字段中则跳过) */
5
+ export declare function matchConditions(conditions: QueryCondition[] | undefined, fields: Record<string, any>): boolean;
6
+ /** EQ:值或集合包含判断(pta.actor_id/cc.actor_id 为数组) */
7
+ export declare function eqValue(v: any, expect: any): boolean;
4
8
  export declare class MemoryRepository implements ProcessRepository {
5
9
  private defines;
6
10
  private instances;
@@ -29,7 +33,24 @@ export declare class MemoryRepository implements ProcessRepository {
29
33
  removeTaskActor(taskId: number, actors: string[]): Promise<void>;
30
34
  createCcInstance(instanceId: number, _creator: string, ...actorIds: string[]): Promise<void>;
31
35
  updateCcStatus(_instanceId: number, _actorId: string): Promise<void>;
32
- pageCcInstances(pageNum: number | undefined, pageSize: number | undefined, actorId: string): Promise<{
36
+ pageDefines(pageNum?: number, pageSize?: number, conditions?: QueryCondition[]): Promise<{
37
+ rows: DefineRow[];
38
+ total: number;
39
+ }>;
40
+ pageInstances(pageNum: number | undefined, pageSize: number | undefined, operator: string, conditions?: QueryCondition[]): Promise<{
41
+ rows: InstanceRow[];
42
+ total: number;
43
+ }>;
44
+ pageTodoTasks(pageNum: number | undefined, pageSize: number | undefined, actorId: string, conditions?: QueryCondition[]): Promise<{
45
+ rows: TaskRow[];
46
+ total: number;
47
+ }>;
48
+ pageDoneTasks(pageNum: number | undefined, pageSize: number | undefined, operator: string, conditions?: QueryCondition[]): Promise<{
49
+ rows: TaskRow[];
50
+ total: number;
51
+ }>;
52
+ private taskRow;
53
+ pageCcInstances(pageNum: number | undefined, pageSize: number | undefined, actorId: string, conditions?: QueryCondition[]): Promise<{
33
54
  rows: CcInstanceRow[];
34
55
  total: number;
35
56
  }>;
package/dist/memory.js CHANGED
@@ -1,4 +1,97 @@
1
+ import { TaskState } from './model.js';
1
2
  import { cloneInstance, cloneTask } from './model.js';
3
+ // ═══ 条件匹配基建(issues/05-5,对齐 JDBC 白名单语义) ═══
4
+ // 行字段映射(列名 → 行属性,白名单列均可匹配)
5
+ const TASK_FIELDS = {
6
+ 't.id': 'id', 't.task_name': 'taskName', 't.display_name': 'displayName',
7
+ 't.task_type': 'taskType', 't.perform_type': 'performType', 't.task_state': 'taskState',
8
+ 't.operator': 'operator', 't.form_key': 'formKey', 't.create_time': 'createTime',
9
+ 't.finish_time': 'finishTime', 't.expire_time': 'expireTime',
10
+ 't.process_instance_id': 'processInstanceId', 't.task_parent_id': 'taskParentId',
11
+ 'pd.name': 'processDefineName', 'pd.display_name': 'processDefineDisplayName',
12
+ 'pd.version': 'defineVersion',
13
+ };
14
+ const INSTANCE_FIELDS = {
15
+ 't.id': 'id', 't.parent_id': 'parentId', 't.process_define_id': 'defineId',
16
+ 't.state': 'state', 't.parent_node_name': 'parentNodeName', 't.business_no': 'businessNo',
17
+ 't.operator': 'operator', 't.expire_time': 'expireTime', 't.create_time': 'createTime',
18
+ 'pd.name': 'defineName', 'pd.display_name': 'defineDisplayName', 'pd.version': 'defineVersion',
19
+ };
20
+ const DEFINE_FIELDS = {
21
+ 't.id': 'id', 't.name': 'name', 't.display_name': 'displayName', 't.type': 'type',
22
+ 't.state': 'state', 't.version': 'version', 't.create_time': 'createTime',
23
+ 't.update_time': 'updateTime',
24
+ };
25
+ /** 行字段提取(列名 → 值) */
26
+ function pickFields(row, map) {
27
+ const fields = {};
28
+ for (const [col, key] of Object.entries(map)) {
29
+ fields[col] = row[key];
30
+ }
31
+ return fields;
32
+ }
33
+ /** 条件全匹配(操作符对齐 JDBC buildWhere;列不在字段中则跳过) */
34
+ export function matchConditions(conditions, fields) {
35
+ for (const c of conditions ?? []) {
36
+ const v = fields[c.column];
37
+ const expect = c.value;
38
+ if (v == null || expect == null)
39
+ continue;
40
+ switch (c.operator.toUpperCase()) {
41
+ case 'EQ':
42
+ if (!eqValue(v, expect))
43
+ return false;
44
+ break;
45
+ case 'NE':
46
+ if (eqValue(v, expect))
47
+ return false;
48
+ break;
49
+ case 'LIKE':
50
+ if (!String(v).includes(String(expect)))
51
+ return false;
52
+ break;
53
+ case 'LLIKE':
54
+ if (!String(v).endsWith(String(expect)))
55
+ return false;
56
+ break;
57
+ case 'RLIKE':
58
+ if (!String(v).startsWith(String(expect)))
59
+ return false;
60
+ break;
61
+ case 'GT':
62
+ if (Number(v) <= Number(expect))
63
+ return false;
64
+ break;
65
+ case 'GE':
66
+ if (Number(v) < Number(expect))
67
+ return false;
68
+ break;
69
+ case 'LT':
70
+ if (Number(v) >= Number(expect))
71
+ return false;
72
+ break;
73
+ case 'LE':
74
+ if (Number(v) > Number(expect))
75
+ return false;
76
+ break;
77
+ case 'IN':
78
+ if (!Array.isArray(expect) || !expect.includes(v))
79
+ return false;
80
+ break;
81
+ case 'NIN':
82
+ if (Array.isArray(expect) && expect.includes(v))
83
+ return false;
84
+ break;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+ /** EQ:值或集合包含判断(pta.actor_id/cc.actor_id 为数组) */
90
+ export function eqValue(v, expect) {
91
+ if (Array.isArray(v))
92
+ return v.includes(expect);
93
+ return String(v) === String(expect);
94
+ }
2
95
  export class MemoryRepository {
3
96
  defines = new Map();
4
97
  instances = new Map();
@@ -162,8 +255,90 @@ export class MemoryRepository {
162
255
  this.ccInstances.set(instanceId, existing);
163
256
  }
164
257
  async updateCcStatus(_instanceId, _actorId) { }
258
+ // ── 核心表分页(v1.5.0)──
259
+ async pageDefines(pageNum = 1, pageSize = 10, conditions) {
260
+ const rows = [...this.defines.values()].map(d => ({
261
+ id: d.id, name: d.name, displayName: d.displayName, type: d.type,
262
+ state: d.state, version: d.version,
263
+ createTime: d.createTime, createUser: d.createUser,
264
+ updateTime: d.updateTime, updateUser: d.updateUser,
265
+ })).filter(r => matchConditions(conditions, pickFields(r, DEFINE_FIELDS)));
266
+ const total = rows.length;
267
+ const start = (pageNum - 1) * pageSize;
268
+ return { rows: rows.slice(start, start + pageSize), total };
269
+ }
270
+ async pageInstances(pageNum = 1, pageSize = 10, operator, conditions) {
271
+ const rows = [];
272
+ for (const inst of this.instances.values()) {
273
+ if (operator && inst.operator !== operator)
274
+ continue;
275
+ const def = this.defines.get(inst.defineId);
276
+ const r = {
277
+ id: inst.id, parentId: inst.parentId, defineId: inst.defineId, state: inst.state,
278
+ parentNodeName: inst.parentNodeName, businessNo: inst.businessNo, operator: inst.operator,
279
+ expireTime: inst.expireTime, variables: { ...inst.variables },
280
+ createTime: inst.createTime, createUser: inst.createUser,
281
+ updateTime: inst.updateTime, updateUser: inst.updateUser,
282
+ defineName: def?.name ?? '', defineDisplayName: def?.displayName ?? '',
283
+ defineVersion: def?.version ?? 0,
284
+ };
285
+ if (matchConditions(conditions, pickFields(r, INSTANCE_FIELDS)))
286
+ rows.push(r);
287
+ }
288
+ const total = rows.length;
289
+ const start = (pageNum - 1) * pageSize;
290
+ return { rows: rows.slice(start, start + pageSize), total };
291
+ }
292
+ async pageTodoTasks(pageNum = 1, pageSize = 10, actorId, conditions) {
293
+ const rows = [];
294
+ for (const t of this.tasks.values()) {
295
+ if (t.taskState !== TaskState.Doing)
296
+ continue;
297
+ if (actorId && !(this.actors.get(t.id) ?? []).includes(actorId))
298
+ continue;
299
+ const r = this.taskRow(t);
300
+ const fields = pickFields(r, TASK_FIELDS);
301
+ fields['pta.actor_id'] = this.actors.get(t.id) ?? [];
302
+ if (matchConditions(conditions, fields))
303
+ rows.push(r);
304
+ }
305
+ const total = rows.length;
306
+ const start = (pageNum - 1) * pageSize;
307
+ return { rows: rows.slice(start, start + pageSize), total };
308
+ }
309
+ async pageDoneTasks(pageNum = 1, pageSize = 10, operator, conditions) {
310
+ const rows = [];
311
+ for (const t of this.tasks.values()) {
312
+ if (t.taskState === TaskState.Doing)
313
+ continue;
314
+ if (operator && t.actorId !== operator)
315
+ continue;
316
+ const r = this.taskRow(t);
317
+ if (matchConditions(conditions, pickFields(r, TASK_FIELDS)))
318
+ rows.push(r);
319
+ }
320
+ const total = rows.length;
321
+ const start = (pageNum - 1) * pageSize;
322
+ return { rows: rows.slice(start, start + pageSize), total };
323
+ }
324
+ taskRow(t) {
325
+ const inst = this.instances.get(t.processInstanceId);
326
+ const def = inst ? this.defines.get(inst.defineId) : undefined;
327
+ return {
328
+ id: t.id, processInstanceId: t.processInstanceId, taskName: t.taskName,
329
+ displayName: t.displayName, taskType: t.taskType, performType: t.performType,
330
+ taskState: t.taskState, operator: t.actorId ?? '', finishTime: t.finishTime,
331
+ expireTime: t.expireTime, formKey: t.formKey ?? '', taskParentId: t.parentTaskId,
332
+ variables: { ...t.variables }, createTime: t.createTime, createUser: t.createUser,
333
+ updateTime: t.updateTime, updateUser: t.updateUser,
334
+ processDefineName: def?.name ?? '', processDefineDisplayName: def?.displayName ?? '',
335
+ defineVersion: def?.version ?? 0,
336
+ instanceVariable: inst ? JSON.stringify(inst.variables ?? {}) : '',
337
+ instanceCreateTime: inst?.createTime ?? t.createTime,
338
+ };
339
+ }
165
340
  // pageCcInstances 我的抄送分页(v1.3.0):按抄送人 actorId 过滤,join 实例 + 定义
166
- async pageCcInstances(pageNum = 1, pageSize = 10, actorId) {
341
+ async pageCcInstances(pageNum = 1, pageSize = 10, actorId, conditions) {
167
342
  const rows = [];
168
343
  for (const [instId, actors] of this.ccInstances) {
169
344
  if (actorId && !actors.includes(actorId))
@@ -172,7 +347,7 @@ export class MemoryRepository {
172
347
  if (!inst)
173
348
  continue;
174
349
  const def = this.defines.get(inst.defineId);
175
- rows.push({
350
+ const r = {
176
351
  id: inst.id, parentId: inst.parentId, defineId: inst.defineId, state: inst.state,
177
352
  parentNodeName: inst.parentNodeName, businessNo: inst.businessNo, operator: inst.operator,
178
353
  expireTime: inst.expireTime, variables: { ...inst.variables },
@@ -180,7 +355,11 @@ export class MemoryRepository {
180
355
  updateTime: inst.updateTime, updateUser: inst.updateUser,
181
356
  defineName: def?.name ?? '', defineDisplayName: def?.displayName ?? '',
182
357
  defineVersion: def?.version ?? 0,
183
- });
358
+ };
359
+ const fields = pickFields(r, INSTANCE_FIELDS);
360
+ fields['cc.actor_id'] = actors;
361
+ if (matchConditions(conditions, fields))
362
+ rows.push(r);
184
363
  }
185
364
  const total = rows.length;
186
365
  const start = (pageNum - 1) * pageSize;
package/dist/model.d.ts CHANGED
@@ -225,3 +225,57 @@ export interface CcInstanceRow {
225
225
  defineDisplayName: string;
226
226
  defineVersion: number;
227
227
  }
228
+ export interface DefineRow {
229
+ id: number;
230
+ name: string;
231
+ displayName: string;
232
+ type: string;
233
+ state: number;
234
+ version: number;
235
+ createTime: Date;
236
+ createUser: string;
237
+ updateTime: Date;
238
+ updateUser: string;
239
+ }
240
+ export interface InstanceRow {
241
+ id: number;
242
+ parentId?: number;
243
+ defineId: number;
244
+ state: InstanceState;
245
+ parentNodeName: string;
246
+ businessNo: string;
247
+ operator: string;
248
+ expireTime?: Date;
249
+ variables: Record<string, any>;
250
+ createTime: Date;
251
+ createUser: string;
252
+ updateTime: Date;
253
+ updateUser: string;
254
+ defineName: string;
255
+ defineDisplayName: string;
256
+ defineVersion: number;
257
+ }
258
+ export interface TaskRow {
259
+ id: number;
260
+ processInstanceId: number;
261
+ taskName: string;
262
+ displayName: string;
263
+ taskType: number;
264
+ performType: number;
265
+ taskState: TaskState;
266
+ operator: string;
267
+ finishTime?: Date;
268
+ expireTime?: Date;
269
+ formKey: string;
270
+ taskParentId?: number;
271
+ variables: Record<string, any>;
272
+ createTime: Date;
273
+ createUser: string;
274
+ updateTime: Date;
275
+ updateUser: string;
276
+ processDefineName: string;
277
+ processDefineDisplayName: string;
278
+ defineVersion: number;
279
+ instanceVariable: string;
280
+ instanceCreateTime: Date;
281
+ }
package/dist/spi.d.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { CcInstanceRow, ProcessDefine, ProcessDesign, ProcessDesignHis, ProcessInstance, ProcessSurrogate, ProcessTask, UserInfo } from './model.js';
1
+ import type { CcInstanceRow, DefineRow, InstanceRow, TaskRow, ProcessDefine, ProcessDesign, ProcessDesignHis, ProcessInstance, ProcessSurrogate, ProcessTask, UserInfo } from './model.js';
2
+ export interface QueryCondition {
3
+ column: string;
4
+ operator: string;
5
+ value: any;
6
+ }
2
7
  export interface ProcessRepository {
3
8
  findDefineById(id: number): Promise<ProcessDefine | null>;
4
9
  findDefineByName(name: string): Promise<ProcessDefine | null>;
@@ -20,10 +25,26 @@ export interface ProcessRepository {
20
25
  removeTaskActor(taskId: number, actors: string[]): Promise<void>;
21
26
  createCcInstance(instanceId: number, creator: string, ...actorIds: string[]): Promise<void>;
22
27
  updateCcStatus(instanceId: number, actorId: string): Promise<void>;
23
- pageCcInstances(pageNum: number, pageSize: number, actorId: string): Promise<{
28
+ pageCcInstances(pageNum: number, pageSize: number, actorId: string, conditions?: QueryCondition[]): Promise<{
24
29
  rows: CcInstanceRow[];
25
30
  total: number;
26
31
  }>;
32
+ pageDefines(pageNum: number, pageSize: number, conditions?: QueryCondition[]): Promise<{
33
+ rows: DefineRow[];
34
+ total: number;
35
+ }>;
36
+ pageInstances(pageNum: number, pageSize: number, operator: string, conditions?: QueryCondition[]): Promise<{
37
+ rows: InstanceRow[];
38
+ total: number;
39
+ }>;
40
+ pageTodoTasks(pageNum: number, pageSize: number, actorId: string, conditions?: QueryCondition[]): Promise<{
41
+ rows: TaskRow[];
42
+ total: number;
43
+ }>;
44
+ pageDoneTasks(pageNum: number, pageSize: number, operator: string, conditions?: QueryCondition[]): Promise<{
45
+ rows: TaskRow[];
46
+ total: number;
47
+ }>;
27
48
  }
28
49
  export interface UserProvider {
29
50
  getUser(userId: string): Promise<UserInfo | null>;
@@ -39,13 +60,13 @@ export interface ProcessExtRepository {
39
60
  saveDesign(d: ProcessDesign): Promise<void>;
40
61
  updateDesign(d: ProcessDesign): Promise<void>;
41
62
  removeDesign(id: number): Promise<void>;
42
- pageDesigns(pageNum?: number, pageSize?: number, filters?: Record<string, any>): Promise<[ProcessDesign[], number]>;
63
+ pageDesigns(pageNum?: number, pageSize?: number, filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessDesign[], number]>;
43
64
  saveDesignHis(his: ProcessDesignHis): Promise<void>;
44
65
  listDesignHis(designId: number): Promise<ProcessDesignHis[]>;
45
66
  findSurrogateById(id: number): Promise<ProcessSurrogate | null>;
46
67
  saveSurrogate(s: ProcessSurrogate): Promise<void>;
47
68
  updateSurrogate(s: ProcessSurrogate): Promise<void>;
48
69
  removeSurrogate(id: number): Promise<void>;
49
- pageSurrogates(pageNum?: number, pageSize?: number, filters?: Record<string, any>): Promise<[ProcessSurrogate[], number]>;
70
+ pageSurrogates(pageNum?: number, pageSize?: number, filters?: Record<string, any>, conditions?: QueryCondition[]): Promise<[ProcessSurrogate[], number]>;
50
71
  getSurrogate(operator: string, processName: string, at?: Date): Promise<ProcessSurrogate | null>;
51
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mldong/jeeflow",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "jeeflow workflow engine — Node.js / TypeScript implementation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,9 @@
29
29
  ],
30
30
  "license": "Apache-2.0",
31
31
  "devDependencies": {
32
+ "@types/express": "^4.17.25",
32
33
  "@types/pg": "^8.20.3",
34
+ "express": "^4.22.2",
33
35
  "tsx": "^4.19.0",
34
36
  "typescript": "^5.5.0"
35
37
  },