Skip to main content

项目自定义路由开发指南

概述

项目路由(Project Routes)是一个灵活的插件机制,允许您在容器外为特定项目编写自定义 API 接口,无需修改系统核心代码。每个项目可以根据业务需求开发独立的 API 路由。

快速开始

1. 创建路由配置文件

在您的项目目录下创建 api/routes.js 文件:

projects/
└── your-project/
└── api/
├── routes.js # 路由配置(必需)
├── handlers.js # 业务处理函数
└── util.js # 工具函数

2. 定义路由

routes.js 中导出路由配置数组:

import handlers from './handlers.js';

export default [
{
method: 'POST', // HTTP 方法:GET、POST、PUT、DELETE
path: '/custom-api', // 路由路径
skipAuth: false, // 是否跳过系统认证(默认 false)
function: handlers.myHandler, // 处理函数
},
{
method: 'GET',
path: '/public-api',
skipAuth: true, // 跳过认证,公开访问
function: (req, res) => {
return res.json({ message: 'Hello World' });
},
},
];

3. 实现处理函数

handlers.js 中实现业务逻辑:

import DBConnector from '../../../src/db-connector.js';

const myHandler = async (req, res, next) => {
try {
// 获取请求参数
const { id } = req.body;

// 访问数据库
const result = await DBConnector.collection('myCollection')
.findOne({ _id: id });

// 返回响应
return res.json({
success: true,
data: result
});
} catch (error) {
// 错误处理
return res.status(500).json({
success: false,
message: error.message
});
}
};

export default {
myHandler,
};

路由配置详解

路由对象属性

属性类型必需说明
methodStringHTTP 方法:GETPOSTPUTDELETE
pathString路由路径,如 /custom-api
functionFunctionExpress 风格的处理函数 (req, res, next) => {}
skipAuthBoolean是否跳过系统 JWT 认证,默认 false
authFunction自定义认证中间件(会自动跳过系统认证)

访问路径

您定义的路由会自动挂载到两个 URL 路径:

  1. 项目特定路径/api/{项目名称}{path}

    • 示例:/api/my-project/custom-api
  2. 通用项目路径/api/project{path}

    • 示例:/api/project/custom-api

认证选项

使用系统认证(默认)

{
method: 'POST',
path: '/protected-api',
function: handler, // 用户必须登录并携带有效 JWT token
}

跳过认证(公开接口)

{
method: 'GET',
path: '/public-api',
skipAuth: true,
function: handler, // 任何人都可以访问
}

自定义认证

import myAuth from './auth.js';

export default [
{
method: 'POST',
path: '/custom-auth-api',
auth: myAuth, // 使用自定义认证逻辑
function: handler,
}
];

自定义认证中间件示例(auth.js):

export default async (req, res, next) => {
const apiKey = req.headers['x-api-key'];

if (apiKey !== 'your-secret-key') {
return res.status(401).json({ error: 'Unauthorized' });
}

next();
};

实战示例

以下是一个完整的实际应用示例,展示了如何实现单点登录(SSO)和数据查询接口。

routes.js

import login from './login.js';

export default [
{
method: 'POST',
path: '/getOneAccount',
skipAuth: true,
function: login.getOneAccount,
},
{
method: 'GET',
path: '/home',
skipAuth: true,
function: login.sso,
},
{
method: 'GET',
path: '/heartbeat',
skipAuth: true,
function: (req, res) => {
return res.send('success');
},
},
];

login.js

import DBConnector from '../../../src/db-connector.js';
import axios from 'axios';
import { signToken } from '../../../src/api/v1/auth.js';

// 查询账户信息
const getOneAccount = async (req, res, next) => {
try {
const EMPLOYEE_ID = req.body.EMPLOYEE_ID;

if (!EMPLOYEE_ID) {
throw new Error('必须提供 EMPLOYEE_ID 参数');
}

// 查询数据库
const account = await DBConnector.collection('accounts')
.find({ username: EMPLOYEE_ID })
.limit(1)
.toArray();

// 移除敏感信息
const result = account.map((a) => ({
...a,
password: undefined
}));

return res.json({
success: true,
data: result,
total: account.length,
});
} catch (error) {
return res.status(500).json({
success: false,
message: error.message
});
}
};

// 单点登录
const sso = async (req, res, next) => {
try {
const { usertoken } = req.query;

if (!usertoken) {
throw new Error('必须提供 usertoken 参数');
}

// 调用第三方认证服务
const response = await axios.post('https://auth.example.com/verify', {
token: usertoken
});

const { employeeId } = response.data;

// 查询本地账户
const account = await DBConnector.collection('accounts')
.findOne({ username: employeeId });

if (!account) {
throw new Error('用户不存在');
}

// 签发系统 JWT token
const token = await signToken(account);

// 重定向到首页
const redirectURI = '/dashboard?headerRender=0';
return res.redirect(`/user/login?token=${token}&redirect=${encodeURIComponent(redirectURI)}`);
} catch (error) {
return res.status(500).json({
success: false,
message: error.message
});
}
};

