项目目录结构的合理规划
合理的项目目录结构是Koa2应用可维护性和扩展性的基础。清晰的模块划分能显著提升团队协作效率,降低后期维护成本。下面从核心目录设计到具体实现细节展开说明。
核心目录结构设计
典型的Koa2项目建议采用功能模块划分为主、技术分层为辅的混合结构。基础目录应包含以下核心部分:
project-root/
├── src/
│ ├── app.js # 应用入口
│ ├── config/ # 配置文件
│ ├── controllers/ # 控制器层
│ ├── services/ # 业务逻辑层
│ ├── models/ # 数据模型层
│ ├── routes/ # 路由定义
│ ├── middlewares/ # 自定义中间件
│ ├── utils/ # 工具函数
│ └── public/ # 静态资源
├── tests/ # 测试代码
├── .env # 环境变量
└── package.json
配置管理规范
配置文件应当按环境分离,并通过dotenv加载敏感信息。建议采用以下结构:
// config/default.js
module.exports = {
port: 3000,
db: {
host: 'localhost',
name: 'dev_db'
}
};
// config/production.js
const { merge } = require('lodash');
const baseConfig = require('./default');
module.exports = merge(baseConfig, {
port: process.env.APP_PORT,
db: {
host: process.env.DB_HOST,
name: process.env.DB_NAME
}
});
在应用启动时动态加载配置:
// app.js
const environment = process.env.NODE_ENV || 'development';
const config = require(`./config/${environment}`);
路由组织最佳实践
避免将所有路由堆砌在单个文件中,应按业务模块拆分:
// routes/api/v1/users.js
const router = require('koa-router')();
const UserController = require('../../controllers/user');
router.prefix('/api/v1/users');
router.get('/', UserController.list);
router.post('/:id', UserController.create);
module.exports = router;
主路由文件进行聚合:
// routes/index.js
const combineRouters = require('koa-combine-routers');
const userRouter = require('./api/v1/users');
const productRouter = require('./api/v1/products');
module.exports = combineRouters(
userRouter,
productRouter
);
中间件分层管理
自定义中间件应当独立存放,并按功能分类:
middlewares/
├── error-handler.js # 错误处理
├── request-logger.js # 请求日志
└── auth/ # 认证相关
├── jwt.js
└── basic-auth.js
示例认证中间件实现:
// middlewares/auth/jwt.js
const jwt = require('jsonwebtoken');
module.exports = (opts = {}) => {
return async (ctx, next) => {
const token = ctx.headers.authorization?.split(' ')[1];
try {
ctx.state.user = jwt.verify(token, opts.secret);
await next();
} catch (err) {
ctx.throw(401, 'Invalid token');
}
};
};
业务逻辑分层
典型的MVC架构中,各层职责应明确划分:
// controllers/user.js
const UserService = require('../services/user');
class UserController {
static async list(ctx) {
const users = await UserService.findAll();
ctx.body = { data: users };
}
}
// services/user.js
const UserModel = require('../models/user');
class UserService {
static async findAll() {
return UserModel.find().lean();
}
}
// models/user.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: String,
email: String
});
module.exports = mongoose.model('User', userSchema);
静态资源处理
静态文件应当与代码分离,并通过专用中间件处理:
// app.js
const serve = require('koa-static');
const mount = require('koa-mount');
app.use(mount('/assets', serve('public/assets')));
app.use(mount('/uploads', serve('storage/uploads')));
推荐目录结构:
public/
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
└── favicon.ico
storage/
└── uploads/
├── avatars/
└── documents/
测试代码组织
测试目录应镜像源代码结构,并添加测试类型后缀:
tests/
├── unit/
│ ├── services/
│ └── utils/
├── integration/
│ ├── api/
│ └── middlewares/
└── fixtures/ # 测试夹具
示例测试文件:
// tests/unit/services/user.test.js
const UserService = require('../../../src/services/user');
const mockUserModel = {
find: jest.fn().mockResolvedValue([{name: 'test'}])
};
jest.mock('../../../src/models/user', () => mockUserModel);
describe('UserService', () => {
it('should return user list', async () => {
const users = await UserService.findAll();
expect(users).toHaveLength(1);
});
});
环境相关文件处理
不同环境的特殊文件应当通过约定区分:
.env.development
.env.test
.env.production
在package.json中配置启动命令:
{
"scripts": {
"dev": "NODE_ENV=development nodemon src/app.js",
"test": "NODE_ENV=test jest",
"start": "NODE_ENV=production node src/app.js"
}
}
工具函数管理
通用工具函数应按功能分类存放:
utils/
├── date-utils.js # 日期处理
├── crypto-utils.js # 加密相关
├── validation.js # 数据校验
└── api/ # API相关
├── wrapper.js # 响应包装
└── error.js # API错误
示例响应包装器:
// utils/api/wrapper.js
module.exports = {
success(data, meta = {}) {
return {
status: 'success',
data,
meta
};
},
error(message, code = 400) {
return {
status: 'error',
message,
code
};
}
};
日志文件管理
生产环境日志应当按类型和日期分割:
logs/
├── access/
│ ├── 2023-08-01.log
│ └── 2023-08-02.log
├── error/
│ └── 2023-08-01.error.log
└── application/
└── 2023-08-01.app.log
配置日志中间件示例:
// middlewares/logger.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const appendFile = promisify(fs.appendFile);
module.exports = (options = {}) => {
return async (ctx, next) => {
const start = Date.now();
await next();
const log = `${new Date().toISOString()} | ${ctx.method} ${ctx.url} | ${ctx.status} | ${Date.now() - start}ms\n`;
await appendFile(path.join(options.logDir, `${new Date().toISOString().split('T')[0]}.log`), log);
};
};
文档与脚本管理
项目辅助文件应当统一存放:
docs/
├── api.md # API文档
├── db-schema.md # 数据库设计
└── setup-guide.md # 环境搭建指南
scripts/
├── db-migrate.js # 数据库迁移
└── seed-data.js # 初始数据
第三方模块扩展
当集成第三方模块时,建议创建专用目录:
lib/
├── email/ # 邮件服务封装
│ ├── sender.js
│ └── templates/
├── payment/ # 支付网关
│ └── stripe.js
└── sms/ # 短信服务
└── twilio.js
示例邮件服务封装:
// lib/email/sender.js
const nodemailer = require('nodemailer');
class EmailSender {
constructor(config) {
this.transporter = nodemailer.createTransport(config);
}
async send(templateName, to, data) {
const template = require(`./templates/${templateName}`);
return this.transporter.sendMail({
to,
html: template(data)
});
}
}
module.exports = EmailSender;
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:常用开发工具与调试技巧