chore: 服务骨架(Hono+TS+vitest+eslint,health 路由与探活注册点)
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
PORT=<缺省 8800>
|
||||||
|
NATS_URL=<nats://主机:4222>
|
||||||
|
KEYCLOAK_URL=<Keycloak 站点地址>
|
||||||
|
KEYCLOAK_REALM=<realm 名,如 corp>
|
||||||
|
KEYCLOAK_CLIENT_ID=<本仓 client,如 api-gateway>
|
||||||
|
KEYCLOAK_CLIENT_SECRET=<从 Keycloak Credentials 标签抄>
|
||||||
|
AGENT_SERVICE_URL=<agent 服务地址,缺省 http://127.0.0.1:8901>
|
||||||
|
TASK_SERVICE_URL=<task 服务地址,缺省 http://127.0.0.1:8801>
|
||||||
|
DATA_SERVICE_URL=<data 服务地址,缺省 http://127.0.0.1:8802>
|
||||||
|
OBJECT_STORE_MODE=<fs | s3>
|
||||||
|
OBJECT_STORE_DIR=<本地对象目录,如 ./.objectstore>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# bpos-service-gateway
|
||||||
|
|
||||||
|
前端唯一入口:BFF 聚合、鉴权与到 task / data / agent 的路由(:8800,无自有库)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm i && pnpm dev # 本地起服务
|
||||||
|
pnpm gates # typecheck + lint + test
|
||||||
|
```
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(...tseslint.configs.recommended, {
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "bpos-service-gateway",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": { "node": ">=22" },
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch --env-file=.env src/app/server.ts",
|
||||||
|
"start": "tsx --env-file=.env src/app/server.ts",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"test": "vitest run",
|
||||||
|
"gates": "pnpm -s typecheck && pnpm -s lint && pnpm -s test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^2.0.12",
|
||||||
|
"hono": "^4.12.0",
|
||||||
|
"jose": "^6.0.0",
|
||||||
|
"nats": "^2.29.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.0",
|
||||||
|
"eslint": "^9.20.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.9.0",
|
||||||
|
"typescript-eslint": "^8.20.0",
|
||||||
|
"vitest": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1971
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
import { healthRoute } from './health';
|
||||||
|
|
||||||
|
export function buildApp(): Hono {
|
||||||
|
const app = new Hono();
|
||||||
|
app.route('/health', healthRoute());
|
||||||
|
return app;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
import { config } from '../shared/config';
|
||||||
|
|
||||||
|
export type HealthCheck = () => Promise<'ok' | 'fail'>;
|
||||||
|
const checks = new Map<string, HealthCheck>();
|
||||||
|
|
||||||
|
/** 后续任务注册 db / nats 探活 */
|
||||||
|
export function registerHealthCheck(name: string, fn: HealthCheck): void {
|
||||||
|
checks.set(name, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function healthRoute(): Hono {
|
||||||
|
const r = new Hono();
|
||||||
|
r.get('/', async (c) => {
|
||||||
|
const entries = await Promise.all(
|
||||||
|
[...checks.entries()].map(async ([name, fn]) => [name, await fn()] as const),
|
||||||
|
);
|
||||||
|
const result = Object.fromEntries(entries);
|
||||||
|
const ok = entries.every(([, v]) => v === 'ok');
|
||||||
|
return c.json({ ok, service: config.serviceName, checks: result }, ok ? 200 : 503);
|
||||||
|
});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { serve } from '@hono/node-server';
|
||||||
|
import { buildApp } from './app';
|
||||||
|
import { config } from '../shared/config';
|
||||||
|
|
||||||
|
serve({ fetch: buildApp().fetch, port: config.port }, (info) => {
|
||||||
|
console.log(`${config.serviceName} listening on :${info.port}`);
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const config = {
|
||||||
|
port: Number(process.env.PORT ?? 8800),
|
||||||
|
natsUrl: process.env.NATS_URL ?? 'nats://172.16.86.151:4222',
|
||||||
|
keycloak: {
|
||||||
|
url: process.env.KEYCLOAK_URL ?? 'https://keycloak.akhr-ai.com',
|
||||||
|
realm: process.env.KEYCLOAK_REALM ?? 'corp',
|
||||||
|
clientId: process.env.KEYCLOAK_CLIENT_ID ?? 'api-gateway',
|
||||||
|
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, // 只从环境读,无缺省;data-service 恒无
|
||||||
|
},
|
||||||
|
agentServiceUrl: process.env.AGENT_SERVICE_URL ?? 'http://127.0.0.1:8901',
|
||||||
|
taskServiceUrl: process.env.TASK_SERVICE_URL ?? 'http://127.0.0.1:8801',
|
||||||
|
dataServiceUrl: process.env.DATA_SERVICE_URL ?? 'http://127.0.0.1:8802',
|
||||||
|
serviceName: 'bpos-service-gateway',
|
||||||
|
} as const;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// 测试期环境缺省:先于任何被测模块的 import 生效
|
||||||
|
process.env.KEYCLOAK_URL ??= 'https://keycloak.akhr-ai.com';
|
||||||
|
process.env.KEYCLOAK_REALM ??= 'corp';
|
||||||
|
process.env.KEYCLOAK_CLIENT_ID ??= 'api-gateway';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { buildApp } from '../../src/app/app';
|
||||||
|
|
||||||
|
describe('health', () => {
|
||||||
|
it('无探活项时返回 200 ok', async () => {
|
||||||
|
const res = await buildApp().request('/health');
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = (await res.json()) as { ok: boolean; service: string };
|
||||||
|
expect(body.ok).toBe(true);
|
||||||
|
expect(body.service).toMatch(/^bpos-service-/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2023",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src", "test", "*.config.ts", "drizzle.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['test/**/*.test.ts'],
|
||||||
|
setupFiles: ['test/support/setup.ts'],
|
||||||
|
testTimeout: 30_000,
|
||||||
|
hookTimeout: 30_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user