I6 把 touch.workflow. 从可订阅集摘出去,理由是**当时没有规则**:形状一过就直接进订阅集, 而鉴权分支只覆盖 touch.task./stream.task.,任何登录用户订 touch.workflow.<任意 id> 都能收 别人流程的推送。N3 的引擎每次推进都发 touch.workflow.<instanceId>,规则也定了:参与人 (节点 ownerSub)/审批人(approverSub)/admin。规则到位,故放回来。 - workflow-access.ts:与 task-access 同形的 HTTP 口——服务账号 token、3s 超时、非 200 一律 false 并告警(失败关闭)。打的是任务侧新加的 /workflow/:id/access。 - hub:SUBSCRIBABLE 收回 touch.workflow.,subscribe 补上对应鉴权分支——放形状与补鉴权 必须同一次改动,只放形状就是把 I6 原样退回去。admin 旁路(全可见,也免掉白跑的往返)。 - 两口分开而不是复用一口:任务问的是单张单的归属,工作流问的是实例**定义里**的节点归属。 - app:两口共用同一个 token provider,缓存只此一份。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
37 lines
2.0 KiB
TypeScript
37 lines
2.0 KiB
TypeScript
import { Hono } from 'hono';
|
||
import { healthRoute, registerHealthCheck } from './health';
|
||
import { checkNats } from '../shared/bus/nats';
|
||
import { createKeycloakTokenProvider } from '../shared/service-token';
|
||
import { proxyRoutes } from '../modules/proxy/routes';
|
||
import { uploadRoutes } from '../modules/upload/routes';
|
||
import { SseHub } from '../modules/sse/hub';
|
||
import { sseRoutes } from '../modules/sse/routes';
|
||
import { createHttpTaskAccess } from '../modules/sse/task-access';
|
||
import { createHttpWorkflowAccess } from '../modules/sse/workflow-access';
|
||
|
||
/**
|
||
* hub 由外面传:server.ts 要拿同一个实例去 attachNats/shutdown。缺省自建一个,
|
||
* 让只关心 HTTP 面的测试照旧 buildApp() 就能用。
|
||
*/
|
||
export function buildApp(hub: SseHub = new SseHub()): Hono {
|
||
registerHealthCheck('nats', checkNats);
|
||
const app = new Hono();
|
||
// 统一错误形状:任何未捕获异常都收敛成 {error,message} 的 500,不漏 Hono 默认裸文本/栈
|
||
app.onError((err, c) => {
|
||
console.error(err);
|
||
return c.json({ error: 'internal_error', message: '服务内部错误——已记录,稍后重试或联系维护' }, 500);
|
||
});
|
||
app.route('/health', healthRoute()); // 公开:不过 requireAuth
|
||
// 惰性构造:createKeycloakTokenProvider 只在闭包被调用时读 KEYCLOAK_CLIENT_SECRET,
|
||
// 建 app 本身不要求环境齐备,否则任何 buildApp() 的测试都会当场炸。
|
||
// 两口共用同一个 token provider:换来的是同一个服务账号 token,缓存也只此一份
|
||
const serviceToken = createKeycloakTokenProvider();
|
||
const access = createHttpTaskAccess(serviceToken);
|
||
const workflowAccess = createHttpWorkflowAccess(serviceToken);
|
||
// 顺序要紧:/api/stream 与 /api/upload 必须先于 /api 代理挂载,否则被代理的 catch-all 吞掉
|
||
app.route('/api/stream', sseRoutes(hub, access, workflowAccess));
|
||
app.route('/api/upload', uploadRoutes());
|
||
app.route('/api', proxyRoutes());
|
||
return app;
|
||
}
|