export default {
getOneAccount,
sso,
};

可用资源

数据库访问

import DBConnector from '../../../src/db-connector.js';

// MongoDB 操作
const data = await DBConnector.collection('accounts').find({}).toArray();

// 更新
await DBConnector.collection('accounts').updateOne(
{ _id: id },
{ $set: { status: 'active' } }
);

JWT Token 操作

import { signToken, verifyToken } from '../../../src/api/v1/auth.js';

// 签发 token
const token = await signToken(userObject);

// 验证 token
const decoded = verifyToken(token);

租户隔离

系统会自动处理多租户隔离,您可以通过以下方式获取当前租户:

const tenant = req.headers.tenant;

请求对象(req)

req.body       // POST/PUT 请求体
req.query // URL 查询参数
req.params // 路由参数
req.headers // HTTP 请求头
req.files // 上传的文件(支持 multipart/form-data)
req.uid // 当前用户 ID(如果已认证)
req.ip // 客户端 IP 地址

响应对象(res)

res.json({ data: 'value' })                 // 返回 JSON
res.send('text') // 返回文本
res.status(404).json({ error: 'Not found' }) // 设置状态码
res.redirect('/path') // 重定向
res.sendFile('/path/to/file') // 发送文件

中间件执行顺序

您的路由会经过以下中间件链:

  1. 租户会话中间件 - 处理多租户隔离
  2. Referer 验证 - 如果启用(可选)
  3. 自定义认证中间件 - 如果提供 auth 属性
  4. 系统认证 - 如果未设置 skipAuth: true
  5. 您的处理函数 - 最终执行业务逻辑

最佳实践

1. 错误处理

始终使用 try-catch 捕获异常:

const handler = async (req, res, next) => {
try {
// 业务逻辑
} catch (error) {
console.error('Error:', error);
return res.status(500).json({
success: false,
message: error.message
});
}
};

2. 参数验证

验证必需参数:

const handler = async (req, res) => {
const { userId, action } = req.body;

if (!userId || !action) {
return res.status(400).json({
error: 'Missing required parameters: userId, action'
});
}

// 继续处理...
};

3. 模块化

将代码拆分为多个模块:

api/
├── routes.js # 路由配置
├── handlers/ # 处理函数目录
│ ├── user.js
│ ├── data.js
│ └── report.js
├── middleware/ # 自定义中间件
│ └── auth.js
└── utils/ # 工具函数
├── validator.js
└── crypto.js

4. 日志记录

记录关键操作:

const handler = async (req, res) => {
console.log(`[${new Date().toISOString()}] API called:`, {
path: req.path,
method: req.method,
user: req.uid,
ip: req.ip
});

// 业务逻辑...
};

5. 安全注意事项

  • 永远不要在响应中返回密码等敏感信息
  • 对用户输入进行验证和清理
  • 使用参数化查询防止注入攻击
  • 公开接口应实施速率限制
// ✅ 安全:移除密码字段
const user = await DBConnector.collection('accounts').findOne({ _id: id });
delete user.password;
return res.json(user);

// ❌ 不安全:直接返回
return res.json(user); // 可能包含 password 字段

调试技巧

查看请求详情

const handler = async (req, res) => {
console.log('Request details:', {
method: req.method,
url: req.originalUrl,
headers: req.headers,
query: req.query,
body: req.body,
});
};

测试接口

使用 curl 测试:

# GET 请求
curl http://localhost:3052/api/project/heartbeat

# POST 请求
curl -X POST http://localhost:3052/api/project/getOneAccount \
-H "Content-Type: application/json" \
-d '{"EMPLOYEE_ID": "123456"}'

# 带认证的请求
curl http://localhost:3052/api/project/protected \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

常见问题

Q: 路由不生效?

检查以下项:

  1. 确保 routes.js 在正确的位置:projects/{project}/api/routes.js
  2. 文件必须导出默认数组:export default [...]
  3. 重启服务器以加载新路由
  4. 检查服务器日志是否有错误

Q: 如何访问系统内置功能?

您可以导入系统模块:

import DBConnector from '../../../src/db-connector.js';
import { signToken } from '../../../src/api/v1/auth.js';
import globalConfig from '../../../src/globalConfig.js';

Q: 能否使用外部 npm 包?

可以,首先安装依赖:

npm install axios

然后在代码中导入:

import axios from 'axios';

Q: 如何处理文件上传?

系统已集成 express-fileupload,直接使用:

const uploadHandler = async (req, res) => {
if (!req.files || !req.files.file) {
return res.status(400).json({ error: 'No file uploaded' });
}

const uploadedFile = req.files.file;
const savePath = `/path/to/save/${uploadedFile.name}`;

await uploadedFile.mv(savePath);

return res.json({
success: true,
filename: uploadedFile.name
});
};

版本兼容性

  • 支持 ES6+ 语法(ESM 模块)
  • Node.js 版本要求:≥ 22.0
  • 必须使用 import/export,不支持 require