跳到主要内容

Koa Mongoose

2025年03月01日
柏拉文
越努力,越幸运

一、认识


Node.js 应用中, 常见的服务架构一般采用分层(或 MVC)的方式,将各个职责区分开来,主要包括以下几个层次:

  1. 路由层 Routes: 负责将请求路由到对应的控制器,是入口层。负责接收客户端请求,并根据 URLHTTP 方法将请求分发到对应的控制器方法。

    // 使用 koa-router 定义路由
    const Router = require('koa-router');
    const positionRouter = new Router();

    // 定义一个 POST 路由,带有动态参数 id
    positionRouter.post("/editItem/:id", editItem);

    module.exports = positionRouter;
  2. 控制器层 Controllers: 解析请求、调用业务逻辑、处理响应和错误,是接口层。作为请求与业务逻辑之间的中介。主要负责解析请求参数、调用业务逻辑(可能在 Service 层或直接调用 Model),然后组织响应返回给客户端。比如说: 获取路由参数(例如 ctx.params)和请求体(ctx.request.body); 校验参数合法性; 处理异常,并根据业务逻辑组织响应数据

    async function editItem(ctx) {
    const id = ctx.params.id; // 从 URL 获取 id
    const data = ctx.request.body; // 从请求体中获取其它数据

    try {
    // 调用数据层或者业务层逻辑进行更新操作
    await PositionModel.updateOne({ _id: id }, data);
    ctx.body = { code: 200, message: "编辑成功" };
    } catch (error) {
    ctx.body = { code: 500, message: "编辑失败", error: error.message };
    }
    }
  3. 数据模型层 Model: 封装数据库操作,定义数据模型,负责数据的持久化。主要负责与数据库进行交互,封装了对数据的增删改查操作。通常使用 ORM/ODM(如 Mongoose 对于 MongoDBSequelize 对于 SQL 数据库)来定义数据模型(Schema)和操作方法。比如: 定义数据结构和数据校验规则; 封装数据库操作,隐藏数据库实现细节。

    const mongoose = require('mongoose');

    // 定义 Schema
    const PositionSchema = new mongoose.Schema({
    title: { type: String, required: true },
    description: String,
    salary: Number,
    // ...其它字段
    });

    // 创建 Model
    const PositionModel = mongoose.model('Position', PositionSchema);

    module.exports = PositionModel;
  4. Service: 聚合复杂业务逻辑,解耦控制器和数据层,提高代码的可测试性和可维护性。进一步分离业务逻辑,将复杂的业务操作从控制器中抽离出来,使控制器只关注请求和响应,业务逻辑则由 Service 层完成。比如: 组合多个数据操作,处理事务或复杂的业务流程; 对外暴露统一的业务接口,便于复用和单元测试。

    // userService.js
    const PositionModel = require('../models/Position');

    async function updatePosition(id, data) {
    // 可以包含更多复杂的业务逻辑
    return await PositionModel.updateOne({ _id: id }, data);
    }

    module.exports = { updatePosition };

    控制器中调用:

    const { updatePosition } = require('../services/positionService');

    async function editItem(ctx) {
    const id = ctx.params.id;
    const data = ctx.request.body;

    try {
    await updatePosition(id, data);
    ctx.body = { code: 200, message: "编辑成功" };
    } catch (error) {
    ctx.body = { code: 500, message: "编辑失败", error: error.message };
    }
    }
  5. 中间件 Middleware: 提供通用功能(如请求解析、日志、权限验证),贯穿整个请求生命周期。在请求处理链中提供额外功能,比如请求体解析、日志记录、权限验证、错误处理等。KoaExpress 等框架都支持中间件机制,允许在路由之前或之后插入公共逻辑。

    const Koa = require('koa');
    const bodyParser = require('koa-bodyparser');
    const app = new Koa();

    // 使用中间件解析请求体
    app.use(bodyParser());

    // 挂载路由
    const positionRouter = require('./routes/position');
    app.use(positionRouter.routes());

