Skip to content

Latest commit

 

History

History
245 lines (197 loc) · 5.18 KB

File metadata and controls

245 lines (197 loc) · 5.18 KB

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.json

三、接口规范

1. 用户认证接口

POST /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"
  }
}

2. 流式聊天接口

POST /api/chat
Content-Type: application/json
Authorization: Bearer <token>

{
  "model": "ollama.qwen",
  "message": "你好"
}

Response (SSE):
data: {"content": "你好"}
data: {"content": "有什么可以帮助你?"}
...
data: {"done": true}

四、分层实现规范

1. 路由层规范

// 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;

2. 服务层规范

// 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;
}

3. 数据层规范

// 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);
}

五、中间件规范

1. JWT 权限验证中间件

// 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: "无效的认证令牌" });
    }
  };
}

2. 日志中间件

// 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();
  };
}

六、数据存储结构

1. 用户数据 (users.json)

[
  {
    "id": "uuid",
    "username": "testuser",
    "password": "$2a$08$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "createdAt": "2025-08-03T10:00:00Z"
  }
]

七、安全要求:

  1. 用户密码必须使用 bcrypt 加密存储
  2. JWT 令牌需设置合理过期时间(建议 15 分钟)

八、开发规范:

  1. 所有接口返回统一格式
{
  "code": 200,
  "message": "操作成功",
  "data": {}
}
  1. 错误处理中间件统一处理异常
  2. 所有异步操作使用 async/await 并处理异常