feat: add HLI CI/CD pipeline, contract scripts, Copilot instructions and project structure
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
7bcc2d6e0a
commit
818e7c5d89
|
|
@ -0,0 +1,40 @@
|
||||||
|
# HoloLake · Copilot Custom Instructions
|
||||||
|
|
||||||
|
## 项目背景
|
||||||
|
这是 HoloLake (光湖) MVP 后端项目,运行在 guanghulab.com。
|
||||||
|
技术栈:Node.js 20 + Express + PM2 + Nginx。
|
||||||
|
核心架构:人格语言操作系统 (AGE OS),壳-核分离设计。
|
||||||
|
|
||||||
|
## HLI 接口协议
|
||||||
|
- 所有 API 路由必须以 `/hli/` 为前缀
|
||||||
|
- 每个路由文件必须在 `src/routes/hli/{domain}/` 目录下
|
||||||
|
- 每个路由必须有对应的 `src/schemas/hli/{domain}/{name}.schema.json`
|
||||||
|
- Schema 文件必须包含 `hli_id`, `input`, `output` 三个顶层字段
|
||||||
|
- 接口编号格式: `HLI-{DOMAIN}-{NNN}`
|
||||||
|
|
||||||
|
## 代码风格
|
||||||
|
- 所有接口入口必须先经过 `middleware/hli-auth.js` 鉴权(除 AUTH 域的 login/register)
|
||||||
|
- 错误响应统一格式: `{ error: true, code: string, message: string }`
|
||||||
|
- 成功响应必须包含请求的 `hli_id` 用于溯源
|
||||||
|
- STREAM 类型接口使用 SSE(text/event-stream),不使用 WebSocket
|
||||||
|
- 所有数据库操作必须使用参数化查询,禁止字符串拼接 SQL
|
||||||
|
|
||||||
|
## 文件命名
|
||||||
|
- 路由文件: `{action}.js` (如 login.js, upload.js)
|
||||||
|
- Schema 文件: `{action}.schema.json`
|
||||||
|
- 测试文件: `{action}.test.js`
|
||||||
|
- 中间件: `{name}.middleware.js`
|
||||||
|
|
||||||
|
## 新建接口的标准流程
|
||||||
|
1. 在 `src/schemas/hli/{domain}/` 下创建 schema JSON
|
||||||
|
2. 在 `src/routes/hli/{domain}/` 下创建路由文件
|
||||||
|
3. 在 `src/routes/hli/index.js` 中注册路由
|
||||||
|
4. 在 `tests/contract/` 下创建契约测试
|
||||||
|
5. 在 `tests/smoke/` 下创建冒烟测试
|
||||||
|
6. 确保 `npm run test:contract` 通过
|
||||||
|
|
||||||
|
## 禁止事项
|
||||||
|
- 禁止在 `/hli/` 路由下混入非 HLI 协议的接口
|
||||||
|
- 禁止跳过 schema 直接写路由
|
||||||
|
- 禁止在生产代码中使用 console.log(使用项目 logger)
|
||||||
|
- 禁止硬编码 persona_id 或 user_id
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
name: HLI Contract Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, dev]
|
||||||
|
paths:
|
||||||
|
- 'src/routes/hli/**'
|
||||||
|
- 'src/schemas/**'
|
||||||
|
- 'tests/contract/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'src/routes/hli/**'
|
||||||
|
- 'src/schemas/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
contract-lint:
|
||||||
|
name: 🔍 接口契约校验
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run HLI schema validation
|
||||||
|
run: npm run test:contract
|
||||||
|
env:
|
||||||
|
HLI_REGISTRY_MODE: strict
|
||||||
|
|
||||||
|
- name: Run route-schema alignment check
|
||||||
|
run: npm run test:route-align
|
||||||
|
|
||||||
|
api-smoke:
|
||||||
|
name: 🚀 接口冒烟测试
|
||||||
|
needs: contract-lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Start test server
|
||||||
|
run: npm run start:test &
|
||||||
|
env:
|
||||||
|
PORT: 3001
|
||||||
|
NODE_ENV: test
|
||||||
|
|
||||||
|
- name: Wait for server
|
||||||
|
run: npx wait-on http://localhost:3001/health -t 30000
|
||||||
|
|
||||||
|
- name: Run smoke tests
|
||||||
|
run: npm run test:smoke
|
||||||
|
|
||||||
|
- name: Upload test report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: hli-test-report
|
||||||
|
path: reports/
|
||||||
|
|
@ -39,3 +39,8 @@ yarn-error.log*
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# HLI 运行时产物
|
||||||
|
/logs
|
||||||
|
/reports/*
|
||||||
|
!/reports/.gitkeep
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// ecosystem.config.js
|
||||||
|
// PM2 进程管理配置
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'guanghulab',
|
||||||
|
script: 'src/index.js',
|
||||||
|
instances: 'max',
|
||||||
|
exec_mode: 'cluster',
|
||||||
|
watch: false,
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3000,
|
||||||
|
},
|
||||||
|
env_development: {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
PORT: 3000,
|
||||||
|
},
|
||||||
|
env_test: {
|
||||||
|
NODE_ENV: 'test',
|
||||||
|
PORT: 3001,
|
||||||
|
},
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
error_file: 'logs/error.log',
|
||||||
|
out_file: 'logs/out.log',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// jest.smoke.config.js
|
||||||
|
// 冒烟测试 Jest 配置
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/tests/smoke/**/*.test.js'],
|
||||||
|
testTimeout: 30000,
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
|
|
@ -6,19 +6,25 @@
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"test:contract": "node scripts/contract-check.js",
|
||||||
|
"test:route-align": "node scripts/route-align-check.js",
|
||||||
|
"test:smoke": "jest --config jest.smoke.config.js --forceExit",
|
||||||
|
"start:test": "NODE_ENV=test node src/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"next": "15.3.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0"
|
||||||
"next": "15.3.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@tailwindcss/postcss": "^4",
|
"glob": "^10.5.0",
|
||||||
"tailwindcss": "^4"
|
"jest": "^29.7.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# 测试报告目录(CI 产物,不纳入版本控制)
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// scripts/contract-check.js
|
||||||
|
// 用途:确保每个 HLI 路由文件都有对应的 JSON Schema 且路径一致
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
const ROUTE_DIR = 'src/routes/hli';
|
||||||
|
const SCHEMA_DIR = 'src/schemas/hli';
|
||||||
|
|
||||||
|
// 扫描所有 HLI 路由文件
|
||||||
|
const routeFiles = glob.sync(`${ROUTE_DIR}/**/*.js`);
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
routeFiles.forEach(routeFile => {
|
||||||
|
const domain = path.basename(path.dirname(routeFile));
|
||||||
|
const name = path.basename(routeFile, '.js');
|
||||||
|
|
||||||
|
// 跳过 index.js(路由注册中心,无需 schema)
|
||||||
|
if (name === 'index') return;
|
||||||
|
|
||||||
|
const schemaPath = path.join(SCHEMA_DIR, domain, `${name}.schema.json`);
|
||||||
|
|
||||||
|
// 检查1: 对应 schema 文件是否存在
|
||||||
|
if (!fs.existsSync(schemaPath)) {
|
||||||
|
errors.push(`❌ [MISSING SCHEMA] ${routeFile} → 缺少 ${schemaPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查2: schema 文件格式是否合法
|
||||||
|
try {
|
||||||
|
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||||
|
if (!schema.input || !schema.output) {
|
||||||
|
errors.push(`❌ [INVALID SCHEMA] ${schemaPath} → 缺少 input/output 定义`);
|
||||||
|
}
|
||||||
|
if (!schema.hli_id) {
|
||||||
|
errors.push(`❌ [MISSING HLI_ID] ${schemaPath} → 缺少 hli_id 字段`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`❌ [PARSE ERROR] ${schemaPath} → ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error('\n🚫 HLI Contract Check FAILED:\n');
|
||||||
|
errors.forEach(e => console.error(e));
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('✅ HLI Contract Check PASSED — 所有路由均有合法 schema');
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// scripts/route-align-check.js
|
||||||
|
// 用途:检查路由路径与 HLI 注册表编号的一致性
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// HLI 路由路径映射(从 Notion 注册表同步)
|
||||||
|
const HLI_ROUTES = {
|
||||||
|
'HLI-AUTH-001': '/hli/auth/login',
|
||||||
|
'HLI-AUTH-002': '/hli/auth/register',
|
||||||
|
'HLI-AUTH-003': '/hli/auth/verify',
|
||||||
|
'HLI-PERSONA-001': '/hli/persona/load',
|
||||||
|
'HLI-PERSONA-002': '/hli/persona/switch',
|
||||||
|
'HLI-USER-001': '/hli/user/profile',
|
||||||
|
'HLI-USER-002': '/hli/user/profile/update',
|
||||||
|
'HLI-TICKET-001': '/hli/ticket/create',
|
||||||
|
'HLI-TICKET-002': '/hli/ticket/query',
|
||||||
|
'HLI-TICKET-003': '/hli/ticket/status',
|
||||||
|
'HLI-DIALOGUE-001': '/hli/dialogue/send',
|
||||||
|
'HLI-DIALOGUE-002': '/hli/dialogue/stream',
|
||||||
|
'HLI-DIALOGUE-003': '/hli/dialogue/history',
|
||||||
|
'HLI-STORAGE-001': '/hli/storage/upload',
|
||||||
|
'HLI-STORAGE-002': '/hli/storage/download',
|
||||||
|
'HLI-DASHBOARD-001': '/hli/dashboard/status',
|
||||||
|
'HLI-DASHBOARD-002': '/hli/dashboard/realtime',
|
||||||
|
};
|
||||||
|
|
||||||
|
const schemaDir = 'src/schemas/hli';
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
Object.entries(HLI_ROUTES).forEach(([hliId, expectedPath]) => {
|
||||||
|
const domain = hliId.split('-')[1].toLowerCase();
|
||||||
|
const domainDir = path.join(schemaDir, domain);
|
||||||
|
|
||||||
|
// 若 domain 目录尚不存在,视为未实现
|
||||||
|
if (!fs.existsSync(domainDir)) {
|
||||||
|
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaFiles = fs.readdirSync(domainDir).filter(f => f.endsWith('.schema.json'));
|
||||||
|
|
||||||
|
const matched = schemaFiles.find(f => {
|
||||||
|
try {
|
||||||
|
const schema = JSON.parse(fs.readFileSync(path.join(domainDir, f), 'utf8'));
|
||||||
|
return schema.hli_id === hliId;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!matched) {
|
||||||
|
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.warn('\n⚠️ Route Alignment Report:\n');
|
||||||
|
errors.forEach(e => console.warn(e));
|
||||||
|
// 非阻断,仅报告
|
||||||
|
console.warn(`\n📊 覆盖率: ${Object.keys(HLI_ROUTES).length - errors.length}/${Object.keys(HLI_ROUTES).length}`);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Route Alignment PASSED — 所有 HLI 接口均已实现');
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// src/index.js
|
||||||
|
// HoloLake (光湖) 后端服务入口
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// 健康检查(CI smoke test 依赖此端点)
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok', env: NODE_ENV, timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
// HLI 路由挂载
|
||||||
|
const hliRouter = require('./routes/hli');
|
||||||
|
app.use('/hli', hliRouter);
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`[HoloLake] Server running on port ${PORT} (${NODE_ENV})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// src/middleware/hli-auth.middleware.js
|
||||||
|
// HLI 鉴权中间件:验证请求携带有效 token
|
||||||
|
|
||||||
|
module.exports = function hliAuth(req, res, next) {
|
||||||
|
const authHeader = req.headers['authorization'];
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: true,
|
||||||
|
code: 'AUTH_TOKEN_MISSING',
|
||||||
|
message: '请求缺少 Authorization Bearer token',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.slice(7);
|
||||||
|
|
||||||
|
// TODO: 实现真实的 token 校验逻辑(JWT 验证或数据库查询)
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: true,
|
||||||
|
code: 'AUTH_TOKEN_INVALID',
|
||||||
|
message: 'token 无效',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将用户信息挂载到 req 供后续中间件使用
|
||||||
|
req.hliUser = { token };
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// src/middleware/hli-validator.middleware.js
|
||||||
|
// HLI Schema 自动校验中间件:根据路由自动加载对应 schema 校验请求体
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = function hliValidator(req, res, next) {
|
||||||
|
// 从路径中解析 domain 和 action,例如 /hli/auth/login → auth/login
|
||||||
|
const match = req.path.match(/^\/hli\/([^/]+)\/([^/]+)/);
|
||||||
|
if (!match) return next();
|
||||||
|
|
||||||
|
const [, domain, action] = match;
|
||||||
|
const schemaPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'../schemas/hli',
|
||||||
|
domain,
|
||||||
|
`${action}.schema.json`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(schemaPath)) return next();
|
||||||
|
|
||||||
|
let schema;
|
||||||
|
try {
|
||||||
|
schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||||
|
} catch (_) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单必填字段校验(生产环境可替换为 ajv 等标准校验库)
|
||||||
|
const required = schema.input && schema.input.required;
|
||||||
|
if (required && req.method !== 'GET') {
|
||||||
|
for (const field of required) {
|
||||||
|
if (req.body[field] === undefined || req.body[field] === '') {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: true,
|
||||||
|
code: 'VALIDATION_ERROR',
|
||||||
|
message: `缺少必填字段: ${field}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// src/routes/hli/auth/index.js
|
||||||
|
// AUTH 域路由
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const login = require('./login');
|
||||||
|
const register = require('./register');
|
||||||
|
const verify = require('./verify');
|
||||||
|
|
||||||
|
router.use('/login', login);
|
||||||
|
router.use('/register', register);
|
||||||
|
router.use('/verify', verify);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// src/routes/hli/auth/login.js
|
||||||
|
// HLI-AUTH-001: 用户登录
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
// TODO: 实现登录逻辑(查询数据库,验证密码,生成 token)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
hli_id: 'HLI-AUTH-001',
|
||||||
|
token: '',
|
||||||
|
user_id: '',
|
||||||
|
persona_id: '',
|
||||||
|
expires_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: true, code: 'AUTH_LOGIN_ERROR', message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// src/routes/hli/auth/register.js
|
||||||
|
// HLI-AUTH-002: 用户注册
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username, password, email } = req.body;
|
||||||
|
|
||||||
|
// TODO: 实现注册逻辑(检查重复,写入数据库)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
hli_id: 'HLI-AUTH-002',
|
||||||
|
user_id: '',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: true, code: 'AUTH_REGISTER_ERROR', message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// src/routes/hli/auth/verify.js
|
||||||
|
// HLI-AUTH-003: Token 验证
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { token } = req.body;
|
||||||
|
|
||||||
|
// TODO: 实现 token 验证逻辑
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
hli_id: 'HLI-AUTH-003',
|
||||||
|
valid: false,
|
||||||
|
user_id: '',
|
||||||
|
persona_id: '',
|
||||||
|
expires_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: true, code: 'AUTH_VERIFY_ERROR', message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// src/routes/hli/index.js
|
||||||
|
// HLI 路由注册中心
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// AUTH 域(不需要鉴权)
|
||||||
|
const authRouter = require('./auth');
|
||||||
|
router.use('/auth', authRouter);
|
||||||
|
|
||||||
|
// 以下域需要 HLI 鉴权中间件
|
||||||
|
const hliAuth = require('../../middleware/hli-auth.middleware');
|
||||||
|
|
||||||
|
router.use(hliAuth);
|
||||||
|
|
||||||
|
// PERSONA 域
|
||||||
|
// const personaRouter = require('./persona');
|
||||||
|
// router.use('/persona', personaRouter);
|
||||||
|
|
||||||
|
// USER 域
|
||||||
|
// const userRouter = require('./user');
|
||||||
|
// router.use('/user', userRouter);
|
||||||
|
|
||||||
|
// TICKET 域
|
||||||
|
// const ticketRouter = require('./ticket');
|
||||||
|
// router.use('/ticket', ticketRouter);
|
||||||
|
|
||||||
|
// DIALOGUE 域
|
||||||
|
// const dialogueRouter = require('./dialogue');
|
||||||
|
// router.use('/dialogue', dialogueRouter);
|
||||||
|
|
||||||
|
// STORAGE 域
|
||||||
|
// const storageRouter = require('./storage');
|
||||||
|
// router.use('/storage', storageRouter);
|
||||||
|
|
||||||
|
// DASHBOARD 域
|
||||||
|
// const dashboardRouter = require('./dashboard');
|
||||||
|
// router.use('/dashboard', dashboardRouter);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"hli_id": "HLI-AUTH-001",
|
||||||
|
"version": "v0.1",
|
||||||
|
"route": "/hli/auth/login",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "REQUEST",
|
||||||
|
"input": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["username", "password"],
|
||||||
|
"properties": {
|
||||||
|
"username": { "type": "string", "minLength": 1 },
|
||||||
|
"password": { "type": "string", "minLength": 6 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["token", "user_id", "persona_id"],
|
||||||
|
"properties": {
|
||||||
|
"token": { "type": "string" },
|
||||||
|
"user_id": { "type": "string" },
|
||||||
|
"persona_id": { "type": "string" },
|
||||||
|
"expires_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": [],
|
||||||
|
"changelog": [
|
||||||
|
{ "date": "2026-03-05", "note": "初始创建" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"hli_id": "HLI-AUTH-002",
|
||||||
|
"version": "v0.1",
|
||||||
|
"route": "/hli/auth/register",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "REQUEST",
|
||||||
|
"input": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["username", "password", "email"],
|
||||||
|
"properties": {
|
||||||
|
"username": { "type": "string", "minLength": 1 },
|
||||||
|
"password": { "type": "string", "minLength": 6 },
|
||||||
|
"email": { "type": "string", "format": "email" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["user_id"],
|
||||||
|
"properties": {
|
||||||
|
"user_id": { "type": "string" },
|
||||||
|
"created_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": [],
|
||||||
|
"changelog": [
|
||||||
|
{ "date": "2026-03-05", "note": "初始创建" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"hli_id": "HLI-AUTH-003",
|
||||||
|
"version": "v0.1",
|
||||||
|
"route": "/hli/auth/verify",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "REQUEST",
|
||||||
|
"input": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["token"],
|
||||||
|
"properties": {
|
||||||
|
"token": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["valid", "user_id"],
|
||||||
|
"properties": {
|
||||||
|
"valid": { "type": "boolean" },
|
||||||
|
"user_id": { "type": "string" },
|
||||||
|
"persona_id": { "type": "string" },
|
||||||
|
"expires_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": ["HLI-AUTH-001"],
|
||||||
|
"changelog": [
|
||||||
|
{ "date": "2026-03-05", "note": "初始创建" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# 契约测试目录
|
||||||
|
# 每新增 HLI 接口时,在此创建对应的 {action}.test.js
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# 冒烟测试目录
|
||||||
|
# 每新增 HLI 接口时,在此创建对应的冒烟测试
|
||||||
Loading…
Reference in New Issue