二、主程序


const Koa = require("koa");
const KoaBody = require("koa-body");
const mongoose = require("mongoose");
const positionRouter = require("./router/position");

const port = 3000;
const app = new Koa();
const { koaBody } = KoaBody;

mongoose.connect("mongodb://127.0.0.1:27017/bolawen");

mongoose.connection.on("connected", function () {
console.log("MongoDB 启动成功!!!");
});
mongoose.connection.on("error", function (error) {
console.log("MongoDB 出现错误, 错误为:", error);
});
mongoose.connection.on("disconnected", function () {
console.log("MongoDB 断开连接!!!");
});

app.use(
koaBody({
text: true,
json: true,
multipart: true,
urlencoded: true,
encoding: "gzip",
})
);
app.use(positionRouter.routes()).use(positionRouter.allowedMethods());

app.listen(port, () => {
console.log("Koa 服务启动成功!!!");
});

三、路由层


const KoaRouter = require("koa-router");
const {
getList,
addItem,
editItem,
getDetail,
removeItem,
} = require("../controller/position");

const positionRouter = new KoaRouter({ prefix: "/position" });

positionRouter.get("/getList", getList);
positionRouter.get("/getDetail", getDetail);
positionRouter.get("/getDetail/:id", getDetail);

positionRouter.post("/addItem", addItem);
positionRouter.post("/editItem", editItem);
positionRouter.post("/editItem/:id", editItem);
positionRouter.post("/removeItem", removeItem);
positionRouter.post("/removeItem/:id", removeItem);

module.exports = positionRouter;

四、控制器层


const PositionModel = require("../model/position");

async function getList(ctx) {
const page = parseInt(ctx.query.page, 10) || 1;
const limit = parseInt(ctx.query.limit, 10) || 10;
const skip = (page - 1) * limit;

const [list, total] = await Promise.all([
PositionModel.find().skip(skip).limit(limit),
PositionModel.countDocuments(),
]);

if (list) {
ctx.body = {
code: 200,
data: {
list,
pageInfo: {
page,
limit,
total,
},
},
message: "查询成功!!",
};
return;
}

ctx.body = {
code: 200,
message: "查询失败!!",
};
}

async function getDetail(ctx) {
const id = ctx.params.id || ctx.query.id;
const result = await PositionModel.findById(id);

if (result) {
ctx.body = {
code: 200,
data: result,
message: "查询成功!!",
};
return;
}

ctx.body = {
code: 200,
message: "查询失败!!",
};
}

async function addItem(ctx) {
const { body } = ctx.request;

const result = await PositionModel.create(body);

if (result) {
ctx.body = {
code: 200,
message: "添加成功!!",
};
return;
}

ctx.body = {
code: 200,
message: "添加失败!!",
};
}

async function editItem(ctx) {
const { body } = ctx.request;
const id = ctx.params.id || body.id;
const { stock, price, quantity } = body;

const result = await PositionModel.findByIdAndUpdate(id, {
stock,
price,
quantity,
});

if (result) {
ctx.body = {
code: 200,
message: "编辑成功!!",
};
return;
}

ctx.body = {
code: 200,
message: "编辑失败!!",
};
}

async function removeItem(ctx) {
const { body } = ctx.request;
const id = ctx.params.id || body.id;
const result = await PositionModel.findByIdAndDelete(id);

if (result) {
ctx.body = {
code: 200,
message: "删除成功!!",
};
return;
}

ctx.body = {
code: 200,
message: "删除失败!!",
};
}

module.exports = {
getList,
addItem,
editItem,
getDetail,
removeItem,
};

五、数据模型层


const mongoose = require("mongoose");

const PositionSchema = new mongoose.Schema({
stock: { type: String, required: true },
price: { type: Number, required: true },
account: { type: String, required: true },
quantity: { type: Number, required: true },
});

const PositionModel = mongoose.model("Position", PositionSchema);

module.exports = PositionModel;