引擎从不发一条消息:jeeflow 的两类扩展点与消息模块的分工

作者:mldong日期:2026/9/6

二、引擎有两类"伸手"的地方:拦截器和事件

引擎核心是零框架依赖的,但它知道一件事:业务方总要"在流程跑的时候干点自己的事"——发通知、改业务单据状态、按规则算审批人、写代码做决策。这些需求它不自己实现,而是开出两类扩展点让外面插进来:

  • 拦截器(FlowInterceptor:插在流程执行过程中,能看到、甚至能正在发生的事。
  • 事件(ProcessEventListener:流程走到某个关键点时喊一声,外面的人听到后自己决定要不要做点什么。

这两个接口的长相,朴素到可以各贴一行:

1// jeeflow-core/src/.../interceptor/FlowInterceptor.java
2public interface FlowInterceptor {
3    void intercept(Execution execution);   // 拿到整个执行上下文,能读能改
4}
5
6// jeeflow-core/src/.../event/ProcessEventListener.java
7public interface ProcessEventListener {
8    void onEvent(ProcessEvent event);      // 只拿到一个"喊声",改不了流程
9}
10

差别就在这一行入参上,而且差别是本质的:

  • 拦截器拿到的是 Execution——整条正在跑的上下文(实例、当前节点、这一批要建的任务……),它在事务内、落库前执行,所以它能改走向:典型是"委托代理"拦截器,任务建好但还没落库时,它查一下"这个人是不是委托了别人",命中就把代理人加进这个任务的参与者列表——流程接下来谁收待办,被它当场改了
  • 事件拿到的是 ProcessEvent——一个几乎只有 id 的小盒子(后面细说),它在落库后才被 fire,而且 fire 完引擎就继续往下走了,管不了也不该管你听到之后干嘛。

这条界线,jeeflow 文档站里用一句话总结,我一直觉得是写扩展点最该有的心态:

要"拦"用拦截器,要"通知"用事件。两者不要混。

"拦"意味着你要影响流程本身(谁能办、走哪条边、前置条件不满足就不让建任务)——那必须同步、必须能改上下文、必须排在正确的位置,这是拦截器的事。"通知"意味着你只是想在事实已经发生后被知会一声(发个消息、记个日志、推个 MQ)——你不关心也不应该关心流程内部,事件就够了。

你们的消息模块,用的是右边这个——事件。 站内信是"流程走完一步之后通知一声",不是"影响流程怎么走",所以它天然是事件监听器的活,而不是拦截器的活。想清楚了这一点,后面消息模块那一节就顺了。


三、事件:引擎只"喊",而且喊得很省

那引擎到底在哪些点"喊"?答案是——很少,只有四类

1// jeeflow-core/src/.../enums/ProcessEventTypeEnum.java
2public enum ProcessEventTypeEnum {
3    PROCESS_INSTANCE_START(1, "流程实例开始事件"),
4    PROCESS_INSTANCE_END(2,   "流程实例结束事件"),
5    PROCESS_TASK_START(3,     "流程任务开始事件"),
6    CC_CREATE(4,              "抄送知会事件");
7    // ...
8}
9

对比一下重量级引擎:Flowable、Camunda 的事件监听点能列出一长串(实体级、生命周期级、各类 before/after)。jeeflow 只有四个,而且是流程生命周期级的粗粒度喊声——它不给你"某个实体的某个字段被改了"这种细粒度事件。这是克制,不是偷懒:引擎核心不背"通知谁、通知什么"的复杂度,它只保证"在正确的时机、以统一的语义、喊那么几声"。

四个喊声分别在哪里 fire(以 Java 参考实现为准):

事件在哪 fire时机
INSTANCE_START开始节点 StartModel.exec流程启动、走出开始节点时
TASK_STARTJeeflowEngineImpl.notifyTaskStart任务行落库之后,逐任务 fire(会签每个子任务各喊一次)
INSTANCE_ENDEndProcessHandler.handle到达 end 节点,办结(同意)和拒绝两条路都喊这一个
CC_CREATEJeeflowEngineImpl.handleCcActors抄送实例落库后,逐抄送人喊一次

注意两个刻意为之的设计。

第一,事件体里几乎没有东西。 打开 ProcessEvent

1public class ProcessEvent {
2    private ProcessEventTypeEnum eventType;  // 喊的是哪一类
3    private Long sourceId;                   // 一个 id(实例 id 或任务 id)
4    private String ccActorId;                //  CC_CREATE 用:抄送人 id
5    private FlowData data;                   // 空容器,引擎不填
6}
7

就一个类型、一个 id(抄送那种多一个 id)。没有业务快照,没有表单数据,没有审批意见。 为什么?因为事件是**"通知你发生了",不是"把数据传给你"**。监听器想要更多数据,自己拿着这个 id 去仓储查(findTaskByIdfindInstanceById)。这样事件体永远不膨胀,也永远不会把一份"可能已经过期"的快照塞给监听器——数据以仓储里当前那份为准。

第二,也是最容易踩的:TASK_START 是在任务落库之后才 fire 的,不是建任务时就 fire。 这个点当年是踩过坑的。直觉上"创建任务"这个动作一发生就该喊,但那时任务行还没落库、taskId 还是 null——监听器拿到 sourceId==null 直接守卫跳过,结果"新待办"那条消息漏发了。所以引擎把 fire 挪到了 saveTask(真正分配 taskId)之后:

1// JeeflowEngineImpl:任务落库后才 fire,taskId 一定非空
2private void notifyTaskStart(ProcessTask task) {
3    if (task == null || task.getTaskId() == null) {
4        return;
5    }
6    ProcessPublisher.notify(ProcessEvent.builder()
7            .eventType(ProcessEventTypeEnum.PROCESS_TASK_START)
8            .sourceId(task.getTaskId())
9            .build());
10}
11

这个"时机"不是实现细节,是契约——六语言必须对齐成"任务落库后逐任务 fire",否则各语言的监听器行为就漂了。

顺带说一个我挺喜欢的细节:这四个喊声里的第 4 号位,是个"死而复生"的槽位。 它最早叫 PROCESS_TASK_END(任务结束),但引擎从来不 fire 独立的任务结束事件——办结和拒绝都用 INSTANCE_END 覆盖了,全联邦 grep 下来这个枚举值零引用,是个挂了名字却没被用过一次的死码。后来需要一个"抄送知会"的喊声,与其新增第 5 号、把码值表改乱,不如把这个从没人用的 4 号位复用过来,语义从"任务结束"改成"抄送知会",码值不动、1/2/3 也不动。一个没人记得的死枚举,就这么变成了在用的第 4 类事件。

这四个喊声、各自的 fire 时机,画出来长这样:

再补一个关键事实,它是下一节所有故事的引子:notify 本身就是一个 for 循环。 打开 ProcessPublisher

1public static void notify(ProcessEvent event) {
2    List<ProcessEventListener> listeners = ServiceContext.findList(ProcessEventListener.class);
3    if (listeners != null) {
4        for (ProcessEventListener listener : listeners) {
5            listener.onEvent(event);   // 挨个喊,喊完就返回,不等谁做没做完
6        }
7    }
8}
9

没有注册表魔法、没有异步队列、没有重试。引擎把事件挨个递给监听器,递完就走。 它不知道、也不关心监听器有没有把消息写进数据库。"喊"这个动作到此为止——喊了不等于送达


四、消息模块:把"喊声"翻译成"站内信"

现在讲你们消息模块那一侧。它干的事,本质是把引擎喊的那几声,翻译成一条一条站内信落进 sys_message 表,让消息中心能读到。

先看它长什么样(Java 集成层 WfMessageProcessEventListeneronEvent 主干):

1@Override
2public void onEvent(ProcessEvent event) {
3    if (!wfMessageEnabled) {          // 铁律 1:消息开关,关掉直接跳过
4        return;
5    }
6    ProcessEventTypeEnum type = event.getEventType();
7    Long sourceId = event.getSourceId();
8    if (type == null || sourceId == null) {
9        return;                        // 铁律 3 的守门:没有 id 就无从反查
10    }
11    try {
12        Runnable action;
13        switch (type) {
14            case PROCESS_TASK_START:   // 任务落库  给处理人发"待审批" TODO
15                action = () -> handleTaskStart(sourceId);
16                break;
17            case PROCESS_INSTANCE_END: // 办结/拒绝  给发起人发"已办结" NOTICE
18                action = () -> handleInstanceEnd(sourceId);
19                break;
20            case CC_CREATE:            // 抄送知会  给抄送人发 NOTICE
21                action = () -> handleCcCreate(sourceId, event.getCcActorId());
22                break;
23            default:                   // INSTANCE_START 不产生消息
24                return;
25        }
26        afterCommitIfActive(action);   // 引擎在事务内 fire  延迟到提交后再落消息
27    } catch (Throwable e) {            // 铁律 2:消息出任何错,绝不打断审批
28        log.error("[工作流消息] 监听器异常(已忽略,不影响流程)", e);
29    }
30}
31

对着上一节那张 fire 表读,你会发现一个一一映射:引擎喊的每一种,消息模块都规定了"翻译成什么":

引擎喊的(事件语义)翻译成(站内信)接收人文案(真实)
TaskCreateTODO 待办(msgType=10)任务处理人 actorIds标题 待审批:{流程名}({节点名}),正文 {发起人}发起的流程已到达【{节点名}】节点,请及时处理。
InstanceEnd(办结拒绝都触发)NOTICE 通知(msgType=20)发起人 createUser标题 流程已办结:{流程名},正文 你发起的流程已全部审批完成,正式归档。
CC_CREATENOTICE 抄送知会(msgType=20)事件直传的 ccActorId标题 你被抄送:{流程名},正文 {发起人}发起的【{流程名}】已抄送给你,请知悉。
InstanceStart / TaskComplete不产生消息

注意 TaskCreate → 处理人 TODO 这条是消息模块的主力:一次提交产生几个新待办,就 fire 几次 TASK_START,就落几条 TODO。你手机上那条"待审批",就是这么来的。

这里埋着全文最深的一个坑,值得单独说——afterCommitIfActive 这一行

引擎是在事务内、同步地 fire 事件的(CreateTaskHandlerrunInTx 里调 notify)。但监听器要发"新待办",得先拿着 taskId 去仓储 findTaskById 反查处理人——而那个反查走的是独立连接的自动提交,在 READ COMMITTED 下,引擎当前事务还没提交,这条刚落库的任务它根本读不到。如果监听器当场就处理,就会因为查不到任务而漏发这条 TODO(办结那条 NOTICE 反而侥幸没事,因为实例是上一个事务提交的)。

所以 Java 监听器没有当场干,而是把活挂到 Spring 的 afterCommit:等引擎那个事务真正提交之后,任务对独立连接可见了,再反查、再落消息;万一引擎事务回滚了,afterCommit 不触发,也就不会误发一条"其实没发生"的消息

1private void afterCommitIfActive(Runnable action) {
2    if (TransactionSynchronizationManager.isSynchronizationActive()) {
3        // 有活跃事务  注册 afterCommit,等提交后再落消息
4        TransactionSynchronizationManager.registerSynchronization(
5            new TransactionSynchronization() {
6                @Override public void afterCommit() {
7                    try { action.run(); }
8                    catch (Throwable e) { log.error("afterCommit 落消息异常(已忽略)", e); }
9                }
10            });
11    } else {
12        action.run();   // 没走事务模板的路径  立即执行
13    }
14}
15

这件事的意义在于:它证明了**"引擎只 fire、不保证落库"不是一句口号**,而是一个会真实咬人的边界——引擎 fire 的时机(事务内)和监听器要读数据的安全时机(事务后)之间,隔着一整个事务提交。把这条边界想反了(以为引擎 fire 时数据已经全可见、或者以为引擎会替你保证消息一定落库),就会出现"消息时有时无、还和事务回滚纠缠在一起"的诡异现象。这条经验,是六语言里 Java 栈用 Spring 换来的;别的语言(Go 的 defer recover、Rust 的 catch_unwind)各有各的"全局兜底"写法,但**"引擎只喊、落库归你"**这条边界本身是一样的。

把整条链画出来,左半是引擎、右半是集成层,中间那道线就是这条职责边界:

这条边界不是拍脑袋定的,是踩坑踩出来的契约。它有一条清晰的演进史,正好对应三个 issue:

  1. issue 100 · 消息写路径整个缺失:最早移动端走查发现 pro 站的 /wf/message/page 恒空——不是没数据、不是种子没造,而是全链路没有任何一段代码把流程事件组装成站内信落库。读接口好好的,写路径整条是空的。修法是双管齐下:引擎补齐"任务创建"事件(Go/Python/Node 之前根本不 fire,Java/Rust 有,先对齐),再给七套集成层各补一个"事件→消息"监听器。
  2. issue 101 · PHP 引擎压根没有事件机制:补监听器时发现,PHP 引擎是六语言里唯一没有 fire 通道的——没有 ProcessPublisher、没有监听器接口,监听器无处可挂。最后走"引擎补事件机制"(jeeflow-php 1.3.8 补上 ProcessEvent/Registry/Publisher,纯增量、不注册监听器时和 1.3.7 逐字节一致),PHP 才算真正并进"事件驱动"这条主流。
  3. issue 102 · 抄送人从来没收到过站内信:抄送实例数据一直在(ccList 能查),但抄送人的消息中心永远没有提醒——因为引擎建 cc 实例时既没发抄送消息、也没 fire 抄送事件。补法就是给引擎加一个 CC_CREATE 喊声(也就是上面那个"死而复生"的 4 号位),监听器收到后给抄送人落一条 NOTICE。

三个 issue 串起来,就是"消息模块"这套东西从无到有的全过程:先发现"引擎喊了但没人翻译"(100),再发现"有一门语言连喊都不会"(101),最后发现"有一类该喊的没喊"(102)。它不是一开始设计好的,是一条边界被反复确认、一个缺口被逐个补上长出来的。这也解释了为什么"引擎只喊、集成层翻译"会被写进 spec 当成契约——因为它不是某一次的设计选择,是踩过三层坑之后的共识。

顺带把三条铁律说全,任何一栈的消息监听器都得满足,少一条都会在某个极端场景漏消息或打断审批:

  1. 开关:有一个消息开关(默认开),关了监听器直接 return——性能零损耗,也给了业务方"这套流程不要发通知"的口子。
  2. 全局兜底:消息组装、落库的任何异常,只记日志,绝不打断审批主流程。审批是主业务,消息是旁路——旁路炸了不能把主流程拖下水。这就是 onEvent 外面那层 catch (Throwable) 存在的唯一理由。
  3. 接收人过滤:接收人去空、只认纯数字 ID、去重;过滤完是空就干脆不发。这一条防的是"审批人是个部门号/角色号、不是真实用户 id"时,把消息发进一个不存在的收件箱。

五、六语言,怎么把这条边界对齐

上面讲的是 Java 一栈。但 jeeflow 是六语言联邦,"引擎只喊、集成层翻译"这条边界,六门语言都得遵守,而且遵守的方式各不相同——这恰恰是这套东西最有意思的地方。

先说最容易漂的地方:事件的名字,六语言并不统一。触发语义是统一的,监听器是按语义对齐,不是按名字对齐

语义JavaRustGoNodePythonPHP
实例开始PROCESS_INSTANCE_STARTProcessInstanceStartEventProcessStartProcessStartPROCESS_STARTINSTANCE_START
任务创建(落库后)PROCESS_TASK_STARTProcessTaskStartEventTaskCreateTaskCreateTASK_CREATETASK_START
实例结束PROCESS_INSTANCE_ENDProcessInstanceEndEventProcessFinish/EventProcessRejectProcessFinish/ProcessRejectPROCESS_FINISH/PROCESS_REJECTINSTANCE_END
抄送知会CC_CREATE(4)CcCreate(4)EventCCCreate(5)CcCreate(5)CC_CREATECC_CREATE(4)

(表里是各语言引擎的事件名。这张表如今已原样收进 spec §4.1,成为官方映射表——集成方以它为唯一对照,不用再逐语言读源码。CC_CREATE 那行的码值也顺带标了:Rust 的 4 是后来补的,复用的正是那个从没人 fire 的 ProcessTaskEnd 死位,和 Java/PHP 同派。)

两处差异最值得记住:

  • 办结和拒绝,是合并还是一个? Java 和 Rust 只有一个 INSTANCE_END,办结和拒绝两条路都 fire 它(没有独立的 finish/reject);Go、Node、Python 则拆成 FinishReject 两个事件。所以"实例结束"这个语义,在不同语言里对应的事件个数都不一样。你的监听器不能写死事件名,得认"语义"——spec 里给这组差异起了个正经名字:语义锚点,"实例结束(办结+拒绝)"就是一个锚点,合并派接 1 个事件、拆分派两个都得接,漏接一个就是静默缺口。
  • CC_CREATE 的码值,也是各说各话:Java、PHP、Rust 里它是 4(4 号死位复用派),Go/Node 里是 5(它们 4 号位是活着的 TaskComplete,只能往后追加),Python 干脆用字符串标识。同一个"抄送知会",码值三套——spec 的官方态度很明确:各语言枚举独立,码值无强制统一必要,记进映射表即可。

挂载方式则是四家:Java 和 Rust 把监听器注册进 ServiceContext(Java 那个 SimpleContext 是纯 Map,不自动发现 Spring bean,所以监听器得在 @PostConstruct 里显式 put 进去);Go/Node 走 EngineExtensions 的监听器数组、Python 是 event_listener 单个 async 回调;PHP 用静态 ProcessEventListenerRegistry,在 ServiceProvider::boot() 里显式注册。四种形态在 spec 映射表里并列为官方——挂载是各语言惯用形态,不强制统一。

实现进度(截至 2026-09-04,issues/104 收口批次):CC_CREATE 这条喊声,六门语言 6/6 全部就位。上一版稿子写这篇时还是"五门已加、Rust 待补"——收口批次里 Rust 引擎把 CcCreate 补上了(复用 4 号位死码,jeeflow-rust 1.0.8 发版),salvo 集成层同批接线;Java / Go / Python / Node 引擎 bump 到 1.8.25、PHP 到 1.3.9,八栈集成仓同步升级,六个生产镜像全部换新。抄送知会这条消息,现在在任何一门语言的引擎上发流程,抄送人都收得到——"契约已冻结、实现按批次推进"的中间态结束,全绿。

顺带交代这批收口里最有意思的一个发现:"旁路不能拖垮主流程"这条铁律,引擎自己此前并不都做得到。 盘点六语言引擎的 publisher(issues/104 P2)发现:只有 PHP 是逐监听器 try/catch 的;Java/Go/Node/Rust 都是裸调 for-loop——一个坏监听器抛异常,后续监听器全部不执行,异常还会直接传播进审批主流程;Python 的单回调异常同样直接传播。收口批次把五门语言统一修成了 per-listener 兜底(Java try/catch+JUL、Go defer recover、Node try-catch、Python try-except、Rust catch_unwind),每门语言配了一条兜底测试:塞一个必抛异常的坏监听器,断言后续监听器仍被调用、主流程不受影响。"消息是旁路"从此由引擎侧自己兜住,不再只依赖集成层自觉。

这一节想说的是:六语言对齐的不是"名字一样",是"行为一样"。 事件叫什么、码值是几、挂在哪儿,随语言习惯各走各的;但"任务落库后 fire 一次""办结和拒绝都算实例结束""抄送逐人 fire""引擎只喊不保证落库"——这些语义是硬约束,一栈漂了,那栈的消息就会和其它栈对不上。


六、想接钉钉 / 企微 / 邮箱,加个监听器就行

把这篇收拢,其实是三条线:

  1. 引擎开两类口子:要流程走哪/谁能办,用拦截器(同步、能改 Execution、排在落库前);要被通知发生了什么,用事件(落库后 fire、只带 id、不改流程)。"要拦用拦截器,要通知用事件。"
  2. 消息模块站在事件的下游:引擎喊 TaskCreate / InstanceEnd / CC_CREATE,监听器把它们一一翻译成 TODO / NOTICE / 抄送知会,反查仓储补全数据,落进 sys_message。中间那道线是契约:引擎只喊、不保证落库;落库、过滤、兜底、开关,全是集成层的活。
  3. 这条边界是踩坑踩出来的,不是设计出来的:写路径整个缺失(100)、一门语言不会喊(101)、一类该喊的没喊(102),三层缺口逐个补上;然后 104 把"六语言实现参差"这个最后的坑也填了——CC_CREATE 补齐 6/6、publisher 兜底统一、差异表固化成 spec 官方映射表,"引擎只喊、集成层翻译"从共识变成了有官方对照表的契约。

而这套分工最爽的一点在最后:引擎对"消息往哪发"完全无感。 你的消息今天落 sys_message 表,明天想同时推一条钉钉、发一封邮箱、投一个 MQ——引擎一行代码都不用动,你只需要再写一个监听器,往 ServiceContext(或各语言的 extensions/registry)里再 register 一个,去消费同一批事件就行。

一个引擎,六门语言都在用的消息模块,未来要扩成 N 个渠道——加的都是监听器,动的是集成层。引擎始终只做它最擅长的那件事:把流程稳稳地推到下一个该停的地方,然后在关键点上喊那么几声。 剩下的,交给听到喊声的人。


参考资料


引擎从不发一条消息:jeeflow 的两类扩展点与消息模块的分工》 是转载文章,点击查看原文


相关推荐


一个 SKILL.md 拿下 2.3 万 Star:让 Codex/Claude Code 少说废话、先给答案
JavaGuide2026/8/29

用 Coding Agent 改代码,最磨耐心的情况之一,就是它明明答对了,答案却藏在一堆废话里。 你让 Codex 排查一个接口为什么返回 401,它可能先查认证流程,再分析中间件、Token 和 Cookie,顺手提醒你依赖版本也该升级了。全部看完之后,真正要执行的命令只有一条。 任务再长一点,人更容易跟丢。Agent 写了很多,却没说现在做到第几步;测试失败了,也没告诉你先看哪个文件。最后再来一句“希望这能帮到你”,问题还停在原地。 前几天技术群里有朋友分享了一个名字很有意思的项目 i-h


AI编程实战:让 AI 修 Bug,从报错到修复
机构师2026/8/21

文章目录 AI编程实战:让 AI 修 Bug,从报错到修复写在前面一、贴报错的正确姿势二、三个真实案例三、多轮定位技巧四、修完要回归小结与下篇预告小作业 AI编程实战:让 AI 修 Bug,从报错到修复 写在前面 Debug 是 AI 最擅长、也最常用的场景之一,但很多人习惯只贴一半报错,效果大打折扣。这篇讲怎么让 AI 高效修 Bug。 一、贴报错的正确姿势 把三样东西一次给全:完整报错 + 相关代码 + 环境信息。 运行报错,请帮我定位并修复。 - 报错:(粘贴完整


GitHub #1 拆解|它说自己在进化,但 worker 跑的是你本机权限,不是沙箱
苏灿烤鱼2026/8/8

GitHub #1 拆解|它说自己在进化,但 worker 跑的是你本机权限,不是沙箱 让可复用经验留下,让错误改动能回滚。 ⚡️ 30 秒速读:PrimeIntellect-ai/prime-agent 新上榜即登顶 GitHub Trending #1,今日新增 2,271 星。它用 RLM 把上下文当变量,用可执行 Skill 和 Continual Harness 保存可 refine 的经验。亮点:技能可被 IPython 直接调用,会话可后台续跑;风险:官方明确不是安全沙箱,v


Android 面试系列:Retrofit 的 suspend 是如何实现的?
潜龙勿用之化骨龙2026/7/30

今天这个是送分题,但是仍然有很多人不清楚,今天就聊聊这个 比如: interface UserApi { @GET("user") suspend fun getUser(): User } 调用: val user = api.getUser() 这里 Retrofit 并不是同步等待网络结果。 它只是把原来的异步流程转换成了 Kotlin 协程可以理解的挂起恢复流程。所以你不要再withContext 一下。 Retrofit 是怎么知道这是 suspend 方法?


为什么学习 Go?Go 能做什么?
程序员爱钓鱼2026/7/22

Go 编程实战系列(一) 近年来,无论是云计算、容器技术、微服务,还是人工智能基础设施,Go(又称 Golang)几乎都占据着重要位置。从 Docker、Kubernetes 到 Prometheus、Etcd,再到大量互联网公司的后端服务,Go 已经成为现代服务端开发的重要语言之一。 对于刚开始学习编程的人来说,可能会有这样几个问题: Go 为什么越来越流行? Go 和 Java、Python、Rust、C++ 有什么区别? 学 Go 能做哪些项目? 学完 Go 能找到什么工


Cursor安全插件链:代码审计新范式
lideiguo2026/7/13

一、 引言:当AI代码助手成为安全审计的“双刃剑” 核心问题:随着 Cursor、GitHub Copilot 等 AI 代码助手的普及,开发效率大幅提升,但同时也引入了新的安全风险——AI 生成的代码可能存在漏洞,而开发者可能因过度信任而疏于审计。 新范式诞生:“安全插件链”应运而生,它通过将静态代码分析、依赖检查、漏洞扫描等安全工具与 Cursor 等 IDE 深度集成,在代码生成、修改、提交等关键环节自动介入,形成“开发即审计”的闭环。 本文目标:探讨 Cursor 安全插件链的核心


基于MATLAB图像处理的饮料瓶识别与价格显示系统设计与实现
7zcode2026/7/5

摘要:随着图像处理技术在商品识别、智能零售和自动检测领域的广泛应用,传统依靠人工判断饮料种类和价格的方式存在效率低、主观性强、信息统计不便等问题。针对饮料瓶图像识别与价格自动显示需求,本文设计并实现了一套基于MATLAB图像处理的饮料瓶识别与价格显示系统。 项目概览 项目简介 本文设计并实现了一种基于MATLAB的饮料瓶智能识别与价格统计系统。该系统采用计算机视觉技术,结合HSV颜色空间阈值分割和形状特征分析两种识别方法,实现对常见饮料瓶的自动识别与分类。系统首先通过图像预处理获取饮料


spring-ai-starter-mcp-server-webmvc的包冲突
山塘小鱼儿2026/6/27

搭建spring-ai的mcp服务,引用官网的包 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://m


斥资500元/上亿Token,深度横评4个顶尖模型的真实排名~
AI袋鼠帝2026/6/18

大家好,我是袋鼠帝。 6月,感觉又是模型爆发的月份。 前有MiniMax-M3,然后是Claude Fable 5,到Kimi 2.7-code、GLM-5.2,麻了。 我现在都不想看模型的各种榜单、跑分了,感觉有很多刷榜的嫌疑。 而且,每次我测新模型,都有很多朋友在评论区比较其他模型。 毛选说的好,没有调查就没有发言权,实践是检验真理的唯一标准! 这次就来给大家做个顶级模型横测吧 看了下,kimi最新模型主要是编程能力强,这次我想测多个维度。GLM-5.2的Coding Plan暂时没抢到。。


警惕供应链陷阱:从 Red Hat npm 恶意包事件看依赖安全防护
在水一缸2026/6/10

警惕供应链陷阱:从 Red Hat npm 恶意包事件看依赖安全防护 在现代软件开发的宏大叙事中,开源供应链安全已经成为最惊心动魄的章节之一。近期,安全社区爆出的一起针对 Red Hat Cloud Services 的恶意 npm 包事件,再次为我们敲响了警钟。这不仅仅是一次简单的安全通报,它揭示了当前软件供应链攻击手段的隐蔽性与危害性正在不断升级。 对于中级开发者而言,理解这类攻击的底层逻辑、掌握识别恶意包的技巧以及建立系统性的防御思维,已成为职场进阶的必修课。本文将深入剖析这一事件的攻击原

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读