news 2026/10/24 3:27:15

Camunda BPM REST API 高频操作实战:从部署到任务完成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Camunda BPM REST API 高频操作实战:从部署到任务完成

有经验的人都知道,拿到一套 Camunda BPM 流程引擎,最难的往往不是画流程图,而是怎么让外部业务系统真正"用"起来。流程定义部署到引擎里只是第一步,接下来前端页面要发起申请、审批人要处理待办、后台脚本要查询流程状态,这些动作全部都得通过 API 完成。在 Camunda 的体系里,最直接、最通用的一条路就是 Rest-Api。这篇文章就围绕 Camunda-BPM-Rest-Api 的基本调用展开,重点讲部署流程、启动实例、查询任务、完成任务这几组高频操作,把请求格式和返回结构一次说清楚,再附上我在实际对接时踩过的坑。适合刚接触 Camunda、准备把流程引擎接进现有系统的后端开发或集成工程师参考。

1. 先搞清楚:REST API 在流程引擎里的位置,以及什么场景不该用

1.1 引擎的两套对外窗口

Camunda 本质上是一个跑在 JVM 里的 BPM 引擎,给了开发者两套操作入口。第一套是原生 Java API,也就是在 Spring Boot 或任何 Java 服务里直接注入RepositoryService、RuntimeService、TaskService,调方法完成部署、启动、处理任务;第二套就是 REST API,它是在 Java API 之上封装出来的一层 HTTP 接口,把引擎能力拆成一个个 URL 端点,请求和响应全部走 JSON。

很多初学者纠结该用哪套。我的判断标准很简单:如果你的业务系统本身就是 Java 技术栈,并且和引擎跑在同一个进程里,直接调 Java API 更舒服,类型安全、调试方便、事务边界也好控制。但凡是涉及跨语言、前后端分离、自动化脚本,或者你不想让项目跟 Camunda 的 Java 类库深度耦合,REST API 就是唯一合理的选择。它返回 JSON,任何现代语言都能消费,这也让"先用 curl 验证接口、再在业务代码里调用"的开发模式特别顺手。

1.2 REST API 的典型适用场景

我实际接触下来,下面几类场景几乎绕不开 REST API:

  • 前端页面直接驱动流程:Vue、React 项目里通过 axios 发起流程、查待办、提交审批结果,走的全是 REST。
  • 跨语言系统集成:公司里可能有 Golang、Python、Node 写的服务,不可能全部为了接一个工作流改成 Java。REST 是语言无关的公共通道。
  • 自动化脚本与测试:用 Python 脚本批量发起流程、用 curl 验证某个 BPMN 模型部署是否正确,开发效率比写 Java 单元测试高得多。
  • 低代码平台或外部编排工具:企业里用各类低代码平台搭流程门户,底层经常就是通过 REST 对接 Camunda。

1.3 什么场景不该硬上 REST API

有些场景用 REST API 反而会给自己找麻烦。第一,对吞吐量极敏感的内部循环调用,比如每次业务操作都要同步调流程引擎,走一次 HTTP 序列化和反序列化,性能和稳定性都不如进程内 Java API。第二,需要流程引擎和业务数据库共享同一个本地事务的场景,REST 调用天然跨进程,无法和调用方的事务边界保持一致,回滚就成了老大难问题。第三,你需要在引擎内部持有 Java 对象、注册 Java 监听器、通过表达式调用 Spring Bean 方法时,REST 调用传的都是 JSON,根本没法把 JVM 内部的对象引用塞进去。

理解"基本调用"的同时,认清它的边界反而能让你后面少走弯路。它相当于引擎对外开放的一扇门,但引擎本身的能力远不止门后那几间屋子。

2. 十分钟把环境跑起来:从 Camunda Run 到第一个 API 请求

2.1 选哪个发行版

Camunda 有几种常见发行方式:完整 Tomcat 发行版、Camunda Run、以及自己用 Spring Boot 集成。我的建议是,如果你只是想快速验证 REST API 的用法和流程模型,直接下载 Camunda Run。它是一个轻量级的独立服务,解压就能跑,里面对引擎、REST API、Cockpit 控制台都做了默认装配,不需要你自己搭 Tomcat 或写 Spring Boot 启动类。

