✨ feat(server): 增加混淆发布链路与调试页静态说明
- 新增 Obfuscar 混淆发布脚本、发布测试与产物隐私检查 - 调整 VS Code 发布任务和宿主发布配置,统一走混淆流程 - 为 debug 页面内嵌静态接口说明,并补充对应集成测试 - 更新 README 与设计计划文档,记录跨平台发布约束
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<!-- 生成 XML 文档以便 Swashbuckle 把控制器/DTO 上的 /// summary 注释注入 OpenAPI 文档,
|
||||
调试页和 Swagger UI 的端点标题都依赖这一份文档。1591 抑制掉 “缺失 XML 注释” 的噪音。 -->
|
||||
<!-- 生成 XML 文档以便 Swashbuckle 把控制器/DTO 上的 /// summary 注释注入 OpenAPI 文档;
|
||||
调试页说明文本已经静态内嵌在 debug.html 中,这份 XML 现在只服务 Swagger UI。1591 抑制掉 “缺失 XML 注释” 的噪音。 -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -40,7 +40,8 @@ try
|
||||
Version = swaggerOptions.Version
|
||||
});
|
||||
|
||||
// 把控制器与 DTO 上的 /// summary 注释纳入 OpenAPI 文档;调试页据此渲染端点标题。
|
||||
// 把控制器与 DTO 上的 /// summary 注释纳入 OpenAPI 文档;当前主要服务 Swagger UI,
|
||||
// 调试页的人类可读说明已经静态内嵌在 debug.html 中,不再依赖发布目录中的 XML 文档。
|
||||
var xmlDocumentationPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml");
|
||||
if (File.Exists(xmlDocumentationPath))
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const DEBUG_CONFIG_URL = "/api/debug/config";
|
||||
const STORAGE_PREFIX = "flyshot.debug.";
|
||||
const HISTORY_LIMIT = 10;
|
||||
const STATIC_DOCS_ELEMENT_ID = "debug-static-docs";
|
||||
|
||||
const groupTitleByPrefix = [
|
||||
// 基础与状态分组:探活和状态快照两个固定 API 路径
|
||||
@@ -13,7 +14,8 @@ const groupTitleByPrefix = [
|
||||
const state = {
|
||||
spec: null,
|
||||
operations: [],
|
||||
history: []
|
||||
history: [],
|
||||
staticDocsByKey: loadStaticDocs()
|
||||
};
|
||||
|
||||
const requestBodySamples = {
|
||||
@@ -34,6 +36,29 @@ function escapeHtml(value) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 为 METHOD PATH 文档块构造统一索引键。 */
|
||||
function buildOperationKey(method, path) {
|
||||
return String(method || "").toUpperCase() + " " + String(path || "");
|
||||
}
|
||||
|
||||
/** 读取 debug.html 中内嵌的静态说明 JSON,并建立 METHOD PATH 索引。 */
|
||||
function loadStaticDocs() {
|
||||
const docNode = document.getElementById(STATIC_DOCS_ELEMENT_ID);
|
||||
if (!docNode) return {};
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(docNode.textContent || "{}");
|
||||
const indexed = {};
|
||||
Object.keys(parsed).forEach(function (key) {
|
||||
indexed[key] = parsed[key];
|
||||
});
|
||||
return indexed;
|
||||
} catch (error) {
|
||||
console.warn("读取静态调试说明失败:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析 OpenAPI 中的 $ref 引用,仅支持本地 components.schemas 形式。 */
|
||||
function resolveRef(ref) {
|
||||
if (!ref || !state.spec) return null;
|
||||
@@ -49,34 +74,49 @@ function resolveRef(ref) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/** 展开 schema,统一处理 $ref / oneOf / anyOf / allOf。 */
|
||||
function resolveSchema(schema, depth) {
|
||||
depth = depth || 0;
|
||||
if (!schema || depth > 4) return null;
|
||||
|
||||
if (schema.$ref) {
|
||||
return resolveSchema(resolveRef(schema.$ref), depth + 1);
|
||||
}
|
||||
|
||||
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return resolveSchema(schema.oneOf[0], depth + 1);
|
||||
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return resolveSchema(schema.anyOf[0], depth + 1);
|
||||
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
||||
const merged = { type: "object", properties: {}, required: [] };
|
||||
schema.allOf.forEach(function (subSchema) {
|
||||
const resolved = resolveSchema(subSchema, depth + 1);
|
||||
if (!resolved) return;
|
||||
if (resolved.properties) Object.assign(merged.properties, resolved.properties);
|
||||
if (Array.isArray(resolved.required)) {
|
||||
resolved.required.forEach(function (name) {
|
||||
if (merged.required.indexOf(name) < 0) merged.required.push(name);
|
||||
});
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/** 根据 schema 生成默认 JSON 模板,用于自动填充请求体编辑器。 */
|
||||
function buildSampleFromSchema(schema, depth) {
|
||||
depth = depth || 0;
|
||||
// 防御递归:复杂自引用 schema 在 4 层后停下,避免栈爆。
|
||||
if (!schema || depth > 4) return null;
|
||||
|
||||
if (schema.$ref) {
|
||||
const resolved = resolveRef(schema.$ref);
|
||||
return resolved ? buildSampleFromSchema(resolved, depth + 1) : null;
|
||||
}
|
||||
const resolvedSchema = resolveSchema(schema, depth);
|
||||
if (!resolvedSchema) return null;
|
||||
|
||||
// 部分 schema 只标 oneOf/anyOf/allOf,挑第一个分支即可,调试场景够用。
|
||||
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return buildSampleFromSchema(schema.oneOf[0], depth + 1);
|
||||
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return buildSampleFromSchema(schema.anyOf[0], depth + 1);
|
||||
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
||||
const merged = {};
|
||||
schema.allOf.forEach(function (sub) {
|
||||
const value = buildSampleFromSchema(sub, depth + 1);
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) Object.assign(merged, value);
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
const type = schema.type || (schema.properties ? "object" : "string");
|
||||
const type = resolvedSchema.type || (resolvedSchema.properties ? "object" : "string");
|
||||
switch (type) {
|
||||
case "object": {
|
||||
const result = {};
|
||||
const props = schema.properties || {};
|
||||
const props = resolvedSchema.properties || {};
|
||||
Object.keys(props).forEach(function (key) {
|
||||
result[key] = buildSampleFromSchema(props[key], depth + 1);
|
||||
});
|
||||
@@ -91,7 +131,7 @@ function buildSampleFromSchema(schema, depth) {
|
||||
return false;
|
||||
case "string":
|
||||
default:
|
||||
if (schema.enum && schema.enum.length > 0) return schema.enum[0];
|
||||
if (resolvedSchema.enum && resolvedSchema.enum.length > 0) return resolvedSchema.enum[0];
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -99,13 +139,65 @@ function buildSampleFromSchema(schema, depth) {
|
||||
/** 把 schema.type 翻译成 input type 与展示文本。 */
|
||||
function inputKindForType(schema) {
|
||||
if (!schema) return { kind: "text", label: "string" };
|
||||
const type = schema.type || "string";
|
||||
const resolvedSchema = resolveSchema(schema, 0) || schema;
|
||||
const type = resolvedSchema.type || "string";
|
||||
if (type === "boolean") return { kind: "checkbox", label: "boolean" };
|
||||
if (type === "integer" || type === "number") return { kind: "number", label: type };
|
||||
return { kind: "text", label: type };
|
||||
}
|
||||
|
||||
/** 把 OpenAPI 的 paths 节点展开成扁平的 operation 列表。 */
|
||||
/** 把 schema 转成适合字段表展示的简短类型文本。 */
|
||||
function describeSchemaType(schema) {
|
||||
const resolvedSchema = resolveSchema(schema, 0);
|
||||
if (!resolvedSchema) return "unknown";
|
||||
|
||||
if (resolvedSchema.type === "array") {
|
||||
const itemType = describeSchemaType(resolvedSchema.items);
|
||||
return itemType === "unknown" ? "array" : "array<" + itemType + ">";
|
||||
}
|
||||
|
||||
if (resolvedSchema.type) return resolvedSchema.type;
|
||||
if (resolvedSchema.properties) return "object";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/** 当静态说明缺失时,从普通 object schema 推导字段表。 */
|
||||
function buildFieldDocsFromSchema(schema) {
|
||||
const resolvedSchema = resolveSchema(schema, 0);
|
||||
if (!resolvedSchema) return [];
|
||||
|
||||
const schemaType = resolvedSchema.type || (resolvedSchema.properties ? "object" : "");
|
||||
if (schemaType !== "object" || !resolvedSchema.properties) return [];
|
||||
|
||||
const requiredSet = new Set(resolvedSchema.required || []);
|
||||
return Object.keys(resolvedSchema.properties).map(function (name) {
|
||||
const propertySchema = resolvedSchema.properties[name];
|
||||
return {
|
||||
name: name,
|
||||
type: describeSchemaType(propertySchema),
|
||||
required: requiredSet.has(name),
|
||||
description: propertySchema && propertySchema.description ? propertySchema.description : "暂无说明"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** 规范化静态字段说明,避免缺字段时前端渲染异常。 */
|
||||
function normalizeFieldDocs(fieldDocs) {
|
||||
if (!Array.isArray(fieldDocs)) return [];
|
||||
|
||||
return fieldDocs.map(function (fieldDoc) {
|
||||
return {
|
||||
name: fieldDoc && fieldDoc.name ? String(fieldDoc.name) : "",
|
||||
type: fieldDoc && fieldDoc.type ? String(fieldDoc.type) : "unknown",
|
||||
required: Boolean(fieldDoc && fieldDoc.required),
|
||||
description: fieldDoc && fieldDoc.description ? String(fieldDoc.description) : "暂无说明"
|
||||
};
|
||||
}).filter(function (fieldDoc) {
|
||||
return fieldDoc.name !== "";
|
||||
});
|
||||
}
|
||||
|
||||
/** 把 OpenAPI 的 paths 节点展开成扁平的 operation 列表,并合并静态说明。 */
|
||||
function extractOperations(spec) {
|
||||
const operations = [];
|
||||
const paths = spec.paths || {};
|
||||
@@ -120,6 +212,11 @@ function extractOperations(spec) {
|
||||
const json = op.requestBody.content["application/json"];
|
||||
if (json && json.schema) bodySchema = json.schema;
|
||||
}
|
||||
|
||||
const operationKey = buildOperationKey(method, path);
|
||||
const staticDoc = state.staticDocsByKey[operationKey] || null;
|
||||
const staticFieldDocs = staticDoc ? normalizeFieldDocs(staticDoc.requestBodyFields) : [];
|
||||
const schemaFieldDocs = staticFieldDocs.length === 0 ? buildFieldDocsFromSchema(bodySchema) : [];
|
||||
operations.push({
|
||||
method: method.toUpperCase(),
|
||||
path: path,
|
||||
@@ -127,7 +224,11 @@ function extractOperations(spec) {
|
||||
description: op.description || "",
|
||||
tags: op.tags || [],
|
||||
parameters: parameters,
|
||||
bodySchema: bodySchema
|
||||
bodySchema: bodySchema,
|
||||
staticDoc: staticDoc,
|
||||
mergedSummary: staticDoc && staticDoc.summary ? staticDoc.summary : "暂无说明",
|
||||
mergedDescription: staticDoc && staticDoc.description ? staticDoc.description : "暂无说明",
|
||||
requestBodyFieldDocs: staticFieldDocs.length > 0 ? staticFieldDocs : schemaFieldDocs
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -150,7 +251,8 @@ function storageKey(op) {
|
||||
|
||||
/** 返回指定端点的手工调试样例,处理 JsonElement 等 OpenAPI 无法自动推断字段的接口。 */
|
||||
function getRequestBodySample(op) {
|
||||
return requestBodySamples[op.method + " " + op.path] || null;
|
||||
const operationKey = op.method + " " + op.path;
|
||||
return requestBodySamples[operationKey] || null;
|
||||
}
|
||||
|
||||
/** 读取本端点最近一次输入;解析失败则当作空。 */
|
||||
@@ -268,10 +370,80 @@ function renderParameterRows(container, op, savedValues) {
|
||||
};
|
||||
}
|
||||
|
||||
/** 渲染请求体字段说明表;静态说明缺失时退回 schema 推导。 */
|
||||
function renderRequestBodyFieldDocs(container, op) {
|
||||
if (!op.bodySchema && op.requestBodyFieldDocs.length === 0) return;
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "body-block";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "body-label";
|
||||
const left = document.createElement("div");
|
||||
left.className = "left";
|
||||
left.textContent = "请求体字段说明";
|
||||
title.appendChild(left);
|
||||
wrapper.appendChild(title);
|
||||
|
||||
if (op.requestBodyFieldDocs.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.style.color = "var(--muted)";
|
||||
empty.style.fontSize = "13px";
|
||||
empty.textContent = "暂无说明";
|
||||
wrapper.appendChild(empty);
|
||||
container.appendChild(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
const table = document.createElement("table");
|
||||
table.style.width = "100%";
|
||||
table.style.borderCollapse = "collapse";
|
||||
table.style.fontSize = "13px";
|
||||
|
||||
const headerRow = document.createElement("tr");
|
||||
["字段名", "类型", "必填", "说明"].forEach(function (text) {
|
||||
const th = document.createElement("th");
|
||||
th.textContent = text;
|
||||
th.style.textAlign = "left";
|
||||
th.style.padding = "6px 8px";
|
||||
th.style.borderBottom = "1px solid rgba(148, 163, 184, 0.25)";
|
||||
th.style.color = "var(--muted)";
|
||||
headerRow.appendChild(th);
|
||||
});
|
||||
table.appendChild(headerRow);
|
||||
|
||||
op.requestBodyFieldDocs.forEach(function (fieldDoc) {
|
||||
const row = document.createElement("tr");
|
||||
[
|
||||
fieldDoc.name,
|
||||
fieldDoc.type,
|
||||
fieldDoc.required ? "是" : "否",
|
||||
fieldDoc.description || "暂无说明"
|
||||
].forEach(function (value, index) {
|
||||
const cell = document.createElement("td");
|
||||
cell.style.padding = "8px";
|
||||
cell.style.verticalAlign = "top";
|
||||
cell.style.borderBottom = "1px solid rgba(148, 163, 184, 0.12)";
|
||||
if (index === 0 || index === 1) {
|
||||
cell.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
||||
}
|
||||
cell.textContent = value;
|
||||
row.appendChild(cell);
|
||||
});
|
||||
table.appendChild(row);
|
||||
});
|
||||
|
||||
wrapper.appendChild(table);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
/** 渲染请求体编辑器,返回收集函数。 */
|
||||
function renderBodyEditor(container, op, savedBody) {
|
||||
if (!op.bodySchema) return function () { return null; };
|
||||
|
||||
// 字段说明先于 JSON 编辑器出现,便于现场先看约定再改样例。
|
||||
renderRequestBodyFieldDocs(container, op);
|
||||
|
||||
const block = document.createElement("div");
|
||||
block.className = "body-block";
|
||||
|
||||
@@ -342,6 +514,7 @@ function pushHistory(entry) {
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
/** 渲染请求历史列表。 */
|
||||
function renderHistory() {
|
||||
const list = document.getElementById("history-list");
|
||||
list.innerHTML = "";
|
||||
@@ -376,7 +549,7 @@ function renderHistory() {
|
||||
/** 渲染单个端点的卡片。 */
|
||||
function renderOperationCard(op) {
|
||||
const card = document.createElement("section");
|
||||
card.className = "card collapsed";
|
||||
card.className = "card";
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.className = "card-head";
|
||||
@@ -393,13 +566,13 @@ function renderOperationCard(op) {
|
||||
|
||||
const summary = document.createElement("span");
|
||||
summary.className = "card-summary";
|
||||
summary.textContent = op.summary;
|
||||
summary.title = op.summary;
|
||||
summary.textContent = op.mergedSummary;
|
||||
summary.title = op.mergedSummary;
|
||||
head.appendChild(summary);
|
||||
|
||||
const toggle = document.createElement("span");
|
||||
toggle.className = "card-toggle";
|
||||
toggle.textContent = "展开 ▾";
|
||||
toggle.textContent = "收起 ▴";
|
||||
head.appendChild(toggle);
|
||||
|
||||
head.addEventListener("click", function () {
|
||||
@@ -411,15 +584,12 @@ function renderOperationCard(op) {
|
||||
const body = document.createElement("div");
|
||||
body.className = "card-body";
|
||||
|
||||
// 描述(来自 XML summary)独立成一段
|
||||
if (op.summary) {
|
||||
const desc = document.createElement("div");
|
||||
desc.style.color = "var(--muted)";
|
||||
desc.style.marginBottom = "10px";
|
||||
desc.style.fontSize = "13px";
|
||||
desc.textContent = op.summary;
|
||||
body.appendChild(desc);
|
||||
}
|
||||
const desc = document.createElement("div");
|
||||
desc.style.color = "var(--muted)";
|
||||
desc.style.marginBottom = "10px";
|
||||
desc.style.fontSize = "13px";
|
||||
desc.textContent = op.mergedDescription;
|
||||
body.appendChild(desc);
|
||||
|
||||
const saved = loadInputs(op) || {};
|
||||
|
||||
@@ -464,6 +634,7 @@ function renderOperationCard(op) {
|
||||
responseBlock.style.display = "none";
|
||||
body.appendChild(responseBlock);
|
||||
|
||||
/** 在卡片内渲染 HTTP 响应摘要、头和正文。 */
|
||||
function renderResponse(payload) {
|
||||
responseBlock.style.display = "block";
|
||||
responseBlock.innerHTML = "";
|
||||
|
||||
@@ -39,6 +39,247 @@
|
||||
</h3>
|
||||
<ul id="history-list"></ul>
|
||||
</aside>
|
||||
<script id="debug-static-docs" type="application/json">
|
||||
{
|
||||
"GET /": {
|
||||
"summary": "根路径探活",
|
||||
"description": "返回旧 HTTP 服务约定的 Hello World 响应,用来确认兼容层宿主已启动。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /healthz": {
|
||||
"summary": "宿主健康检查",
|
||||
"description": "返回 replacement 宿主的基础健康状态,适合探活和部署验收。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /status": {
|
||||
"summary": "跳转状态页",
|
||||
"description": "短路由入口,浏览器访问后会跳转到静态状态监控页 `/status.html`。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /debug": {
|
||||
"summary": "跳转调试页",
|
||||
"description": "短路由入口,浏览器访问后会跳转到静态调试页 `/debug.html`。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /api/status/snapshot": {
|
||||
"summary": "读取状态快照",
|
||||
"description": "返回当前 ControllerClient 兼容层和 FANUC 运行时的聚合状态快照,供状态页和外部诊断读取。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /connect_server/": {
|
||||
"summary": "记录旧客户端目标服务",
|
||||
"description": "兼容旧 `connect_server` 调用形状;在 replacement 宿主中只记录调用方期望连接的服务端地址,不会再启动旧 TCP+JSON 网关。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /get_server_version/": {
|
||||
"summary": "读取服务端版本",
|
||||
"description": "返回当前 replacement 宿主暴露给旧客户端语义的服务端版本号。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /get_client_version/": {
|
||||
"summary": "读取客户端版本",
|
||||
"description": "返回兼容层对外声明的客户端版本号,便于旧链路做版本诊断。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /setup_robot/": {
|
||||
"summary": "按名称加载机器人",
|
||||
"description": "根据机器人名称加载对应模型和运行配置,为后续连接、使能和执行做准备。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /setup_robot_from_env/": {
|
||||
"summary": "按环境文件加载机器人",
|
||||
"description": "兼容旧 `env_file` 入口,根据环境文件路径完成机器人初始化配置加载。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /set_show_tcp/": {
|
||||
"summary": "设置 TCP 显示开关",
|
||||
"description": "兼容旧可视化参数接口,记录是否显示 TCP 坐标轴及其尺寸参数。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /is_setup/": {
|
||||
"summary": "查询是否已初始化",
|
||||
"description": "返回当前兼容层是否已经完成 `SetUpRobot` 初始化。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /enable_robot/": {
|
||||
"summary": "使能机器人",
|
||||
"description": "调用兼容层使能机器人,并可通过查询参数传入控制器执行缓冲区大小。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /disable_robot/": {
|
||||
"summary": "去使能机器人",
|
||||
"description": "调用兼容层关闭机器人使能,通常用于结束调试或执行链路回收。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /stop_move/": {
|
||||
"summary": "停止当前运动",
|
||||
"description": "触发兼容层的 `StopMove` 语义,用于终止当前点动或轨迹执行。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /set_active_controller/": {
|
||||
"summary": "切换控制器模式",
|
||||
"description": "通过查询参数 `sim` 指定当前使用仿真控制器还是真机控制器。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /connect_robot/": {
|
||||
"summary": "连接机器人控制器",
|
||||
"description": "根据查询参数 `ip` 连接机器人控制器,建立后续命令与状态链路。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /disconnect_robot/": {
|
||||
"summary": "断开机器人连接",
|
||||
"description": "主动断开当前机器人控制器连接,释放兼容层运行时资源。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /robot_info/": {
|
||||
"summary": "读取机器人信息",
|
||||
"description": "返回机器人名称、自由度、速度倍率和服务端版本等聚合元信息。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /set_tcp/": {
|
||||
"summary": "设置 TCP 偏移",
|
||||
"description": "设置当前工具中心点的三维偏移,单位按旧接口约定使用毫米。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "x", "type": "number", "required": true, "description": "TCP 的 X 偏移,单位 mm。" },
|
||||
{ "name": "y", "type": "number", "required": true, "description": "TCP 的 Y 偏移,单位 mm。" },
|
||||
{ "name": "z", "type": "number", "required": true, "description": "TCP 的 Z 偏移,单位 mm。" }
|
||||
]
|
||||
},
|
||||
"GET /get_tcp/": {
|
||||
"summary": "读取 TCP 偏移",
|
||||
"description": "返回当前生效的 TCP 三维偏移值。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /set_io/": {
|
||||
"summary": "设置数字 IO",
|
||||
"description": "通过查询参数设置指定端口的 IO 值和 IO 类型,常用于现场脉冲或夹具联动调试。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /get_io/": {
|
||||
"summary": "读取数字 IO",
|
||||
"description": "读取指定端口和类型的当前 IO 值,便于确认控制柜当前输入输出状态。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"GET /get_joint_position/": {
|
||||
"summary": "读取当前关节位置",
|
||||
"description": "返回旧 HTTP 层兼容的关节数组外形,便于现场确认当前姿态。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /move_joint/": {
|
||||
"summary": "执行关节点到点运动",
|
||||
"description": "根据目标关节数组执行一次关节空间点到点运动;实际执行链路会进入兼容层与 J519 运行时。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "joints", "type": "array<number>", "required": true, "description": "目标关节数组,按关节顺序给出数值。" }
|
||||
]
|
||||
},
|
||||
"POST /move_pose/": {
|
||||
"summary": "执行笛卡尔点到点运动",
|
||||
"description": "以 `[x,y,z,w,p,r]` 直角位姿执行点到点移动。六个字段必须完整提供,`x/y/z` 使用 mm,`w/p/r` 使用 deg,并受当前入口限幅约束。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "x", "type": "number", "required": true, "description": "TCP X,单位 mm,当前入口限制在 -1000 到 1000 之间。" },
|
||||
{ "name": "y", "type": "number", "required": true, "description": "TCP Y,单位 mm,当前入口限制在 -1000 到 1000 之间。" },
|
||||
{ "name": "z", "type": "number", "required": true, "description": "TCP Z,单位 mm,当前入口限制在 0 到 1200 之间。" },
|
||||
{ "name": "w", "type": "number", "required": true, "description": "姿态 W,单位 deg,当前入口限制在 -180 到 180 之间。" },
|
||||
{ "name": "p", "type": "number", "required": true, "description": "姿态 P,单位 deg,当前入口限制在 -90 到 90 之间。" },
|
||||
{ "name": "r", "type": "number", "required": true, "description": "姿态 R,单位 deg,当前入口限制在 -180 到 180 之间。" }
|
||||
]
|
||||
},
|
||||
"POST /get_nearest_ik/": {
|
||||
"summary": "求最近 IK",
|
||||
"description": "根据目标位姿和 seed 关节数组求最近一组 IK 解;当前若底层未实现,会返回 501。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "pose", "type": "array<number>", "required": true, "description": "目标位姿数组,约定为 `[x,y,z,qx,qy,qz,qw]`。" },
|
||||
{ "name": "seed", "type": "array<number>", "required": true, "description": "IK seed 关节数组,用作就近求解参考。" }
|
||||
]
|
||||
},
|
||||
"GET /list_flyShotTraj/": {
|
||||
"summary": "列出已上传飞拍轨迹",
|
||||
"description": "返回当前运行目录中已经保存的飞拍轨迹名称列表。",
|
||||
"requestBodyFields": []
|
||||
},
|
||||
"POST /execute_trajectory/": {
|
||||
"summary": "执行普通轨迹",
|
||||
"description": "执行普通关节轨迹。该接口兼容两种历史请求体形态:一是对象形态 `{ waypoints, method, save_traj }`,二是直接传纯数组路点;查询参数里的 `method` 和 `save_traj` 仍可覆盖请求体值。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "waypoints", "type": "array<array<number>> | array<object>", "required": true, "description": "历史兼容对象形态中的轨迹路点集合;每个路点既可直接是关节数组,也可写成 `{ joints: [...] }`。" },
|
||||
{ "name": "method", "type": "string", "required": false, "description": "对象形态中的轨迹规划方法,默认 `icsp`。" },
|
||||
{ "name": "save_traj", "type": "boolean", "required": false, "description": "对象形态中的是否保存轨迹标记,默认 `false`。" },
|
||||
{ "name": "根数组", "type": "array<array<number>> | array<object>", "required": true, "description": "纯数组形态时,请直接提交路点数组本身,而不是对象包裹层。" },
|
||||
{ "name": "根数组[].joints", "type": "array<number>", "required": false, "description": "当路点元素是对象形态时,兼容层会读取其中的 `joints` 数组。" }
|
||||
]
|
||||
},
|
||||
"POST /upload_flyshot/": {
|
||||
"summary": "上传飞拍轨迹",
|
||||
"description": "上传一条带拍照触发信息的飞拍轨迹。`waypoints`、`shot_flags`、`offset_values`、`addrs` 四组数据长度必须一致。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "name", "type": "string", "required": true, "description": "飞拍轨迹名称,后续执行、校验和删除都通过它定位。" },
|
||||
{ "name": "waypoints", "type": "array<array<number>>", "required": true, "description": "飞拍轨迹的关节路点集合。" },
|
||||
{ "name": "shot_flags", "type": "array<boolean>", "required": true, "description": "与每个路点对齐的拍照触发标志数组。" },
|
||||
{ "name": "offset_values", "type": "array<number>", "required": true, "description": "与每个路点对齐的触发偏移周期集合。" },
|
||||
{ "name": "addrs", "type": "array<array<integer>>", "required": true, "description": "与每个路点对齐的 IO 地址组集合。" }
|
||||
]
|
||||
},
|
||||
"POST /execute_flyshot/": {
|
||||
"summary": "执行已上传飞拍轨迹",
|
||||
"description": "按名称执行一条已上传的飞拍轨迹,可控制是否先移动到起点、是否复用缓存、是否保存轨迹以及是否等待执行完成。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "name", "type": "string", "required": true, "description": "要执行的飞拍轨迹名称。" },
|
||||
{ "name": "move_to_start", "type": "boolean", "required": false, "description": "是否先用临时 PTP 轨迹移动到飞拍起点,默认 `true`。" },
|
||||
{ "name": "method", "type": "string", "required": false, "description": "飞拍轨迹生成方法,默认 `icsp`。" },
|
||||
{ "name": "save_traj", "type": "boolean", "required": false, "description": "是否保存轨迹信息,默认 `true`。" },
|
||||
{ "name": "use_cache", "type": "boolean", "required": false, "description": "是否优先复用已缓存的轨迹结果,默认 `true`。" },
|
||||
{ "name": "wait", "type": "boolean", "required": false, "description": "是否等待整条飞拍轨迹执行完成后再返回,默认 `true`。" }
|
||||
]
|
||||
},
|
||||
"POST /save_traj_info/": {
|
||||
"summary": "导出轨迹文件",
|
||||
"description": "为指定飞拍轨迹生成并导出 `SaveTrajInfo` 兼容文件集合;底层未实现时会返回 501。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "name", "type": "string", "required": true, "description": "目标飞拍轨迹名称。" },
|
||||
{ "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`。" }
|
||||
]
|
||||
},
|
||||
"POST /is_flyShotTrajValid/": {
|
||||
"summary": "校验飞拍轨迹有效性",
|
||||
"description": "检查指定飞拍轨迹是否可生成有效执行结果,并返回轨迹时长;底层未实现时会返回 501。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "name", "type": "string", "required": true, "description": "要校验的飞拍轨迹名称。" },
|
||||
{ "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`。" },
|
||||
{ "name": "save_traj", "type": "boolean", "required": false, "description": "是否同时保存轨迹信息,默认 `true`。" }
|
||||
]
|
||||
},
|
||||
"POST /set_speedRatio/": {
|
||||
"summary": "设置运行速度倍率",
|
||||
"description": "设置机器人运行速度倍率。当前接口要求 `speed` 在 `0.01` 到 `1.0` 之间,否则会直接返回 400。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "speed", "type": "number", "required": true, "description": "目标速度倍率,允许范围为 0.01 到 1.0。" }
|
||||
]
|
||||
},
|
||||
"POST /delete_flyshot/": {
|
||||
"summary": "删除飞拍轨迹",
|
||||
"description": "按名称删除一条已上传的飞拍轨迹及其持久化记录。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "name", "type": "string", "required": true, "description": "要删除的飞拍轨迹名称。" }
|
||||
]
|
||||
},
|
||||
"POST /init_mpc_robt": {
|
||||
"summary": "一键初始化旧链路",
|
||||
"description": "兼容旧 `init_mpc_robt` 入口,按历史拼写保留。会串联执行 `SetUpRobot -> SetActiveController -> Connect -> EnableRobot`。",
|
||||
"requestBodyFields": [
|
||||
{ "name": "server_ip", "type": "string", "required": true, "description": "旧客户端记录的目标服务端 IP,仅保留兼容语义。" },
|
||||
{ "name": "port", "type": "integer", "required": true, "description": "旧客户端记录的目标服务端端口。" },
|
||||
{ "name": "robot_name", "type": "string", "required": true, "description": "机器人名称,用于加载模型与配置。" },
|
||||
{ "name": "robot_ip", "type": "string", "required": true, "description": "机器人控制器 IP。" },
|
||||
{ "name": "sim", "type": "boolean", "required": false, "description": "是否连接仿真控制器;默认 `false` 表示真机。" }
|
||||
]
|
||||
},
|
||||
"GET /get_pose": {
|
||||
"summary": "读取当前末端位姿",
|
||||
"description": "返回当前末端位姿数组,便于确认机器人 TCP 的当前空间位置。",
|
||||
"requestBodyFields": []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/assets/debug.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user