docs: complete console application scope acceptance and deployment guidance

This commit is contained in:
2026-09-27 12:29:31 +08:00
parent 1f2e7230dc
commit c2ca5e987f
10 changed files with 46 additions and 10 deletions

View File

@@ -65,7 +65,7 @@
| 业务结果 Inbox | 事件去重与业务更新同事务,提交成功后确认交付 | [result-inbox](examples/result-inbox/README.md) |
| HTTP 服务完整生命周期 | 业务接口、Outbox、Worker、结果消费、就绪检查和有界关闭放进同一业务服务 | [http-service](examples/http-service/README.md) |
| 平台身份与凭据 | 应用/命名空间/队列授权范围、凭据轮换/撤销;SDK 不直连平台数据库 | [鉴权](docs/api/authentication.md) · [凭据管理](docs/api/authentication.md) |
| 账号密码控制台 | 初始化管理员,登录查看统计、任务/计划/流程/交付详情和管理操作,未登录跳转登录页 | [Console 手册](docs/console.md) |
| 账号密码控制台 | 初始化管理员,按应用/环境/队列查看任务、计划、流程、交付与归档,列表和详情显示应用归属 | [Console 手册](docs/console.md) · [应用范围](docs/api/console-scope.md) |
| 保留策略与周期归档 | 配置各类数据保留天数,持久化归档计划/作业,分页、续跑、重试与文件核验 | [保留策略](docs/api/retention.md) · [周期归档](docs/api/archive-scheduler.md) |
| 运维与观测 | 健康/readiness、独立指标端口、迁移核验、负载工具、备份恢复与故障演练 | [观测](docs/api/observability.md) · [运维](docs/20-operations-runbook.md) |
@@ -223,6 +223,7 @@ fmt.Println(current.ID, current.State)
| `ORBIT_MYSQL_DSN` | 平台数据库连接,迁移账号与运行账号按部署手册分开 |
| `ORBIT_HTTP_ADDR` | 服务进程监听地址,例 `127.0.0.1:8080` |
| `ORBIT_CONSOLE_QUEUE_ID` | 控制台默认队列;不授予账号额外权限 |
| `ORBIT_CONSOLE_APPLICATION_ADMIN` | 指定唯一可接入和管理各应用的控制台登录名;空值不开放跨应用权限,多节点保持一致 |
| `ORBIT_DB_MAX_OPEN` / `ORBIT_DB_MAX_IDLE` | 数据库连接池上限 |
| `ORBIT_SHUTDOWN_TIMEOUT` | SIGTERM 关闭窗口,默认 10s |
| `ORBIT_WORKFLOW_TICK_INTERVAL` / `ORBIT_SCHEDULE_TICK_INTERVAL` | 流程/计划后台扫描间隔,默认 1s |

View File

@@ -24,6 +24,7 @@ x-orbit-environment: &orbit-environment
ORBIT_RETENTION_INBOX_DAYS: ${ORBIT_RETENTION_INBOX_DAYS:-90}
ORBIT_RETENTION_AUDIT_DAYS: ${ORBIT_RETENTION_AUDIT_DAYS:-2555}
ORBIT_CONSOLE_QUEUE_ID: ${ORBIT_CONSOLE_QUEUE_ID:-}
ORBIT_CONSOLE_APPLICATION_ADMIN: ${ORBIT_CONSOLE_APPLICATION_ADMIN:-}
ORBIT_METRICS_ADDR: ${ORBIT_METRICS_ADDR:-}
services:

View File

@@ -1,6 +1,6 @@
# Orbit HTTP / WebSocket 接口手册
更新:2026-09-18。接口实现状态随 [开发计划](../08-development-plan.md) 更新。
更新:2026-09-27。接口实现状态随 [开发计划](../08-development-plan.md) 更新。
## 当前接口状态
@@ -9,6 +9,7 @@
| `GET /health/live` | 已实现 | [健康检查](health.md) |
| `GET /health/ready` | 已实现,验证数据库、迁移记录及零外键要求 | [健康检查](health.md) |
| `GET /version` | 已实现,返回版本、提交、构建时间及协议版本,不包含数据库、凭据或租户信息 | [健康检查](health.md) |
| `GET /console/scope` 与控制台显式应用范围 | 已实现单管理员应用/环境切换、队列分页和列表归属;SDK 不可覆盖范围 | [应用范围](console-scope.md) |
| 独立监听的 `GET /metrics` | 已实现基础运行指标,默认关闭 | [运行观测](observability.md) |
| `/v1/tasks` 提交、查询、分页与待执行取消 | 已实现,P3扩展业务键与创建时间筛选 | [任务接口](tasks.md) / [查询契约](task-queries.md) |
| `GET /v1/tasks/{task_id}/source` | 已实现普通、人工重试、计划固定版本及流程历史激活/轮次来源 | [来源查询](task-source.md) |