下载的时候注意选择对应的操作系统压缩包,拿到手之后解压,进入目录你会看到一个start.bat或start.sh。在命令行里执行它,等日志输出启动完成,默认监听端口是 8080。这一步基本不会出问题,最容易踩的坑是端口被占用,改一下配置文件的端口即可。

2.2 启动后先验证 API 通不通

服务启动后,不要急着部署流程,先验证 REST API 是否真的可访问。打开浏览器或终端执行:

curl http://localhost:8080/engine-rest/engine

如果能返回类似下面的 JSON,说明 engine-rest 模块已经正常工作:

[ { "name": "default" } ]

这个接口返回的是当前 Camunda 平台里注册的引擎实例名称。默认情况下只有一个名为default的引擎,如果你在配置文件里自定义了引擎名称,这里会出现多个条目。后续调用所有业务接口时,请求路径里可以带引擎名,也可以不带,不带时默认打在这个唯一的引擎上。

提示:REST 接口的根路径是/engine-rest,完整业务地址形如http://localhost:8080/engine-rest/process-definition。有些版本或配置下会带/engine/default前缀,二者指向同一套端点,具体以你手头版本返回的文档为准。

同时你也可以打开http://localhost:8080/camunda/看看控制台页面是否正常。Cockpit 登录用的是 Camunda 自带用户,默认账号通常是demo/demo。这个账号属于演示用途,后续讲认证的时候我会专门说它的问题。

2.3 部署一个最小流程定义

环境通了之后,第一件正经事就是部署流程定义。Camunda 把 BPMN 文件看作资源,部署动作本质是把资源上传到引擎并解析成可运行的流程定义。先准备一个最简 BPMN 文件,我一般叫它leave-approve.bpmn:

<?xml version="1.0" encoding="UTF-8"?> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" targetNamespace="http://example.com/process"> <bpmn:process id="leave-approve" name="请假审批" isExecutable="true"> <bpmn:startEvent id="startEvent" name="开始"> <bpmn:outgoing>flow1</bpmn:outgoing> </bpmn:startEvent> <bpmn:sequenceFlow id="flow1" sourceRef="startEvent" targetRef="approveTask" /> <bpmn:userTask id="approveTask" name="主管审批" camunda:assignee="manager"> <bpmn:incoming>flow1</bpmn:incoming> <bpmn:outgoing>flow2</bpmn:outgoing> </bpmn:userTask> <bpmn:sequenceFlow id="flow2" sourceRef="approveTask" targetRef="endEvent" /> <bpmn:endEvent id="endEvent" name="结束"> <bpmn:incoming>flow2</bpmn:incoming> </bpmn:endEvent> </bpmn:process> </bpmn:definitions>

这个模型非常简单:开始事件 -> 一个主管审批的人工任务 -> 结束事件,流程定义 key 是leave-approve。注意写 BPMN 的时候一定要把isExecutable设为true,否则 Camunda 会忽略这个流程定义。

curl -X POST http://localhost:8080/engine-rest/deployment/create \ -F "deployment-name=leave-deployment" \ -F "enable-duplicate-filtering=true" \ -F "leave-approve.bpmn=@/path/to/leave-approve.bpmn"

部署接口走的是multipart/form-data格式,-F用来传表单字段。deployment-name是部署包的名字,enable-duplicate-filtering开启后,如果资源没变化,引擎会跳过重复部署并返回原部署记录。文件字段名就是文件名,文件内容用@引用本地路径。

部署成功后返回的 JSON 里最关键是deployedProcessDefinitions:

{ "id": "deployment-uuid", "name": "leave-deployment", "deployedProcessDefinitions": { "leave-approve": { "id": "leave-approve:1:xxxxx", "key": "leave-approve", "version": 1 } } }

拿到这个结果,说明流程定义已经成功解析并注册。如果你在这个阶段看到报错,优先检查 BPMN 文件路径是否正确、XML 内容是否完整、引擎日志里有没有 schema 解析异常。

