目标: 按 existing query / GraphQL where 语法调整 relation filter,不再引入 source.* / target.* 专用前缀。
修改:
- relation filter 目标字段统一使用
<relationField>.<field>,例如activeStudents.status。 _field中引用当前表字段时直接使用裸路径,例如{ "_field": "id" }。RelationFilterSupport负责路径识别、SQL alias 替换和懒加载目标过滤解析;支持_and/_or与反向字段比较。- SQL join、Mongo
$lookuppipeline/let、嵌套查询与懒加载统一使用新路径规则;Mongo 显式 join filter 继续沿用旧查询 alias 语法。 - 更新 FML 解析回归测试、SQL/Mongo 渲染测试、UI filter 提示与技术方案文档。
- 同步
flexmodel-website的records.md与modeling.md:说明策略、filter 元数据、REST 展开行为、路径规则与当前 GraphQL 支持范围。 - 网站教程进一步补充策略推断规则、条件关联谓词要求、反向路径写法、索引建议与执行支持边界。
验证:
mvn test -q -pl flexmodel-engine通过。mvn -pl flexmodel-engine/flexmodel-core test "-Dtest=ASTNodeConverterTest,RelationFilterTest"通过(11 tests,0 failures,0 errors)。mvn compile -q -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。git diff --check通过。flexmodel-website npm run build通过。- 2026-09-24 完整运行
./init.sh:全模块 clean compile 与flexmodel-enginetests 通过;当前环境未安装 Node,UI E2E 按脚本跳过。
遗留/风险:
- Condition 关联依赖高频目标字段索引,索引提示仍待后续完善。
目标: 修复 ModelRef filter 在 SQL、MongoDB、REST expand、懒加载和 UI 提交链路中的语义漂移,优先消除静默错数据与运行时错误。
修复:
- Mongo 手动
join.where恢复为目标文档普通匹配;模型级 relation filter 与调用方 filter 分开渲染并以$and叠加,不再让调用方条件覆盖模型声明。 - Mongo 普通查询中的
_field渲染为文档字段$field,不再生成未声明$$变量;源字段_between生成合法的$expr/$and/$gte/$lte聚合表达式。 FOREIGN_KEY + 纯目标 filter的懒加载、expand 与 join 均应用 filter;expand 路径改为批量查询,避免常见场景 N+1。- UI 新建/编辑 ModelRef 完整透传
strategy、filter、from、localField、foreignField、cascadeDelete与multiple,编辑时不再丢失已保存 filter。 - 源字段显式
null可参与过滤;源数据缺少引用字段时抛出明确错误,不再触发Map.ofNPE。 - SQL builder 不再修改共享
Query.Join,replaceAliases幂等;_in/_nin集合内_field在 SQL 与 Mongo 均可解析。 - FML 对象字面量保留数字与布尔原始类型;
filter支持 JSON 字符串;FML 输出可 round-trip。 - FML
filter支持同一对象中的多个顶层条件,语义等价于_and;已补充解析回归测试并修正dev_test.fml的多条件示例。 CONDITION解析阶段强制包含至少一个当前表字段与目标字段比较,拒绝仅有常量目标过滤的错误谓词。- 单数 condition/filter 关联在 expand 与懒加载中匹配多条均抛出明确错误;多个关系指向同一目标模型时要求使用关系字段名作为 alias,避免歧义。
- GraphQL 关系 resolver 从 left join 调整为 inner join:复数关系无匹配返回空数组,有 N 条匹配返回 N 条,不再生成目标字段全为 null 的占位记录。
验证:
mvn -pl flexmodel-engine/flexmodel-core test "-Dtest=RelationFilterTest,ASTNodeConverterTest"通过(23 tests)。mvn test -q -pl flexmodel-engine通过。mvn compile -q -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。flexmodel-ui:针对性 ESLint、npx tsc --noEmit --pretty false、npm run build通过(仅既有 chunk size / Browserslist 警告)。flexmodel-website npm run build通过;根仓库、flexmodel-ui、flexmodel-website的git diff --check通过。mvn test -q -pl flexmodel-engine/flexmodel-graphql "-Dtest=GraphQLProviderTest#relationWithNoRowsReturnsEmptyList"通过;用例同时断言空关系返回[]、普通多条关系返回 2 条、filter 后关系返回 1 条。mvn test -q -pl flexmodel-engine/flexmodel-core "-Dtest=ASTNodeConverterTest"通过;mvn compile -q -pl flexmodel-server通过,确认dev_test.fml可解析。bash ./init.sh本次在 WSL 路径下编译 JavaCC 生成文件时失败;已改用原生 Windows Maven 执行同等mvn compile与mvn test -pl flexmodel-engine验证。mvn clean compile曾被运行中的 Quarkus dev 进程锁定 target jar 阻断。
遗留/风险:
- 引用当前表字段的
CONDITION关联在 expand 中仍按父行逐条查询;FOREIGN_KEY + 纯目标 filter已批量优化。通用 condition 批量执行需要目标结果与父行谓词的内存匹配器,建议作为后续独立优化。 - GraphQL relation resolver 统一接入尚未完成;当前覆盖 SQL / Mongo 查询构建、REST expand 与对象懒加载。
- 字段存在性、类型兼容性与操作符合法性的完整 schema 校验仍待后续增强。
目标: 让策略值与语义一致,避免 KEY 被误解为主键或普通键。
修改:
- 后端
RelationStrategy、默认策略、解析赋值与回归测试统一改为FOREIGN_KEY。 - UI 策略类型、默认值、表单选项、条件判断与英文文案统一改为
FOREIGN_KEY/Foreign key relation。 docs/plans/model-ref-filter-relation.md与功能清单中的策略值说明同步更新。
验证:
mvn test -q -pl flexmodel-engine通过。flexmodel-ui针对性 ESLint 通过;隔离无关未跟踪 UI 组件后的 TypeScript 项目检查通过;npx vite build通过。git diff --check通过;代码中无残留RelationStrategy.KEY或表单KEY策略值。
目标: 在建模表单中显式选择 FOREIGN_KEY / CONDITION 关联策略,并提供 filter 编辑、校验与提交转换。
修改:
FieldForm.tsx新增策略选择器;FOREIGN_KEY必填localField/foreignField,filter可选;CONDITION必填filter,隐藏并清理外键字段与cascadeDelete。filter通过 JSON 文本编辑,提交前校验必须为 JSON 对象并转换为结构化filter;filter与cascadeDelete互斥并在表单内自动处理。FieldList.tsx的 ModelRef 提示展示策略、外键或 filter 配置。fieldFormConstants.ts、data-modeling.d.ts与中英文 i18n 补充策略、filter、校验文案。
验证:
npx eslint src/pages/DataModeling/components/FieldForm.tsx src/pages/DataModeling/components/FieldList.tsx src/pages/DataModeling/components/fieldFormConstants.ts
通过。
- 中英文 locale JSON 解析通过;
git diff --check通过。 - 使用临时 tsconfig 排除工作区既有未跟踪 UI 组件后,
npx tsc -p ...项目级类型检查通过。 npx vite build通过;未隔离的npm run build仍被既有未跟踪 UI 组件的缺失依赖与类型错误阻断(lucide-react、Radix、sonner等)。
目标: 为 @relation 支持 filter,实现 FOREIGN_KEY 关联附加过滤与无外键的 CONDITION 关联。
修改:
- 新增
RelationStrategy、FieldReference、RelationFilterSupport。 ModelRefField保存filter与strategy;FML 注解支持对象字面量。ASTNodeConverter解析<relationField>.*目标字段与裸路径当前表字段 filter,校验 key/filter/cascadeDelete 组合。SqlStatementBuilder支持 FOREIGN_KEY+filter 与 condition-only join。MongoStatementBuilder支持$lookuppipeline/let。BaseService嵌套查询与LazyLoadInterceptor支持当前表字段引用解析。- 新增
RelationFilterTest和ASTNodeConverterTest回归用例。
验证:
mvn clean compile -q -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。mvn test -q -pl flexmodel-engine通过。mvn -pl flexmodel-engine/flexmodel-core test "-Dtest=ASTNodeConverterTest,RelationFilterTest"通过(10 tests, 0 failures)。
遗留/风险:
- Condition 关联没有物理外键,性能依赖目标字段索引;后续可在 UI/校验层补充索引提示。
- 当前未增加 UI 表单配置入口,FML 已可直接使用新语法。
目标: 为 @relation 增加 filter 语义,明确键关联、条件关联、cardinality、生命周期和跨库执行策略。
修改:
- 新增
docs/plans/model-ref-filter-relation.md,包含语法设计、元数据模型、解析器扩展、SQL/MongoDB/GraphQL 翻译规则、校验策略和实施阶段。 - 明确 v1 中
cascadeDelete与filter互斥,condition 关联不生成外键或 DDL。 - 明确现有
localField/foreignField关联保持兼容。
验证: 文档已人工校对;本次为设计文档产出,未修改运行时代码。
目标: 移除 OpenTelemetry 运行时依赖,保留轻量 traceId 生成/传播;将各类日志迁移到所属功能包与前端功能路由下。
完成内容:
- 新增
common.trace轻量 trace 上下文,替换 OTel span/span context;HTTP、Quartz、EventBus、函数运行时调用均继续生成或传递 W3Ctraceparent。 - 删除 FmSpanExporter、Span 落库、链路列表后端接口、链路资源、日志清理任务和
quarkus-opentelemetry配置。 - 接口日志迁入
dev.flexmodel.apilog,审计日志迁入dev.flexmodel.data,函数日志迁入dev.flexmodel.functions,任务执行日志保留在dev.flexmodel.scheduling。 - 前端移除可观测性菜单:API 日志移到 API,审计日志移到数据,函数日志移到边缘函数,任务执行日志移到任务调度;trace 详情改用轻量
/trace/:traceId路由。
验证:
mvn compile -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。mvn test -pl flexmodel-server -Dtest=ApiLogResourceTest,TriggerResourceTest通过(18 tests, 0 failures, 0 errors)。npx tsc --noEmit(flexmodel-ui)通过。
遗留/风险:
ApiLogResourceIT存在原有@Inject is not supported in @QuarkusIntegrationTest问题,不属于本次改动引入;后续需要单独修复。
背景: flow 事件桥接(events-out)与 realtime 数据变更桥接(data-events-out)共享同一 connector、同一
flexmodel.events topic 交换机、同一开关 flexmodel.events.rabbitmq.enabled,仅载荷类型不同。当前无分开使用的实际
场景,合并减少一个 AMQP channel 与一处配置。
修改:
- 新增
dev.flexmodel.common.FlexmodelEvent标记接口(出站事件载荷统一类型)。 FlowEvent(抽象基类)与DataChangeEvent实现FlexmodelEvent。FlowEventRabbitmqBridge、RealtimeRabbitmqListener统一注入@Channel("events-out") Instance<MutinyEmitter<FlexmodelEvent>>。application.properties删除mp.messaging.outgoing.data-events-out.*两行。DataChangeEventjavadoc 通道名同步更新为events-out。
设计效果: 单一出站通道 events-out,消费端按 routing key(data.* / flow.*)区分流类型,零改动。 native image
反射注册无需调整(两具体类型包 dev.flexmodel.flow.event.**、dev.flexmodel.realtime.** 已注册)。
验证:
- 服务模块编译(
build_project指定改动文件)→ 通过,无错误。 - 改动文件 lint 仅余既有 warning(Lombok @Getter 提示、
@ConsumeEvent方法 "never used" 误报、预存未用 import),非本次引入。
修改:
flexmodel-ui/src/pages/Flow/components/UserTasksDrawer.tsx:流程实例完成时间由不存在的modifyTime改为后端返回的updatedAt;开始时间由不存在的createTime改为createdAt。
验证:
npx tsc -b→ 通过。npm run build(tsc -b && vite build)→ 通过。
背景: flow.usertask.rollback.suspended 因 . 分段导致事件后缀多出一段(4 段 vs 其他事件 3 段), 消费端绑定
flow.*.usertask.* 无法匹配该事件;且命名解析为「回滚被挂起」,与实际语义(任务因回滚被重新挂起)不符。
修改:
flexmodel-server/.../flow/event/FlowEventTypes.java:USER_TASK_ROLLBACK_SUSPENDED值改为flow.usertask.rollback-suspended(连字符合并动作段)。FlowEvent.rabbitmqRoutingKey()通用插入逻辑自动 生成flow.<projectId>.usertask.rollback-suspended,消费端flow.*.usertask.*现可匹配全部用户任务事件。flexmodel-website/docs/tutorial/features/flow.md:事件表、载荷示例、订阅示例全部改为flow.<projectId>.xxx格式(此前漏写 projectId 段);同步修正交换机名(flexmodel.flow.events→flexmodel.events,与 application.properties 及数据事件文档一致)与 转发通道名(flow-events-out→ connector 级默认events-out)。
验证:
mvn -q clean compile -pl '!flexmodel-engine/flexmodel-maven-plugin'→ 无 ERROR,BUILD 成功。@ConsumeEvent/UserTaskRollbackSuspendedEvent/UserTaskExecutor均引用常量,自动生效。
背景: feature/model-ref 分支此前将 RelationField 重命名为 ModelRefField、ScalarType.RELATION → MODEL_REF、
ScalarType.ENUM → ENUM_REF(commit d3be8f0、d8bb3cb)。源码与测试资源已全部更新且编译通过,但 flexmodel-engine/docs/ 下
3 个文档仍残留旧 API 名称。
修复:
flexmodel-engine/docs/API.md:将「关系字段」章节标题改为「模型引用字段」;将RelationField示例重写为ModelRefField新 API(setFrom/setLocalField/setForeignField/setMultiple);删除新 API 已不支持的多对多示例(setJoinTable等方法已移除)。flexmodel-engine/docs/flexmodel-core.md:new RelationField("studentClass")→new ModelRefField("studentClass")。flexmodel-engine/docs/flexmodel-codegen.md:it.isRelationField()→it.isModelRefField()。
验证:
mvn test -pl flexmodel-engine/flexmodel-core -o→ BUILD SUCCESS,Tests run: 49, Failures: 0, Errors: 0(含JsonSerializeTest)。- 全仓搜索
RelationField/isRelationField(docs 目录)→ 0 残留。
症状: Storage 页面点击「复制链接」后,把链接粘贴到浏览器地址栏无法访问(401 / 路径异常)。
根因(两个叠加问题):
- 下载接口(
BucketResource.downloadObject/headObject)始终要求 Bearer token,浏览器地址栏不带Authorization头 →AuthFilter抛Token is missing401。BucketVisibility.PUBLIC(公开 Bucket)的匿名读 语义虽在模型中定义,却从未被认证层实现。 LocalStorageOperations用basePath.relativize(p).toString()返回相对路径,分隔符为 OS 相关;Windows 下 为反斜杠,导致子目录文件的复制链接、面包屑与目录导航失效(S3 实现用正斜杠,无此问题)。
修复:
flexmodel-server/.../storage/config/LocalStorageOperations.java:新增toPosixPath(Path),listFiles与getFile返回的path统一为 POSIX 正斜杠,跨平台一致。flexmodel-server/.../common/config/web/filter/AuthFilter.java:注入BucketService,新增isAnonymousPublicBucketRead。无 Bearer token 时,对GET/HEAD命中projects/{projectId}/buckets/{bucketName}/objects(/.*)?的请求解析 Bucket;visibility 为PUBLIC时放行匿名读 (下载/HEAD/元数据/列表),PRIVATE/AUTHENTICATED及写操作仍要求认证。解析失败回退标准认证流程。flexmodel-ui/.../Storage/components/FileBrowser.tsx:新增visibility入参;handleCopyLink对path做反斜杠→正斜杠兜底归一;非 PUBLIC Bucket 复制时给出 warning 提示需设为公开。flexmodel-ui/.../Storage/index.tsx:向FileBrowser传入activeBucket.visibility。- i18n 新增
copy_link_not_public(zh/en)。
验证:
mvn compile -pl flexmodel-server -am→ BUILD SUCCESS。mvn test -pl flexmodel-server -Dtest=LocalStorageOperationsTest→ Tests run: 14, Failures: 0, Errors: 0。- IDE inspections(FileBrowser.tsx / index.tsx / AuthFilter.java / LocalStorageOperations.java)无 error。
行为说明: 公开 Bucket 的复制链接现在可在浏览器直接打开下载;非公开 Bucket 的链接仍需认证(符合可见性 设计),前端复制时会提示用户将 Bucket 设为「公开」以获得匿名访问。
Last Updated: 2026-08-04 Session ID: pages-deploy-eventloop-block Active Feature: 修复部署 pages 报错(事件循环被 JDBC 阻塞)
症状: 上传 zip 部署 pages 时报错;日志显示 LogEventConsumer 在 vert.x-eventloop-thread-0 上被 MySQL SSL socket
读阻塞 4176ms+(blocked-thread-checker 警告)。
根因(两个叠加问题):
LogEventConsumer.consume无@Blocking,阻塞 JDBC 写入直接跑在 Vert.x 事件循环线程上。- 生产 JDBC URL(docker-compose)无
connectTimeout/readTimeout(Connector/J 默认无限等待)。MySQL 容器重启后池中残留旧连接读到已死对端 → 无限阻塞 → 事件循环冻结 → 部署请求超时报错。
修复:
- 全部 5 个
@ConsumeEvent消费者统一改为@ConsumeEvent(value=..., blocking = true)(工作线程/虚拟线程执行,不再阻塞事件循环):LogEventConsumer(JDBC 写入,本次故障元凶)ProjectDeletedSchedulingConsumer(Quartz 操作走 JDBC f_qrtz_*)TriggerFlowEventConsumer(流程执行含 DB + HTTP 函数调用)SettingsEventConsumer/GraphQLEventConsumer(纯内存/日志,顺带统一)
-
docker-compose.ymlJDBC URL 添加connectTimeout=5000&readTimeout=15000(Connector/J 9.x 命名) -
PageResource.java类级@Blocking(zip 解包 + DB 写入均阻塞)
验证: mvn compile -pl flexmodel-server -am -q -o 通过。
遗留风险: JAX-RS 端点默认仍在事件循环上运行(本仓库无全局 @Blocking 约定),仅 pages 特性包已处理;其他含 DB 访问的
Resource 若遇 DB 挂死同样会冻结事件循环,建议后续按特性包逐个加 @Blocking。
-
deno.json— Deno project configuration with Hono.js dependency -
src/types.ts— Full TypeScript type definitions (FunctionMeta, InvokeRequest/Result, Worker messages, etc.) -
src/sdk/flexmodel.ts— RPC Dispatcher (proxies Worker SDK requests to Java REST API) -
src/runner/registry.ts— Function Registry (metadata cache + LRU source cache, lazy load from Java) -
src/runner/worker-entry.ts— Worker internal entry (addEventListener message handling, dynamic import, ctx SDK injection) -
src/runner/worker.ts— Worker executor (create Worker, timeout/terminate, proxy SDK RPC) -
src/router/health.ts— Health check endpoint (GET /health) -
src/router/functions.ts— Functions routes (POST /deploy, DELETE /:projectId/:name, POST /invoke) -
src/server.ts— Hono.js server initialization and route registration -
src/main.ts— Entry point (Deno.serve on configurable port)
- Extended
enum TriggerTypewithHTTP - Added
f_functionmodel (id, project_id, name, slug, description, entry_point, status, current_version, timeout, memory_limit, timestamps, indexes) - Added
f_function_versionmodel (id, function_id, version, source_code, timestamps, index) - Code generation verified —
Function.java,FunctionVersion.java,TriggerType.HTTPgenerated successfully
-
dto/FunctionCreateRequest.java— Create request with validation -
dto/FunctionUpdateRequest.java— Update request DTO -
dto/FunctionInvokeRequest.java— Invocation request (method, headers, body, query) -
dto/FunctionInvokeResponse.java— Invocation response with _meta (executionTimeMs, logs) -
dto/FunctionDeployRequest.java— Deploy request to Deno (metadata only) -
dto/FunctionResponse.java— API response with TriggerRef list -
dto/FunctionVersionResponse.java— Version list response -
dto/FunctionPageRequest.java— Pagination request -
FunctionException.java— Business exception for functions -
FunctionRepository.java— Repository interface -
FunctionFmRepository.java— Repository implementation (AbstractRepository pattern) -
FunctionVersionRepository.java— Version repository interface -
FunctionVersionFmRepository.java— Version repository implementation -
FunctionInvoker.java— HTTP client to Deno functions runtime (deploy, invoke, delete, healthCheck) -
FunctionService.java— Core service (CRUD, state machine, version management, invocation, auth validation, startup recovery) -
FunctionResource.java— REST endpoints (CRUD, trigger management, public invoke entry) -
FunctionInternalResource.java— Internal API for runtime lazy source loading
-
src/services/function.ts— API service layer (TypeScript interfaces + all CRUD/invoke/trigger endpoints) -
src/pages/Functions/index.tsx— Main list page (table with status tags, search/filter, pagination, create/edit/delete actions) -
src/pages/Functions/components/FunctionForm.tsx— Create/Edit modal (tabs: Basic Settings + Source Code + HTTP Trigger config) -
src/pages/Functions/components/FunctionDetail.tsx— Detail drawer (tabs: Overview + Code + Versions with rollback + Test Invoke) -
src/pages/Functions/components/FunctionInvokePanel.tsx— Test invoke panel (request builder with method/headers/body/query + response viewer with logs) -
src/locales/zh.json— Chinese translations (70+ keys for function management) -
src/locales/en.json— English translations (70+ keys) -
src/routes.tsx— Added Functions route with CodeOutlined icon under/project/:projectId/functions
- Phase 4: Integration Testing — Install Deno, start functions runtime, run end-to-end tests
- Source Code Viewing — Add frontend endpoint to retrieve source code for editing (currently requires re-pasting on update)
- V2 Enhancements — Worker Pool, Cron triggers, metrics, secrets management
- Deno not installed in current environment — functions runtime cannot be runtime-verified yet
- IDE lock on
flexmodel-server-dev.jarpreventsmvn clean(not caused by our changes)
- HTTP Client: Used Java 25 built-in
java.net.http.HttpClientinstead of Vert.x WebClient to avoid additional dependency - Configuration: Functions runtime host/port configurable via
flexmodel.functions-runtime.hostandflexmodel.functions-runtime.portproperties - Source Code Loading: Lazy load pattern — source code not sent at deploy time, loaded by runtime on first invoke via internal API
- Auth Validation: Implemented PUBLIC/JWT/API_KEY/INTERNAL auth modes per trigger config
- Startup Recovery: Only deploys metadata on startup (O(1) time), source code lazy-loaded
flexmodel-functions-runtime/deno.jsonflexmodel-functions-runtime/src/main.tsflexmodel-functions-runtime/src/server.tsflexmodel-functions-runtime/src/types.tsflexmodel-functions-runtime/src/router/functions.tsflexmodel-functions-runtime/src/router/health.tsflexmodel-functions-runtime/src/runner/registry.tsflexmodel-functions-runtime/src/runner/worker.tsflexmodel-functions-runtime/src/runner/worker-entry.tsflexmodel-functions-runtime/src/sdk/flexmodel.ts
flexmodel-server/src/main/java/dev/flexmodel/functions/FunctionException.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionRepository.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionFmRepository.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionVersionRepository.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionVersionFmRepository.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionInvoker.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionService.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionResource.javaflexmodel-server/src/main/java/dev/flexmodel/functions/FunctionInternalResource.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionCreateRequest.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionUpdateRequest.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionInvokeRequest.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionInvokeResponse.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionDeployRequest.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionResponse.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionVersionResponse.javaflexmodel-server/src/main/java/dev/flexmodel/functions/dto/FunctionPageRequest.java
flexmodel-server/src/main/resources/project.fml— Added f_function, f_function_version models + HTTP to TriggerTypeflexmodel-ui/src/routes.tsx— Added Functions route with CodeOutlined iconflexmodel-ui/src/locales/zh.json— Added 70+ function management translation keysflexmodel-ui/src/locales/en.json— Added 70+ function management translation keysfeature_list.json— Added feat-011progress.md— This file
- Compile:
mvn compile -pl flexmodel-server→ BUILD SUCCESS (424 source files, 0 errors) - Engine tests:
mvn test -pl flexmodel-engine -q→ all passed - Frontend TypeScript:
tsc --noEmit→ 0 errors - Frontend build:
npm run build(Vite) → built in 42.98s - Deno type-check:
deno check src/main.ts(Deno not installed in environment) - End-to-end test: create → deploy → invoke → update → delete (requires running functions runtime)
完善 TriggerResourceTest e2e 用例,在调用 Trigger REST 接口后,通过注入的 Scheduler 直接查询 Quartz 作业状态,确保
Quartz 作业被正确创建/移除/状态变更。
flexmodel-server/src/test/java/dev/flexmodel/rest/TriggerResourceTest.java- 新增 Quartz 断言辅助方法:
jobKey/triggerKey/assertScheduledInQuartz/assertNotScheduledInQuartz/unscheduleFromScheduler,镜像TriggerService.buildJobKey/buildTriggerKey/getJobGroup的命名规则(group =dev_test_flow_{jobId})。 testCreateIntervalTrigger/testCreateCronTrigger:创建 state=true SCHEDULED 触发器后断言 JobDetail/Trigger 存在、JobDataMap 携带 triggerId/jobId/projectId。testCreateEventTrigger:断言 EVENT 类型不创建 Quartz 作业。testCreateDisabledTriggerNotScheduled:state=false SCHEDULED 不创建 Quartz 作业。testPatchTriggerEnable/testPatchTriggerDisable:启用→创建调度任务,禁用→移除调度任务。testUpdateTrigger/testUpdateTriggerReschedules(新增):update 先 unschedule 再 schedule 的重建路径,禁用态不残留调度任务,且全程保留 seed 原始名称。testDeleteTrigger:删除前断言调度任务存在、删除后断言已移除。@AfterEach增强:清理 created/启用的 seed 触发器的 Quartz 调度任务(兜底deleteJob/unscheduleJob),并还原 seed 禁用态。
- 新增 Quartz 断言辅助方法:
flexmodel-server/src/main/java/dev/flexmodel/scheduling/TriggerService.java- 修复
create():对 state=false 的 SCHEDULED 触发器不再调用scheduleTrigger(与update()行为一致),避免禁用态触发器误注册 Quartz 作业。这是被新 e2e 用例暴露出的预存缺陷。
- 修复
mvn -pl flexmodel-server -am test -Dtest=TriggerResourceTest -Dsurefire.failIfNoSpecifiedTests=false→ BUILD SUCCESS,Tests run: 16, Failures: 0, Errors: 0, Skipped: 0。- TriggerResourceIT(@QuarkusIntegrationTest extends TriggerResourceTest)保持兼容(用例均为普通 HTTP+Scheduler 断言,无 JVM 模式不兼容 API)。
assertScheduledInQuartz不再强断言getTriggerState==NORMAL与nextFireTime:短间隔 SimpleTrigger 在断言前可能已触发完成被移除(含 startup-restore 触发的实际 Job 执行,会出现SessionContextNPE 噪声日志,属已知环境问题,不影响断言)。改为断言 JobDetail/Trigger 存在 + JobDataMap 内容 + 状态非 ERROR,保证稳定性。testDeleteTrigger改用 1 小时间隔创建触发器,避免调度任务在断言前被触发清理。- 启动恢复
restoreScheduledTriggersOnStartup对 seed EVENT 触发器不调度(符合预期),dev_test seed 中无 state=true 的 SCHEDULED 触发器。
修复项目分析中发现的 15 项优化问题(#6–#25, #33, #35)。
| # | 问题 | 修复 | 文件 |
|---|---|---|---|
| 6 | TurboException extends RuntimeException | → extends BusinessException | TurboException.java |
| 7 | 51 处 bare RuntimeException | → NotFoundException/InternalServerException/ValidationException; BusinessExceptionMapper 更新 | 19 files |
| 11 | DataService/ModelingService 硬编码 databaseName | → @Inject SessionContext + resolveDatabaseName() fallback | DataService.java, ModelingService.java |
| 12 | 缺少权限注解 | → @RequiresPermissions on User/Role/ApiKey/Resource Resource | 4 files |
| 14 | AuthService 职责过重 | → 拆分到 UserService/RoleService/ResourceService | 4 files |
| 16 | ConcurrentHashMapCache 无 null 保护 | → computeIfAbsent + NULL_SENTINEL | ConcurrentHashMapCache.java |
| 17 | SqlSession.close() 静默提交 | → rollback() + warn log | SqlSession.java |
| 19 | Zustand selector 未用 useShallow | → 全部 wrap useShallow | appStore.ts, authStore.ts |
| 20 | ECharts 全量引入 | → modular imports (utils/echarts.ts) | 3 files |
| 21 | i18n 重复 key + 错误翻译 | → 去重 + 修正 | en.json, zh.json |
| 23 | POM 依赖版本分散 | → 父 POM 统一 dependencyManagement/pluginManagement | 7 POMs |
| 24 | CDI.current() 滥用 | → 5 类迁移 @Inject; 3 类保留 CDI.current() + 注释 | 8 files |
| 25 | e.printStackTrace() | → log.error() | DefinitionProcessor.java, LogFilter.java |
| 33 | Maven Wrapper 过旧 | → 3.3.1/3.9.6 | mvnw, mvnw.cmd |
| 35 | maven-source-plugin 过旧 | → 3.3.1 | pom.xml |
- AbstractRepository.java:
if (sessionContext != null)→isRequestContextActive()+resolveSessionWithoutContext()fallback (CDI proxy always non-null) - FmJobStore.java:
new FmJobRepository()→CDI.current().select(FmJobRepository.class).get()(Quartz-instantiated, @Inject 不可用) - LogFilter.java:
@Inject EventBus eventBus→@Inject jakarta.inject.Provider<EventBus> eventBusProvider(Vertx 是 RUNTIME_INIT synthetic bean, 直接注入导致 STATIC_INIT 冲突) - pom.xml:
junit.version5.10.3 → 6.0.3 (Quarkus 3.33.1 引入 junit-platform 6.0.3, engine 5.10.3 与之不兼容)
ScheduledJobExecutionTest.java— 11 个测试用例覆盖 JobListener lifecycle, Function/Flow Job missing params, FmJobStore CRUD/trigger state/calendar/trigger fire/disallow concurrent/reschedule/group queries
mvn clean compile -pl '!flexmodel-engine/flexmodel-maven-plugin'→ BUILD SUCCESSmvn test -pl flexmodel-engine→ BUILD SUCCESSmvn clean test -pl flexmodel-server→ @QuarkusTest 启动失败 (pre-existing: SessionFactory/Quartz tables 在测试环境不可用, JUnit 版本已修复)
- 所有
@QuarkusTest测试因SessionFactory在测试环境不可用而启动失败 - LSP 错误 (codegen 相关类型如 FlowDefinition 在 IDE 中未解析) — 非 our edits 引起
目标: 在 flow 核心关键生命周期点发布强类型本地事件,经 Vert.x EventBus 广播;新增可选 RabbitMQ 桥接,默认关闭、未配置不连 broker,本地事件照常发布。
实现:
- 新增
dev.flexmodel.flow.event包:FlowEvent抽象基类(projectId/caller/timestamp + 抽象routingKey())、FlowEventTypes路由 key 常量、11 个具体事件类(定义层 4 / 实例层 4 / 用户任务层 3)。 FlowEventPublisher(@ApplicationScoped):注入EventBus,publish(FlowEvent)经eventBus.publish(routingKey, event)广播,全程 try/catch 仅告警、不抛出、不阻塞流程。FlowEventRabbitmqBridge(@ApplicationScoped):每事件一个@ConsumeEvent(常量, blocking=false)方法消费具体类型,enabled默认 false 时 early-return;forward()以Instance<MutinyEmitter>延迟注入(禁用通道时 bean 仍可创建,不影响本地事件),用OutgoingRabbitMQMetadata设 routing key 尽力转发。FlowEventConfig@ConfigMapping(prefix="flexmodel.flow")声明events.rabbitmq.enabled配置根,避免 SmallRye 严格校验报does not map to any root。- 依赖:
flexmodel-server/pom.xml加io.quarkus:quarkus-messaging-rabbitmq(3.33 起更名自quarkus-smallrye-reactive-messaging-rabbitmq,版本由 quarkus-bom 管理)。 - 配置
application.properties:flexmodel.flow.events.rabbitmq.enabled=false、mp.messaging.outgoing.flow-events-out.*(connector=smallrye-rabbitmq、exchange.type=topic/durable)、enabled绑定同一开关、quarkus.rabbitmq.devservices.enabled=false。 - 埋点(11 处):DefinitionProcessor (create/update/deploy/delete)、FlowExecutor (preExecute→started、execute finally FAILED→failed、postExecute/postCommit COMPLETED/END→completed)、RuntimeProcessor.terminateProcess (TERMINATED→terminated,子流程级联逐个)、UserTaskExecutor (doExecute→suspended、postCommit→committed、doRollback→rollback.suspended)。
测试:
FlowEventPublisherTest(2):本地 EventBus 广播 + 字段完整 + null no-op。FlowEventRabbitmqBridgeTest(2):默认禁用不连 broker(应用无 broker 启动即证)、禁用桥接对本地事件透明。未采用 SmallRye InMemoryConnector(当前 Quarkus 3.33.1 未提供配套 in-memory 扩展,InMemorySinkImpl 无 bean 定义注解,注入不满足)。
验证:
mvn clean compile -pl '!flexmodel-engine/flexmodel-maven-plugin'→ BUILD SUCCESS
mvn test -pl flexmodel-server -Dtest=FlowEventPublisherTest,FlowEventRabbitmqBridgeTest,DefinitionProcessorTest,RuntimeProcessorTest →
25 tests, 0 failures, 0 errors(含 DefinitionProcessorTest 4、RuntimeProcessorTest 17 回归通过)
备注: RabbitMQ 转发(enabled=true)端到端需真实 broker 或 Testcontainers,按计划保持可选、默认关闭;v1 不含 node-instance-created/service-task 等事件,后续同模式按需追加。
背景: 该业务开关与 SmallRye 通道 mp.messaging.outgoing.flow-events-out.enabled 重复;且桥接 enabled 默认 false
时即便通道启用也不转发,属冗余控制层。
变更:
application.properties:删除flexmodel.flow.events.rabbitmq.enabled,通道enabled=false成为单一控制(启用置 true + broker 连接配置)。FlowEventRabbitmqBridge:删除@ConfigProperty enabled字段、11 处if(!enabled) return;early-return、isEnabled()测试探针;转发完全由Instance<MutinyEmitter>解析性决定——通道禁用时 SmallRye 注入 no-op emitter,forward()发送即丢弃、不连 broker。- 删除
FlowEventConfig(@ConfigMapping不再需要声明已移除的配置根)。 FlowEventRabbitmqBridgeTest:移除isEnabled/isEmitterResolvable断言,改为断言桥接 bean 存在且默认通道禁用时发布不抛出、本地事件照常广播。
验证:
mvn test-compile -pl '!flexmodel-engine/flexmodel-maven-plugin'→ ExitCode 0
mvn test -pl flexmodel-server -Dtest=FlowEventPublisherTest,FlowEventRabbitmqBridgeTest,DefinitionProcessorTest,RuntimeProcessorTest →
25 tests, 0 failures, 0 errors
设计效果: 单一配置源——mp.messaging.outgoing.flow-events-out.enabled 同时控制是否连 broker 与是否转发;默认 false
零侵入,本地 EventBus 事件始终发布。
P1 桥接默认配置逐事件 WARN 栈: Instance.isUnsatisfied() 对禁用通道返回 false,.get() 抛 SRMSG00019 被捕获记
WARN(含事件载荷/流程变量)。修复:FlowEventRabbitmqBridge 注入
@ConfigProperty("mp.messaging.outgoing.flow-events-out.enabled", defaultValue="false") channelEnabled,forward() 首行
if(!channelEnabled) return; 静默跳过;转发失败日志改为仅记 routingKey + payloadType,不再打印整个事件(避免泄露流程变量)。
P2 发布可变 variables 快照并发风险: FlowInstanceStartedEvent/FlowInstanceCompletedEvent/UserTaskSuspendedEvent
构造时按引用持有 runtimeContext.getInstanceDataMap(),发布线程后续 mutate 同一 map,异步消费者/序列化读取共享 map 可能不一致或
CME。修复:三个事件构造器对 variables 做 new HashMap<>(variables) 防御性拷贝(null 安全),发布快照不可变。
验证: mvn test-compile 通过;
FlowEventPublisherTest(2)/FlowEventRabbitmqBridgeTest(2)/DefinitionProcessorTest(4)/RuntimeProcessorTest(17) 共 25 测试
0 失败 0 错误。默认配置下桥接不再逐事件打 WARN(SRMSG00232 通道禁用为启动一次性诊断)。
目标: 增加 Testcontainers 端到端测试,启动真实 RabbitMQ broker 验证 FlowEventRabbitmqBridge 以正确 routing key +
JSON 载荷推送事件到 topic 交换机。
新增/修改:
flexmodel-server/pom.xml:加org.testcontainers:rabbitmq:${testcontainers.version}(test scope,版本由父 pomtestcontainers.version=1.21.4管理;amqp-client 5.x 传递可用)。RabbitMqTestResource.java(前序会话已写):实现QuarkusTestResourceLifecycleManager,启动RabbitMQContainer("rabbitmq:3-management"),注入quarkus.rabbitmq.host/port/username/password与mp.messaging.outgoing.flow-events-out.enabled=true,暴露 static host/port/username/password 供测试取连接坐标。FlowEventRabbitmqBridgeE2ETest.java(新增):@QuarkusTest+@QuarkusTestResource(SQLiteTestResource.class)+@QuarkusTestResource(value=RabbitMqTestResource.class, restrictToAnnotatedClass=true)。两个用例:flowDeployedEventForwardedToBroker:amqp-client 临时队列绑定交换机flexmodel.flow.events(routing keyflow.deployed),发布FlowDeployedEvent,basicGet轮询(≤15s)断言 routing key 与 JSON 字段(projectId/caller/flowModuleId/flowDeployId/timestamp)。flowInstanceStartedEventWithVariablesForwarded:同模式断言flow.instance.started与 variables 快照(amount/approved)经 JSON 序列化完整。
关键设计决策:
- 资源泄漏修复(重要): 初版
@QuarkusTestResource(RabbitMqTestResource.class)默认restrictToAnnotatedClass=false,导致 broker 资源被 Quarkus 全局应用到所有共享应用上下文的测试——即便未选 E2E 测试,其他测试(FlowEventPublisherTest等)也触发rabbitmq:3-management镜像拉取,无 Docker Hub 环境下整套测试失败。改为restrictToAnnotatedClass=true,资源仅对本测试类生效。验证确认:非 E2E 测试不再拉取镜像。 - opt-in 开关: E2E 测试加
@EnabledIfEnvironmentVariable(named="FLEXMODEL_E2E_RABBITMQ", matches="true"),默认跳过。项目此前无任何 Docker 依赖测试,此为首个;默认mvn test在无 Docker/无 Docker Hub 环境保持绿色。运行需:本机 Docker 可用 + 能拉取rabbitmq:3-management+ 设环境变量FLEXMODEL_E2E_RABBITMQ=true。 - routing key 取自 AMQP envelope(
routingKey()是方法不进 JSON),JSON 载荷用 Jackson 解析;带 variables 的事件经防御性拷贝保证快照不可变(见前序 Review 修复)。
验证:
mvn test-compile -pl '!flexmodel-engine/flexmodel-maven-plugin' -q→ ExitCode 0。- 综合回归
mvn test -pl flexmodel-server -Dtest=FlowEventPublisherTest,FlowEventRabbitmqBridgeTest,FlowEventRabbitmqBridgeE2ETest,DefinitionProcessorTest,RuntimeProcessorTest→ Tests run: 27, Failures: 0, Errors: 0, Skipped: 2(E2E 默认跳过),BUILD SUCCESS,无 Docker 镜像拉取。 - E2E 实跑未完成: 当前环境 Docker Hub 不可达(
registry-1.docker.ioEOF / 配置镜像源docker.1panel.live超时),rabbitmq:3-management无法拉取,故FLEXMODEL_E2E_RABBITMQ=true实跑未通过。代码逻辑正确、编译通过;待网络恢复或换可用镜像源后,设该环境变量即可执行端到端验证。
未决/后续:
- E2E 实跑待 Docker Hub 可达后补验证(设
FLEXMODEL_E2E_RABBITMQ=true运行)。 - 若 CI 需常态化跑 E2E,建议在 CI 配置可用 RabbitMQ 镜像源或预拉镜像,并设该环境变量。
问题: 首次运行 FlowEventRabbitmqBridgeE2ETest 失败——应用侧 SmallRye outgoing channel Connection refused,事件未发到
broker,测试轮询超时。日志报 Unrecognized configuration key "quarkus.rabbitmq.password"。
根因: 反编译 quarkus-messaging-rabbitmq 扩展的 Quarkus config root RabbitMQBuildTimeConfig 确认:其仅注册
devservices、credentialsProvider、credentialsProviderName 字段, 不注册 host/port/username/password 连接字段。故
RabbitMqTestResource 注入的 quarkus.rabbitmq.host/port/username/password 全部无效(unrecognized),SmallRye client 回退默认
localhost:5672,连不上 Testcontainers 随机映射端口。这些连接字段由 SmallRye connector 自身读取(channel 级
mp.messaging.outgoing.<channel>.host/port/username/password,或全局别名 rabbitmq-host 等),通过查
smallrye-reactive-messaging-rabbitmq-4.33.0 源码 RabbitMQConnectorCommonConfiguration 确认。
修复: RabbitMqTestResource.start() 改用 channel 级连接配置:
mp.messaging.outgoing.flow-events-out.host/port/username/password;保留 quarkus.rabbitmq.devservices.enabled=false(防
DevServices 自启)与 mp.messaging.outgoing.flow-events-out.enabled=true。测试侧 amqp-client 仍用 static host/port
订阅交换机。
验证: 设 FLEXMODEL_E2E_RABBITMQ=true 运行 →
SRMSG17036: RabbitMQ broker configured to [localhost:52374] for channel flow-events-out +
SRMSG17007: Connection with RabbitMQ broker established → Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, BUILD
SUCCESS。两个用例(flowDeployedEventForwardedToBroker、flowInstanceStartedEventWithVariablesForwarded)断言 routing
key 与 JSON 载荷字段(含 variables 快照)完整通过。
结论: E2E 端到端验证完成。Flow 生命周期事件经 FlowEventPublisher→EventBus→FlowEventRabbitmqBridge→SmallRye
outgoing channel→RabbitMQ topic 交换机链路,routing key 与 JSON 载荷均正确。默认 mvn test 不依赖 Docker(E2E opt-in 跳过),
restrictToAnnotatedClass=true 确保 broker 资源不泄漏到其他测试。
需求: 订阅者常需解析节点定义里的扩展属性做业务(审批人、表单、阈值等)。外部 RabbitMQ 订阅者访问不到定义仓库,故将节点属性快照随事件携带。
改动:
- 事件类:
UserTaskSuspendedEvent/UserTaskCommittedEvent/UserTaskRollbackSuspendedEvent各加Map<String, Object> nodeAttributes字段,构造器做new HashMap<>(nodeAttributes)防御性拷贝(与 variables 一致)。 UserTaskExecutor三处埋点填充nodeAttributes:doExecute用flowElement.getProperties();postCommit用runtimeContext.getCurrentNodeModel().getProperties();doRollback用FlowModelUtil.getFlowElement(flowElementMap, nodeKey).getProperties()。- 测试:
FlowEventRabbitmqBridgeE2ETest新增userTaskSuspendedEventWithNodeAttributesForwarded,验证 routing key + variables + nodeAttributes 经 broker 转发后 JSON 载荷完整(含中文字段名、boolean)。 - 文档
flexmodel-website/docs/tutorial/features/flow.md:- 修正公共字段表——
routingKey不在 JSON 载荷(是方法非字段),改注其随 AMQP envelope 投递;同步修正 Python 订阅示例从method.routing_key读取。 - 事件总览表三个 UserTask 事件加
nodeAttributes。 - 新增「事件数据结构」小节,按定义层/实例层/用户任务层分组给出每种事件的 JSON 载荷示例。
- 修正公共字段表——
验证:
mvn test-compile→ ExitCode 0。- E2E(
FLEXMODEL_E2E_RABBITMQ=true)→ Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS(含新增 nodeAttributes 用例)。 - 默认回归 → Tests run: 28, Failures: 0, Errors: 0, Skipped: 3(E2E 默认跳过),BUILD SUCCESS。
设计要点: nodeAttributes = 节点定义的 FlowElement.properties 快照,事件发生时已确定且不可变;外部订阅者无需回查定义仓库即可读取节点配置,解耦核心与外部系统。
结论: 当前阶段不必要,列为 P2,待真实业务场景催动再做。
当前实现摘要:
RollbackTaskParam仅携带flowInstanceId+taskInstanceId,getActiveNodeForRollback(FlowExecutor.java:428)只允许回退与suspendNodeInstanceId完全匹配的节点——ACTIVE 的当前节点,或最后一个 COMPLETED 节点。doRollback(FlowExecutor.java:474)为单步链:禁用当前节点实例 → 若该节点是 COMPLETED 的 UserTask,则在同一 nodeKey 上新建 ACTIVE 实例并SuspendException挂起(UserTaskExecutor.java:125),等用户重新提交;回退到 StartEvent 则置TERMINATED。- 本质是「重做当前环节 / 回退一步」模型,状态机与数据快照均按单步设计。
不做的理由(成本与前提破坏):
- 多节点链路:跨步回退需沿
sourceNodeInstanceId链路批量 DISABLE 中间节点,并重新激活目标节点;目标节点的instanceDataId可能已被后续步骤覆盖,需明确数据回放策略。 - 并行/分支:跨 fork 后 sibling 分支节点实例的处理语义未定义(一并 disable / 保留),当前
getActiveNodeForRollback未涉及。 - CallActivity 嵌套:跨子流程回退时子流程实例生命周期需单独处理。
- 幂等与重复回退:指定节点回退后,「上一个节点」语义变化,需重新定义可回退判定。
触发条件(满足任一可启动):
- 出现「驳回发起人 / 驳回到指定环节」等 BPM 标配语义的真实审批流需求。
- flow 产品 roadmap 需对标 Activiti/Flowable 的回退能力。
- 「连退两步」频率高,单步回退成为体验瓶颈。
启动时的建议实现路径:
RollbackTaskParam增加可选targetNodeInstanceId。getActiveNodeForRollback改为「链路可达性校验 + 中间节点批量 disable」。- 引入节点级 instanceData 快照表支撑数据回放,而非在现有单步逻辑上打补丁。
需求: 流程实例列表已有「用户操作记录」 (/user-tasks) 按钮,需再增加一个按钮拉取流程实例历史元素列表 (/elements)。
改动:
- 新增
flexmodel-ui/src/pages/Flow/components/ElementInstancesDrawer.tsx:只读 Drawer,用 vertical Steps 时间线展示getElementInstances返回的全部元素实例(含开始/网关/任务/结束),复用 NodeInstanceStatus 标签与实例数据查看(getInstanceData + Monaco 只读 Modal)。 flexmodel-ui/src/pages/Flow/components/FlowInstanceList.tsx:- 引入
NodeIndexOutlined、getElementInstances、ElementInstancesDrawer。 - 新增 elements 相关 state(visible/loading/instances)与
handleShowElementshandler。 - 操作列在「用户操作记录」后追加「历史元素列表」按钮(NodeIndexOutlined 图标)。
- JSX 末尾挂载
<ElementInstancesDrawer/>。
- 引入
附带修复: FlowDetail/index.tsx:56 的 loadData 增加 projectId 守卫,修复 hideLayout 路由下 currentProject 未就绪导致
projects//flows/instances/... 404。
验证:
npx tsc -b→ 通过,无错误。
目标: 生产用 npm、开发用本地源码覆盖;修复 flexmodelClient.setTraceId is not a function 链路断裂。
完成内容:
- SDK 版本同步: SDK
package.json已为0.0.10(含setTraceId)。将flexmodel-functions-runtime/deno.json与registry.ts的SDK_NPM_FALLBACK由npm:@flexmodel/sdk@0.0.8统一提升至0.0.10,使生产 npm 依赖与已升级 SDK 一致(含 setTraceId)。 - 开发/生产配置分离:
deno.json(生产/Docker):@flexmodel/sdk→npm:@flexmodel/sdk@0.0.10,start任务无 --config,Docker 安全。deno.local.json(开发覆盖):@flexmodel/sdk→../flexmodel-sdks/typescript/src/index.ts(本地源码,无构建/无 npm 即时反映)。Denoextends替换而非合并 imports,故 deno.local.json 含全量 imports。deno.json的dev任务显式--config=deno.local.json+--sloppy-imports(SDK 源码用 .js 扩展名导入 ESM 规范,Deno 2.8 需该 flag 解析 .ts)。
- Worker SDK 内联:
registry.ts的sdkBundle优先读取本地dist/index.js内联为函数目录_flexmodel_sdk.js,函数级 import map 用相对路径(离线可移植);找不到本地构建时回退 npm。dev/prod 宿主不直接 import SDK,Worker 始终用内联 bundle,故 npm 版本 bump 不影响deno task dev/测试(测试用内联 dist)。 - 清理: 删除根目录误建的
mvn_verify.txt与src/(来自此前错误 create_new_file)。
关键决策:
- 生产 npm@0.0.10 需先
npm publish发布;未发布前生产若 SDK 子模块未进镜像且回退 npm 会失败。monorepo 镜像构建(子模块检出)下 Worker 用内联 dist,与 npm 版本无关。 - 不对 setTraceId 做 typeof 防御(用户明确要求;SDK 升级保证存在)。
验证:
deno check --config=deno.local.json src/main.ts src/server_test.ts→ DEV_OKdeno check src/main.ts src/server_test.ts src/runner/worker_test.ts src/runner/registry_test.ts→ PROD_CHECK_OKdeno run --sloppy-imports --config=deno.local.json _sdk_check.ts→ hasSetTraceId true(本地源码 setTraceId 可用)- SDK
dist/index.js含 5 处 setTraceId;deno --version= 2.8.2
目标: Quartz 定时触发器触发任务(流程/函数)时创建 OTel span,traceId 贯穿 触发器→函数/流程→下游,并记录到 f_job_execution_log。
完成内容:
- TracingHelper(新建 observability 包):封装 OTel 手动 span 创建,设置 flexmodel.project_id 属性;提供 startSpan(根 span)、startChildSpan(从远程 traceId/spanId 恢复)、currentTraceId(获取当前 HTTP span traceId)。
- f_job_execution_log 模型:新增 trace_id 字段 + IDX_JOB_EXEC_TRACE_ID 索引(project.fml)。
- ScheduledFunctionExecutionJob:execute () 中用 TracingHelper.startSpan 创建根 span 包裹执行;span 激活后 Span.current () 有效,FunctionRuntimeClientHeadersFactory 自动注入 traceparent 贯穿 Java→Deno 链路;traceId 存入 JobExecutionContext 供 listener 记录。
- ScheduledFlowExecutionJob:同理创建根 span,traceId+spanId 传入 StartProcessParamEvent,EventBus 跨线程传播。
- TriggerFlowEventConsumer:用 startChildSpan 从 param 的 traceId/spanId 恢复 span 上下文,使流程执行中的下游调用(含函数调用)在同一 trace 下。
- StartProcessParamEvent:新增 traceId、spanId 字段。
- ScheduledFlowExecutionJobListener:从 context 取 traceId 传给 recordJobStart。
- JobExecutionLogService.recordJobStart:新增 traceId 参数,setTraceId 到 JobExecutionLog。
- TriggerService(手动触发 2 处)+ TriggerDataChangedEventListener(事件触发 1 处):注入 TracingHelper,recordJobStart 传 currentTraceId ()。
- FmSpanExporter.extractProjectId:优先从 flexmodel.project_id 属性提取 projectId(非 HTTP span 如 Quartz Job)。
验证: mvn compile -pl '!flexmodel-engine/flexmodel-maven-plugin' BUILD SUCCESS(codegen 重新生成 JobExecutionLog 含 trace_id)。
目标: 创建/合并分支时跳过日志/trace 表的数据迁移(表结构照常复制),通过 FML 声明式标记驱动,可扩展。
完成内容:
- FML 标记(project.fml):在 f_api_request_log、f_function_log、f_audit_log、f_span、f_job_execution_log 的
@system后追加@migration(enabled: false)(叠加不替换)。 - MigrationConfig(新增 dev.flexmodel.project.MigrationConfig):
of(SchemaObject)从additionalProperties["migration"]解析——无参标记→true(启用迁移);带参注解→Map,取 enabled(Boolean.parseBoolean) 与 limit(Integer.parseInt,预留);未标记或 null→默认全量迁移(向后兼容)。 - BranchService:createBranch(数据迁移循环)与 mergeBranch(新模型插入 + 双方都有模型 diff 两个分支)加入
if (!MigrationConfig.of(model).isEnabled()) continue;,跳过数据迁移并 log.info 提示。 - DefaultSchemaCopier 不变:仅复制表结构,日志表空结构照常建到新分支。
- 回归测试(ASTNodeConverterTest.migrationAnnotationIsStoredInAdditionalProperties):断言
@migration(enabled: false)被解析为 additionalProperties 中的 Map,enabled 以 String "false" 存储。
关键事实链: ASTNodeConverter.toSchemaEntity L71-72 未识别注解走 default 分支——无参存 true、带参存参数 Map, 引擎侧零改动;getAdditionalProperties 全仓仅 ModelService 与 FlexmodelGraphQL 两处消费者且只查 "system" key, codegen 不读 additionalProperties,故 @migration 对实体生成透明无副作用。
验证: mvn compile -pl '!flexmodel-engine/flexmodel-maven-plugin' BUILD SUCCESS;mvn test -pl flexmodel-engine 通过;ASTNodeConverterTest 3 tests 0 failures 0 errors。
遗留/风险: v1 仅实现 enabled,limit 字段已预留但 BranchService 未消费;将来支持 @migration (limit: N) 需在两处 迁移循环加按 created_at desc 限量查询逻辑(各日志表均有 created_at)。注:mvn clean 因 dev 进程占用 flexmodel-server-dev.jar 失败,改用 mvn compile 验证。
目标: 将多语言及代码中残留的 observability 相关键/变量统一改为 log 命名。
完成内容:
- i18n key: en.json/zh.json 中 observability. (function_logs/api_logs/job_execution_log/node_instance_logs/audit_logs)重命名为 log.;移除未引用的顶层 observability 孤儿 key。
- routes.tsx: 4 处 translationKey 由 observability.* 改为 log.*;AuditLogList 导入路径由 @/pages/Observability/... 改为 @/pages/Logs/...。
- t () 调用: APILog/index.tsx 与 AuditLogList.tsx 中 t ('observability. ') 改为 t ('log.')。
- 类型文件: @/types/observability.d.ts 重命名为 @/types/log.d.ts,更新 audit-log.ts/function-log.ts/FunctionLogList.tsx/AuditLogList.tsx 共 4 处导入。
- 目录: pages/Observability/ 重命名为 pages/Logs/(仅含 components/AuditLogList.tsx)。
验证: flexmodel-ui tsc --noEmit -p tsconfig.json 通过;rg 全局无残留 observability 引用。
说明: 后端 ProjectLogSettings.java 保留对旧 metadata.observability 的 fallback 读取(向后兼容),不在本次改动范围。
- 目录最终命名为 pages/AuditLog/(非 pages/Logs):因 .gitignore 第 2 行 logs 规则在 Windows(core.ignorecase)下会忽略 pages/Logs,导致文件无法被 git 跟踪。AuditLog 不匹配该规则且语义更贴切。
- 移除 ypes/settings.d.ts 中未被引用的孤儿 Observability 接口(其形状仅含 auditResources,与实际 logSettings 不符)。
- 验证: sc --noEmit -p tsconfig.json 通过; g -i observability flexmodel-ui/src 无残留。
目标: 修复 Docker/UI 生产构建中 TypeScript noUnusedLocals 报错。
修改:
- 移除
APILog/index.tsx、AuditLogList.tsx、JobExecutionLogList.tsx中未使用的useNavigate导入与navigate变量。 - 移除
Functions/index.tsx中未使用的Outlet导入。
验证:
flexmodel-ui/npm run build通过(tsc -b && vite build)。mvn clean compile -q -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。mvn test -pl flexmodel-engine -q通过。
遗留: Vite 构建提示 Monaco Editor 相关 chunk 超过 500 kB,属既有性能警告,不影响本次构建结果;./init.sh 在 Git Bash
中因 java 不在 PATH 无法直接运行,PowerShell 环境中 Maven/Java 25 可用,本次已按同等检查项验证。
目标: 将耗时的分支合并接口改为 SSE 流式响应,向前端实时输出每个模型的数据迁移进度。
修改:
- 后端新增
BranchMergeProgressEvent,包含事件类型、阶段、模型名、总数/已处理数、源/目标/插入/更新记录数和消息。 BranchService.mergeBranch改为返回Flow.Publisher<BranchMergeProgressEvent>,在 ManagedExecutor 中异步执行原合并逻辑,并通过SubmissionPublisher输出准备、结构合并、逐模型数据迁移、完成/失败事件。BranchResource.mergeBranch原路径不变,响应改为text/event-stream。- 针对 Quarkus Reactive 的
BlockingNotAllowedException,为mergeBranch方法补充@Blocking,确保请求体在 worker 线程读取,SSE 响应仍由 Publisher 异步输出。 - 前端
mergeBranch改用fetch流式读取并解析 SSE;BranchSwitcher点击合并后立即显示进度面板,并实时更新总体进度与每个模型的迁移状态。
验证:
mvn clean compile -q -pl '!flexmodel-engine/flexmodel-maven-plugin'通过。mvn test -pl flexmodel-engine -q通过。flexmodel-ui npm run lint通过。flexmodel-ui npm run build通过(tsc -b && vite build)。
遗留/风险: init.sh 在当前 Git Bash 环境受 CRLF/PATH 影响无法直接执行;本次已使用其等价 Maven 检查命令在
PowerShell/cmd 环境完成验证。Vite 仍提示既有 Monaco Editor chunk 大小警告,不影响构建结果。
目标: 将耗时的 createBranch、mergeBranch 改为 SSE 进度流,细化每个模型的数据迁移进度;删除分支恢复普通同步接口。前端实时展示进度,并在成功后保留进度供用户查看。
修改:
- 新增统一
BranchProgressEvent,承载 CREATE/MERGE 操作、阶段、模型、进度、记录数与结果。 BranchService.createBranch、mergeBranch返回Flow.Publisher<BranchProgressEvent>,由 ManagedExecutor + SubmissionPublisher 异步执行。- 创建分支的数据迁移逐模型输出开始、完成、跳过、失败事件,并将 50%-90% 映射为迁移进度;合并分支继续输出结构合并和逐模型数据合并事件。
BranchResource.createBranch、mergeBranch声明text/event-stream与 JSON 元素类型;deleteBranch恢复普通同步接口。- 前端
createBranch、mergeBranch使用fetch流式解析 SSE;BranchSwitcher实时显示进度,成功后弹窗保持打开并展示 100% 进度,用户关闭时清除进度。 LogFilter移除请求体阻塞读取,业务接口不再需要@Blocking。
验证:
mvn compile -pl flexmodel-server -am -q通过。mvn test -pl flexmodel-engine -q通过。flexmodel-ui npm run lint通过。flexmodel-ui npm run build通过(tsc -b && vite build)。