ApiProxy 作业需求文档 (Express 分层架构版本)
前端提供用户注册、登陆、大模型聊天功能,后端实现 API 代理,支持流式聊天。大模型选择为 Ollama 本地模型,需要使用对应的 Ollama 库。
- 后端技术栈:Express + JWT + bcrypt.js
- 前端技术栈:不限制
- 数据存储:使用 JSON 文件替代数据库
- 通信协议:HTTP + SSE 流式传输
04-apiproxy/
├── router/ # 路由层
│ ├── auth.router.js
│ └── chat.router.js
├── service/ # 业务逻辑层
│ ├── user.service.js
│ └── chat.service.js
├── stage/ # 数据访问层
│ ├── user.stage.js
│ └── data/ # JSON数据文件
│ ├── users.json
├── middleware/ # 中间件层
│ ├── cors.middleware.js
│ ├── auth.middleware.js
│ └── logger.middleware.js
├── config/ # 配置文件
│ └── jwt.config.js
├── utils/ # 工具类
│ └── file.utils.js
├── app.js # 入口文件
└── package.jsonPOST /api/auth/register
Content-Type: application/json
{
"username": "string",
"password": "string"
}
Response:
{
"code": 201,
"message": "注册成功",
"data": {
"token": "JWT_TOKEN"
}
}POST /api/auth/login
Content-Type: application/json
{
"username": "string",
"password": "string"
}
Response:
{
"code": 200,
"message": "登录成功",
"data": {
"token": "JWT_TOKEN"
}
}POST /api/chat
Content-Type: application/json
Authorization: Bearer <token>
{
"model": "ollama.qwen",
"message": "你好"
}
Response (SSE):
data: {"content": "你好"}
data: {"content": "有什么可以帮助你?"}
...
data: {"done": true}// router/chat.router.js
const express = require("express");
const router = express.Router();
const chatService = require("../service/chat.service");
router.post("/", requireAuth(), async (req, res) => {
try {
await chatService.streamChat(req, res);
} catch (error) {
res.status(500).json({ code: 500, message: "聊天服务异常" });
}
});
module.exports = router;// service/user.service.js
const bcrypt = require("bcryptjs");
const userStage = require("../stage/user.stage");
async function registerUser(userData) {
const hashedPassword = await bcrypt.hash(userData.password, 8);
userData.password = hashedPassword;
return await userStage.saveUser(userData);
}
async function validateUser(username, password) {
const user = await userStage.getUserByUsername(username);
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
return null;
}// stage/user.stage.js
const fileUtils = require("../utils/file.utils");
async function saveUser(userData) {
const users = await fileUtils.readDataFile("users.json");
users.push(userData);
await fileUtils.writeDataFile("users.json", users);
return userData;
}
async function getUserByUsername(username) {
const users = await fileUtils.readDataFile("users.json");
return users.find((user) => user.username === username);
}// middleware/auth.middleware.js
const jwt = require("jsonwebtoken");
const config = require("../config/jwt.config");
function requireAuth() {
return (req, res, next) => {
try {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ code: 401, message: "未提供认证令牌" });
}
const decoded = jwt.verify(token, config.secretKey);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ code: 401, message: "无效的认证令牌" });
}
};
}// middleware/logger.middleware.js
function requestLogger() {
return (req, res, next) => {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
console.log(
`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} - ${
res.statusCode
} - ${duration}ms`
);
console.log("客户端IP:", req.ip);
console.log("请求参数:", req.method === "POST" ? req.body : req.query);
});
next();
};
}[
{
"id": "uuid",
"username": "testuser",
"password": "$2a$08$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"createdAt": "2025-08-03T10:00:00Z"
}
]- 用户密码必须使用 bcrypt 加密存储
- JWT 令牌需设置合理过期时间(建议 15 分钟)
- 所有接口返回统一格式
{
"code": 200,
"message": "操作成功",
"data": {}
}- 错误处理中间件统一处理异常
- 所有异步操作使用 async/await 并处理异常