注意:Camunda Run 默认使用 H2 内存数据库,重启服务后已部署的流程定义和流程实例会全部清空。想持久化,需要先切换到 MySQL 或 PostgreSQL,再重新部署一遍。开发阶段无所谓,但做联调环境时一定要提前配好数据库。

3. 五个必须会的高频端点:部署、启动、查任务、办任务、取变量

3.1 启动流程实例

部署完流程定义,下一步就是启动流程实例。启动接口最简单的方式是按流程定义 key 启动最新版本:

curl -X POST http://localhost:8080/engine-rest/process-definition/key/leave-approve/start \ -H "Content-Type: application/json" \ -d '{ "variables": { "applicant": {"value": "张三", "type": "String"}, "days": {"value": 3, "type": "Integer"} }, "businessKey": "BG-2025-001" }'

请求体里两个核心概念:variables用于传递流程变量,businessKey是业务唯一标识。流程变量这套 JSON 结构非常容易写错,每个变量都包含value和type字段,type必须对应 Java 类型名,比如String、Integer、Long、Boolean、Date。如果不指定type,引擎会尝试把传入的字符串按默认规则解析,结果往往不是你想要的,后面我会专门说这个坑。

响应会返回流程实例信息:

{ "id": "process-instance-id", "definitionId": "leave-approve:1:xxxxx", "businessKey": "BG-2025-001", "ended": false, "suspended": false }

ended=false表示实例还在运行中。到这里,一个请假申请就正式发起,主管审批的人工任务也已经创建好了。

3.2 查询待办任务

流程实例跑起来后,外部系统最常做的一件事就是查当前有哪些任务需要处理。查询任务用 GET 接口,过滤条件非常丰富:

curl "http://localhost:8080/engine-rest/task?processDefinitionKey=leave-approve&assignee=manager&active=true"

最常见的过滤参数包括assignee、candidateUser、candidateGroup、processDefinitionKey、taskDefinitionKey。上面这个请求查的是leave-approve流程里分配给manager的活动任务。响应是一个数组:

[ { "id": "task-id-123", "name": "主管审批", "processDefinitionKey": "leave-approve", "taskDefinitionKey": "approveTask", "assignee": "manager", "createTime": "2025-01-01T10:00:00", "formKey": null } ]

id是后续处理任务时必传的参数。实际项目中,前端任务列表页通常一次返回多笔任务,配合分页参数firstResult和maxResults使用即可。

3.3 完成任务

查到任务后,审批人在前端点"通过"或"拒绝",后端就是调 complete 接口:

curl -X POST http://localhost:8080/engine-rest/task/task-id-123/complete \ -H "Content-Type: application/json" \ -d '{ "variables": { "approved": {"value": true, "type": "Boolean"} } }'

完成任务时可以在variables里写入审批结果给后续网关判断。引擎收到请求后,会把这个任务从待办里移除,并根据 BPMN 连线的条件往下流转。如果用户任务之后还有排他网关,那么网关上表达式读到的就是这里传入的变量。

3.4 读取与写入流程变量

有时候外部系统需要查询当前流程实例里某个变量的值,比如门户首页展示请假天数,可以直接读流程实例变量:

curl "http://localhost:8080/engine-rest/process-instance/process-instance-id/variables"

返回结果形如:

{ "applicant": { "type": "String", "value": "张三" }, "days": { "type": "Integer", "value": 3 } }

这里要特别留意,REST API 读取变量时,返回的value不保证是原生类型,日期值通常会被序列化成 ISO 8601 字符串。你还要注意大对象变量的读取,默认会返回value本身,而不是valueInfo里的存储引用,这点和 Java API 行为不太一样。

修改变量值用 PUT 或 POST,我常用的是按变量名更新:

curl -X PUT http://localhost:8080/engine-rest/process-instance/process-instance-id/variables/days \ -H "Content-Type: application/json" \ -d '{"value": 5, "type": "Integer"}'

3.5 高频端点速查表