View File

@@ -50,3 +50,5 @@ X-Orbit-Namespace: 22222222222222222222222222222222
每个业务事务重新核验管理员账号、原会话、绑定凭据有效性以及原/目标应用启用状态,再执行原有的资源归属、版本、幂等、核查证据和审计规则。管理审计操作者为 `console:<user_id>`,操作归入目标命名空间。读取其他范围的资源 ID 不会越权返回数据。不存在的范围、未授权范围不自动回退。
缺少 Cookie 或会话失效返回 401;不允许的范围切换、混用 SDK Bearer、跨站写操作返回 403;目标应用停用或资源不属于当前范围返回 404。非法范围 ID 返回 400。页面切换采用完整导航,旧详情和请求不会在新应用页面呈现;URL 只保留非敏感 ID。
任务列表沿用原有 SQL 筛选语义:请求其他应用的队列会得到空列表;该队列或任务的单条详情返回 404。页面会先核对 URL 中的队列归属,错误队列不会被当成正常空列表。

View File

@@ -4,6 +4,21 @@
任务、计划、流程、订阅和归档列表显示应用名称与环境;保留策略及详情显示归属。管理员可选择应用环境和队列,范围保存在 URL,页面切换隔离旧数据。后台按 Cookie 会话授权,普通 SDK 不接受范围覆盖。无表结构变更,无新增外键。
## 验证记录
## 验证记录(2026-09-27)
验证进行中,最终结果在本次交付前更新。
| 检查 | 结果 |
| --- | --- |
| `make test vet build` | 通过,服务端、SDK 和各独立示例/适配模块;最后的 HTTP 范围边界测试及编译检查也通过 |
| `make integration` | 通过:真实 MySQL 全量存储回归、HTTP/WS、SDK、Inbox/Outbox/HTTP 宿主示例、进程中断及多节点流程恢复;存储回归约 307 秒,最终 SDK 进程故障组约 131 秒 |
| `TestIntegrationConsoleScope` | 原始/目标元数据、普通账号队列白名单、二进制 ID 游标、SDK/非指定管理员拒绝、目标队列隔离、仅管理不受理新工作、目标保留策略原键重放、原应用不变、人类审计、停用目标/退出后旧范围拒绝、零外键断言 |
| `make console-e2e` | 通过:隔离 MySQL 和实际 Orbit 容器;新建应用、任务/计划/流程/订阅/保留/归档范围读取,订阅管理及重放保留原 SDK 归属,跨应用单条 404、列表无泄漏,Bearer 混用/跨站写入拒绝,退出后 401;原任务执行、计划和归档端到端也通过 |
| HTTP 范围边界 | 通过 11 个 Cookie/Bearer/CSRF/读写入口场景;Worker、结果消费和资源创建不能借管理范围获得权限 |
| 静态与配置检查 | JS 语法、Python 编译、Markdown 链接、`git diff --check` 通过;单节点 + HA Compose 配置解析通过,双节点使用同一管理员环境配置 |
| 浏览器实际页面 | 任务列和订阅列表/详情显示应用标签;应用及队列切换、无数据范围、保留策略和归档范围提示、非法范围不降级、重置范围恢复均已检查 |
| 本地部署 | 本地 Compose 已部署本批代码;现有两个应用分别读取自己的范围,原 SDK Token 仍有效,无业务接入参数变更 |
应用、队列名称来自授权后的数据库元数据;队列分页首屏不使用空 ID,白名单使用 BINARY ID 参数。URL 中显式指定其他应用的队列会校验失败,不静默切换队列。打开详情、策略未保存或操作结果未确认时,页面阻止切换范围。
## 边界
当前按所选应用环境分别查看,不提供跨应用混合分页或全库统计。任务、计划、订阅按队列筛选;流程、保留与归档按命名空间展示。应用目录 50 项一页、队列 100 项一页。普通账号保留原绑定权限;只有部署指定的单管理员具有显式管理范围。此回归不替代生产部署的容量和跨故障域验收。

View File

@@ -2,6 +2,7 @@ package consoleauth
import (
"context"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
)

View File

