阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 项目目录结构的合理规划

项目目录结构的合理规划

作者:陈川 阅读数:64520人阅读 分类: Node.js

合理的项目目录结构是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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