操作方法与路径请求格式关键返回字段
部署流程POST /deployment/createmultipart/form-datadeployedProcessDefinitions
按 key 启动实例POST /process-definition/key/{key}/startJSONid, definitionId
查询任务GET /taskquery paramsid, assignee, taskDefinitionKey
完成任务POST /task/{id}/completeJSON空/任务结果
读取变量GET /process-instance/{id}/variables无变量 JSON 对象
更新变量PUT /process-instance/{id}/variables/{name}JSON无

这五个端点基本覆盖了 90% 的日常开发需求。只要把部署、启动、查任务、办任务这条链路跑通,Camunda 的一个最小闭环就转起来了。

4. 把一张请假流程从头调到尾:一次完整调用链路复盘

4.1 场景设定与调用顺序

单独讲端点容易碎,我习惯用一条完整的业务流程把它们串起来。假设还是那张leave-approve请假流程:员工提交申请,生成一条流程实例,主管在待办列表看到任务,点击通过后流程结束。完整的调用顺序应该是:部署 -> 启动 -> 查询待办 -> 处理任务 -> 查询流程状态。

前面已经演示了部署和启动,这里我给出一个更贴近真实项目的 Python 脚本,把整个链路串起来。很多团队做接口联调时都是这么干的,比打开 Postman 一个个点要快得多。

4.2 用 Python 脚本串起全链路

import requests BASE_URL = "http://localhost:8080/engine-rest" # 第一步:部署流程定义 bpmn_path = "leave-approve.bpmn" with open(bpmn_path, "rb") as f: resp = requests.post( f"{BASE_URL}/deployment/create", data={"deployment-name": "leave-deployment", "enable-duplicate-filtering": "true"}, files={"leave-approve.bpmn": f}, ) resp.raise_for_status() print("部署成功:", resp.json()["id"]) # 第二步:启动流程实例,传入业务变量 start_resp = requests.post( f"{BASE_URL}/process-definition/key/leave-approve/start", json={ "businessKey": "BG-2025-001", "variables": { "applicant": {"value": "张三", "type": "String"}, "days": {"value": 3, "type": "Integer"}, }, }, ) start_resp.raise_for_status() process_instance_id = start_resp.json()["id"] print("流程实例已启动:", process_instance_id) # 第三步:查询主管的待办任务 task_resp = requests.get( f"{BASE_URL}/task", params={"processDefinitionKey": "leave-approve", "assignee": "manager", "active": "true"}, ) task_resp.raise_for_status() tasks = task_resp.json() print("待办任务数:", len(tasks)) if not tasks: raise RuntimeError("未查到待办任务") task_id = tasks[0]["id"] # 第四步:主管审批通过,写入布尔变量 approved complete_resp = requests.post( f"{BASE_URL}/task/{task_id}/complete", json={"variables": {"approved": {"value": True, "type": "Boolean"}}}, ) complete_resp.raise_for_status() print("任务已处理:", task_id) # 第五步:确认流程实例是否结束 instance_resp = requests.get(f"{BASE_URL}/process-instance/{process_instance_id}") instance_resp.raise_for_status() instance = instance_resp.json() print("流程是否结束:", instance.get("ended", False))

运行这个脚本,正常情况你会依次看到"部署成功"、"流程实例已启动"、"待办任务数: 1"、"任务已处理"、"流程是否结束: True"。

4.3 如果最后一步不是 ended=True

我实际跑这张流程的时候,第一次在最后一步返回的是ended: false。排查了半天,原因是我完成任务时传入的变量名和 BPMN 里排他网关的表达式对不上。这个流程里虽然没有网关,但只要你在真实模型里加了类似${approved}的条件连线,就必须确保变量名、类型与表达式完全一致。

另外请注意ended字段在 Camunda 不同版本里的行为不完全一样,有些版本启动实例时根本不返回ended字段,需要查历史接口才能确认真实状态。

4.4 查历史流程实例确认终态

遇到不确定状态的情形,最稳妥的办法是查历史流程实例接口:

curl "http://localhost:8080/engine-rest/history/process-instance?processInstanceId=process-instance-id"