@@ -23,10 +23,15 @@
}
function setView(view, updateHash = true) {
const next = pageNames[view] ? view : 'overview';
const leavingApplications = state.scopeReady && $('application-scope').hidden && next !== 'applications';
document.querySelectorAll('.page-view').forEach(page => page.classList.toggle('active', page.dataset.page === next));
document.querySelectorAll('.nav-item').forEach(item => item.classList.toggle('active', item.dataset.view === next));
text('page-title', pageNames[next]);
$('application-scope').hidden = next === 'applications';
if (leavingApplications && state.scope?.can_switch) {
scopeUI.nextApplication = '';
loadScopeApplications().catch(error => showToast(`应用目录读取失败:${error.message}`));
}
text('scope-hint', ['workflows', 'retention', 'archives'].includes(next) ? '当前应用环境 · 不受队列筛选影响' : '任务、计划和订阅按所选队列显示');
if (updateHash && location.hash !== `#/${next}`) location.hash = `/${next}`;
window.scrollTo({ top: 0, behavior: 'smooth' });
@@ -131,6 +136,11 @@
state.scopeReady = true; renderConnectionSummary();
} catch (error) {
state.queue = ''; state.scopeReady = false;
text('scope-summary', '范围不可用');
$('scope-application').replaceChildren(new Option('范围不可用', ''));
$('scope-queue').replaceChildren(new Option('未加载', ''));
$('scope-application').disabled = true; $('scope-queue').disabled = true;
$('task-table').innerHTML = '<tr><td colspan="6" class="empty-row"><span class="empty-icon">◇</span><b>当前范围未加载</b><small>请检查应用范围,或点击“重置范围”</small></td></tr>';
text('scope-error', `当前应用范围不可用:${error.message}。可点击“重置范围”返回默认应用。`);
}
}
@@ -783,9 +793,9 @@
renderSchedules(scheduleItems || []);
if (!scheduleItems) $('schedule-list').textContent = '定时计划读取失败,请刷新重试或检查权限';
renderWorkflows(workflowItems || []);
if (!workflowItems) $('workflow-list').textContent = '流程实例读取失败,请刷新重试或检查权限';
if (!workflowItems) { $('workflow-list').textContent = '流程实例读取失败,请刷新重试或检查权限'; text('metric-workflows', '—'); text('metric-workflows-sub', '读取失败'); }
renderSubscriptions(subscriptionItems || []);
if (!subscriptionItems) $('subscription-list').textContent = '结果订阅读取失败,请刷新重试或检查权限';
if (!subscriptionItems) { $('subscription-list').textContent = '结果订阅读取失败,请刷新重试或检查权限'; text('metric-subscriptions', '—'); text('metric-subscriptions-sub', '读取失败'); }
setConnection(true); renderRuntimeStats(taskItems, scheduleItems, workflowItems, subscriptionItems); text('last-updated', `刚刚更新 · ${formatTime(new Date())}`); text('metric-health-sub', '数据库与 Schema 正常');
} catch (error) { setConnection(false); showToast(`连接失败:${error.message}`); text('last-updated', '刷新失败'); }
finally { state.loading = false; $('refresh-button').classList.remove('spinning'); }

View File

@@ -1,12 +1,13 @@
package httpserver
import (
"net/http"
"strings"
"git.yuntuo.hk/k2safe/orbit/internal/consoleauth"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
"net/http"
"strings"
)
type consoleScopeKey struct{}
@@ -40,6 +41,9 @@ func consoleScopePath(r *http.Request) bool {
}
func RegisterConsoleScopes(server *http.Server, store consoleauth.Scopes) {
if server == nil {
return
}
mux, ok := server.Handler.(*http.ServeMux)
if !ok || store == nil {
return

View File

@@ -39,7 +39,8 @@ func TestConsoleScopeOnlyBridgesCookieManagementRequests(t *testing.T) {
{"anonymous", "GET", "/v1/tasks", "", "", false, true, 401},
{"missing-marker", "GET", "/v1/tasks", "", "", true, false, 403},
{"cross-origin", "POST", "/v1/tasks/task:cancel", "https://other.invalid", "", true, true, 403},
{"worker", "GET", "/v1/workers/connect", "", "", true, true, 403},
{"worker", "GET", "/v1/worker/connect", "", "", true, true, 403},
{"consumer", "GET", "/v1/events/connect", "", "", true, true, 403},
{"create", "POST", "/v1/tasks", "", "", true, true, 403},
{"onboarding", "POST", "/console/applications/prepare", "", "", true, true, 403},
} {

View File

@@ -13,7 +13,7 @@ import (
// Token 是已解析但尚未认证的身份,服务端仍须核对数据库摘要与有效期。
type Token struct {
// NamespaceID 凭据所属命名空间,不能由请求体覆盖。
// NamespaceID 凭据所属命名空间;仅服务端已核验的 Console 身份可指定管理范围。
NamespaceID core.ID
// CredentialID 凭据记录标识。
CredentialID core.ID