历史接口返回的记录里会带state字段。COMPLETED表示正常跑完,CANCELED表示被外部取消,ACTIVE表示还在运行。在排查异常时,这个接口比直接查流程实例状态可靠得多。

5. 认证与安全:测试环境多省事,生产环境就多危险

5.1 默认情况下的匿名访问问题

前面所有 curl 和 Python 示例,我都没有加任何认证头,因为 Camunda Run 默认情况下 REST API 在没有额外配置时是开放访问的。这在本地开发时非常省事,但很多人就把这套配置原封不动搬到了测试服务器甚至生产环境,这是最危险的失误。我见过不止一次,直接把 8080 端口暴露在公网,结果流程引擎被人任意部署恶意流程、批量启动实例,数据库被打爆。

5.2 测试环境怎么开 Basic Auth

测试环境至少要把认证开起来。Camunda 的方式是在 engine-rest 这个模块上挂身份认证插件,启用以后,每次请求都要带Authorization: Basic base64(user:password)头。客户端请求变成这样:

curl -u demo:demo "http://localhost:8080/engine-rest/process-definition"

-u参数是 curl 里生成 Basic Auth 头的快捷写法,效果等价于手动加 Header:

curl -H "Authorization: Basic ZGVtbzpkZW1v" "http://localhost:8080/engine-rest/process-definition"

开启方式在不同发行版里略有差异,Camunda Run 可以在配置文件里加身份认证插件,Tomcat 发行版则需要修改 web.xml。配置完成后,Cockpit 和 REST API 都会要求登录。

5.3 生产环境的进阶建议

Basic Auth 本质是把用户名密码以 Base64 编码放到 HTTP Header 里,等于明文传输,一旦没有 HTTPS,抓包就等于泄露密码。生产环境我建议按下面几条做:

  • 全链路 HTTPS:明文传输的认证信息没有任何防护意义。
  • 网关层统一鉴权:在 REST API 前面加一层 OAuth2 或 JWT 校验,由网关验证调用方身份,Camunda 只对可信流量开放。
  • 最小权限账号:给 REST 调用方单独建低权限用户,不要用管理员账号跑业务集成。
  • 限制来源 IP 与频率:至少做到 IP 白名单或 VPC 内网访问,恶意请求根本到不了引擎层。

关于认证这块,我个人的体会是:不要在 Camunda 上投入精力自研复杂的用户权限体系。它作为流程引擎,核心价值在 BPMN 流转,身份认证和权限管理应该由统一认证平台或 API 网关承担。

6. 实际对接中我踩过的坑:404、租户隔离、变量类型的连环坑

6.1 启动流程时 404,问题可能出在 id 和 key 混用

第一次调启动接口时,我直接复制了部署返回的deploymentId当作启动参数去调,结果返回 404。原因是启动接口要的是process-definition/key/leave-approve/start里的 key,而不是部署包的 id。一个部署包可以包含多个 BPMN 文件,一个 BPMN 文件也可以被多次部署成不同版本,deploymentId和流程定义 key 是完全不同的概念。

排查思路是先拉一下流程定义列表:

curl "http://localhost:8080/engine-rest/process-definition?key=leave-approve&latestVersion=true"

如果返回空数组,说明流程压根没部署成功;如果返回了数据,再看返回里的id和key到底用的哪个。后续调用统一以key为准,最不容易绕晕。

6.2 多租户环境下查不到流程定义

后来某个项目启用了 Camunda 的多租户功能,我部署 BPMN 时给流程定义了 tenantId,结果用不带的 tenantId 的账号去查流程定义,返回 404 或者空数组。查了一阵文档才发现,REST API 的查询接口支持tenantIdIn参数:

curl "http://localhost:8080/engine-rest/process-definition?tenantIdIn=tenant-demo"

更麻烦的是,如果你部署的资源没有指定 tenantId,但启动请求里带了租户上下文,引擎同样会查不到。解决思路很土但有效:部署前先明确资源归属的租户,查询和启动时统一带上相同的tenantId参数。多租户导致的 API 可见性问题不会报很明确的错误信息,全靠对比参数定位。

6.3 变量类型没指定,Boolean 被存成了 String

我遇到的最隐蔽的一个坑是流程变量类型。有一次审批通过后,排他网关照样走了拒绝分支,查了半天才发现,完成任务时我写的是:

{ "variables": { "approved": {"value": "true"} } }

这里没指定type,引擎就把approved存成了字符串"true",而 BPMN 网关上写的是${approved == true}。表达式比较的时候,布尔true和字符串"true"永远不相等。改法就是在 REST 请求里显式加"type": "Boolean":

{ "variables": { "approved": {"value": true, "type": "Boolean"} } }

凡是涉及数值比较、布尔判断、日期范围的流程变量,调用前一定确认type字段,这是 Camunda REST API 最容易踩的类型陷阱。日期变量更夸张,传错了格式引擎直接解析失败,报 500。

6.4 任务查不到,先分清 assignee 和 candidate

做任务列表的时候,我用assignee=manager去查,返回空数据,但 Cockpit 里明明能看到一个待办任务。原因是我在 BPMN 里给用户任务配的是camunda:candidateUsers="manager",而不是assignee。候选人和受托人是两种不同的任务占有模式:assignee是明确指定处理人,candidateUser只是候选,需要一个 claim 认领动作之后,任务才会落到具体人名下。

如果流程设计得比较规范,待办查询通常走candidateUser或candidateGroup查询,然后调认领接口:

curl -X POST "http://localhost:8080/engine-rest/task/task-id-123/claim" \ -H "Content-Type: application/json" \ -d '{"userId": "manager"}'

认领之后再用assignee=manager查,结果就对了。这块逻辑如果搞混,任务列表页和数据看板都会出现严重的数据偏差。

6.5 部署成功但启动就报错:表达式和监听器问题

还有一类问题是部署阶段一切正常,一旦启动流程实例就抛异常。常见原因有两个。一是 BPMN 里的表达式引用了不存在的 Spring Bean,比如连线条件写了${orderService.canApprove(approved)},但引擎的 classpath 里根本没有这个 Bean;二是注册了 Java 类监听器,但类没被引擎扫描到。这类错误通常无法在部署阶段暴露,只有启动时反射失败或表达式解析失败才会触发。

排查建议很直接:把引擎日志级别调成 DEBUG,启动一次实例,看异常栈具体卡在哪一行。大多数情况下,报错信息会明确指出是表达式解析失败还是监听器类加载失败,顺着修就行。

现象常见原因处理方式
启动接口返回 404流程 key 不存在或部署失败查询流程定义列表确认 key
查询不到流程定义多租户 tenantId 不匹配查询和启动时统一带 tenantIdIn
变量判断异常未指定变量 type 导致类型错误显式指定 Boolean/Integer 等类型
任务列表为空assignee 与 candidateUser 混淆先 claim 再查询,或改用 candidate 参数
启动实例抛异常表达式或监听器类缺失查看引擎 DEBUG 日志定位表达式

最后再分享一点个人习惯

如果非要给刚接触 Camunda REST API 的朋友一个建议,我会说:先不要急着写业务代码,花半天时间用 curl 把部署、启动、查任务、完成任务这条链路完整敲一遍,把每一步的返回 JSON 都仔细看一遍。这个"手动跑通"的过程比任何文档都更能帮你建立对引擎的直觉。我在实际项目里也是这么带新人的,大家上手速度明显快很多。等你把基本调用练熟了,再去研究历史查询、消息事件、外部任务这些进阶接口,就会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 3:26:18

子弹3D模型下载与工业级验证指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 3:24:03

WGAN-GP与谱归一化:从玄学到可复现的GAN稳定训练

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 3:22:58

基于PCA9422与PIC18F87J50的便携设备可编程电源管理设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 3:22:14

便携浏览器安全测试工作台:Profile隔离、多开与证据归档

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 3:22:08

偏振图像去噪:保偏振角精度与斯托克斯分量的物理约束方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 3:20:09

主程序/子程序架构:2025年仍被低估的稳定架构风格

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华