当前位置: 首页 > news >正文

Egg.js 完全指南:企业级 Node.js 应用框架

文章目录

    • 目标
    • 什么是 Egg.js?
    • Egg.js 项目起步
      • 1. 创建项目
      • 2. 项目结构
    • Egg.js 核心概念
      • 1. 应用程序结构
      • 2. 控制器 (Controller)
      • 3. 服务 (Service)
      • 4. 路由配置
    • 配置管理
      • 1. 默认配置
      • 2. 开发环境配置
      • 3. 生产环境配置
      • 4. 插件配置
    • 中间件开发
      • 1. 认证中间件
      • 2. 参数验证中间件
      • 3. 错误处理中间件
    • 数据库集成
      • 1. Sequelize 模型定义
    • 认证与授权
      • 1. JWT 配置
      • 2. 认证控制器
    • 定时任务
    • 部署与生产环境
      • 1. 环境变量配置
      • 2. PM2 配置
      • 3. Docker 配置
    • 总结

目标

  • 了解什么是egg.js。
  • 学会简单使用egg.js。创建一个简单的egg.js。

什么是 Egg.js?

Egg.js 是基于 Node.js 和 Koa 的企业级应用开发框架,它为开发团队和项目提供了清晰的约定和规范,让开发者能够专注于业务逻辑,快速构建高质量的应用程序。

Egg.js 遵循 “约定优于配置” 的原则,提供了一套完整的 MVC 解决方案,内置了多进程管理、插件机制、框架扩展等企业级特性,帮助团队和开发者降低开发和维护成本。

  • Egg.js 是阿里巴巴开源的企业级 Node.js 框架
  • 基于 Koa 2.x,异步解决方案基于 async/await
  • 提供高度可扩展的插件机制和框架定制能力
  • 内置多进程模型,充分利用多核 CPU 资源
  • 提供统一的开发规范和最佳实践

Egg.js 的优势

  • 完善的生态系统:丰富的官方插件和社区插件
  • 企业级稳定性:经过阿里巴巴大规模业务验证
  • 高度可扩展:灵活的插件机制和框架定制能力
  • 开发体验友好:完善的开发工具链和调试支持
  • 团队协作高效:统一的约定和规范,降低沟通成本

适用场景

  • 大型企业级应用开发
  • 需要统一技术栈和开发规范的团队
  • 高并发、高性能要求的应用
  • 需要快速迭代的业务系统
  • 微服务架构中的单体服务

Egg.js 项目起步

1. 创建项目

# 使用 egg-init 创建项目
npm init egg --type=simple# 或者使用官方脚手架
npx egg-init egg-example --type=simple# 进入项目目录
cd egg-example# 安装依赖
npm install# 启动开发服务器
npm run dev

2. 项目结构

egg-example/
├── app/
│   ├── controller/
│   │   └── home.js
│   ├── service/
│   ├── middleware/
│   ├── router.js
│   └── extend/
├── config/
│   ├── config.default.js
│   ├── config.prod.js
│   └── plugin.js
├── test/
├── typings/
├── .autod.conf.js
├── .eslintrc
├── .gitignore
├── package.json
└── README.md

Egg.js 核心概念

1. 应用程序结构

Egg.js 遵循约定式目录结构:

app/
├── controller/        # 控制器目录
├── service/           # 服务目录  
├── model/             # 模型目录(可选)
├── middleware/        # 中间件目录
├── schedule/          # 定时任务目录
├── public/            # 静态资源目录
├── view/              # 模板目录
├── router.js          # 路由配置
└── extend/            # 扩展目录├── application.js├── context.js├── request.js├── response.js└── helper.js

2. 控制器 (Controller)

控制器负责处理用户请求并返回响应:

// app/controller/user.js
const { Controller } = require('egg');class UserController extends Controller {// 获取用户列表async index() {const { ctx, service } = this;// 验证查询参数const query = ctx.query;ctx.validate({page: { type: 'int', required: false, default: 1 },pageSize: { type: 'int', required: false, default: 10 }}, query);// 调用服务层const users = await service.user.list(query);// 返回响应ctx.body = {success: true,data: users,message: '获取用户列表成功'};}// 获取用户详情async show() {const { ctx, service } = this;const { id } = ctx.params;ctx.validate({id: { type: 'id', required: true }}, { id });const user = await service.user.findById(id);if (!user) {ctx.throw(404, '用户不存在');}ctx.body = {success: true,data: user,message: '获取用户详情成功'};}// 创建用户async create() {const { ctx, service } = this;const userData = ctx.request.body;// 参数验证ctx.validate({username: { type: 'string', required: true },email: { type: 'email', required: true },password: { type: 'string', required: true, min: 6 }});const user = await service.user.create(userData);ctx.status = 201;ctx.body = {success: true,data: user,message: '用户创建成功'};}// 更新用户async update() {const { ctx, service } = this;const { id } = ctx.params;const userData = ctx.request.body;ctx.validate({id: { type: 'id', required: true }}, { id });const user = await service.user.update(id, userData);ctx.body = {success: true,data: user,message: '用户更新成功'};}// 删除用户async destroy() {const { ctx, service } = this;const { id } = ctx.params;ctx.validate({id: { type: 'id', required: true }}, { id });await service.user.delete(id);ctx.status = 204;}
}module.exports = UserController;

3. 服务 (Service)

服务层封装业务逻辑:

// app/service/user.js
const { Service } = require('egg');class UserService extends Service {// 获取用户列表async list(query = {}) {const { app } = this;const { page = 1, pageSize = 10, ...where } = query;const options = {where,limit: parseInt(pageSize),offset: (parseInt(page) - 1) * parseInt(pageSize),order: [[ 'created_at', 'DESC' ]]};const { count, rows } = await app.model.User.findAndCountAll(options);return {list: rows,pagination: {page: parseInt(page),pageSize: parseInt(pageSize),total: count,totalPages: Math.ceil(count / parseInt(pageSize))}};}// 根据ID查找用户async findById(id) {const { app } = this;const user = await app.model.User.findByPk(id, {attributes: { exclude: ['password'] }});return user;}// 根据邮箱查找用户async findByEmail(email) {const { app } = this;const user = await app.model.User.findOne({where: { email }});return user;}// 创建用户async create(userData) {const { app, ctx } = this;// 检查邮箱是否已存在const existingUser = await this.findByEmail(userData.email);if (existingUser) {ctx.throw(422, '邮箱已被注册');}// 加密密码userData.password = await ctx.genHash(userData.password);const user = await app.model.User.create(userData);// 返回用户信息(排除密码)const result = user.toJSON();delete result.password;return result;}// 更新用户async update(id, userData) {const { app, ctx } = this;const user = await app.model.User.findByPk(id);if (!user) {ctx.throw(404, '用户不存在');}// 如果更新邮箱,检查是否重复if (userData.email && userData.email !== user.email) {const existingUser = await this.findByEmail(userData.email);if (existingUser) {ctx.throw(422, '邮箱已被注册');}}// 如果更新密码,重新加密if (userData.password) {userData.password = await ctx.genHash(userData.password);}await user.update(userData);const result = user.toJSON();delete result.password;return result;}// 删除用户async delete(id) {const { app, ctx } = this;const user = await app.model.User.findByPk(id);if (!user) {ctx.throw(404, '用户不存在');}await user.destroy();return true;}
}module.exports = UserService;

4. 路由配置

// app/router.js
module.exports = app => {const { router, controller, middleware } = app;// 中间件const auth = middleware.auth();const validate = middleware.validate();// 首页router.get('/', controller.home.index);// 用户相关路由router.get('/users', controller.user.index);router.get('/users/:id', controller.user.show);router.post('/users', validate, controller.user.create);router.put('/users/:id', auth, validate, controller.user.update);router.delete('/users/:id', auth, controller.user.destroy);// 认证相关路由router.post('/auth/register', validate, controller.auth.register);router.post('/auth/login', validate, controller.auth.login);router.post('/auth/logout', auth, controller.auth.logout);router.get('/auth/me', auth, controller.auth.me);// 文章相关路由router.get('/posts', controller.post.index);router.get('/posts/:id', controller.post.show);router.post('/posts', auth, validate, controller.post.create);router.put('/posts/:id', auth, validate, controller.post.update);router.delete('/posts/:id', auth, controller.post.destroy);
};

配置管理

1. 默认配置

// config/config.default.js
const { EggAppConfig, EggAppInfo, PowerPartial } = require('egg');module.exports = (appInfo: EggAppInfo) => {const config = {} as PowerPartial<EggAppConfig>;// 覆盖框架默认配置config.keys = appInfo.name + '_1234567890';// 中间件配置config.middleware = ['errorHandler'];// 安全配置config.security = {csrf: {enable: false, // 开发环境关闭 CSRF},};// 跨域配置config.cors = {origin: '*',allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',};// 数据库配置config.sequelize = {dialect: 'mysql',host: '127.0.0.1',port: 3306,database: 'egg_example',username: 'root',password: 'password',timezone: '+08:00',define: {timestamps: true,paranoid: true,underscored: true,freezeTableName: true,},};// 参数验证配置config.validate = {convert: true,widelyUndefined: true,};// 日志配置config.logger = {level: 'INFO',consoleLevel: 'DEBUG',};return {...config,};
};

2. 开发环境配置

// config/config.local.js
module.exports = () => {const config = {};// 开发环境数据库配置config.sequelize = {dialect: 'mysql',host: '127.0.0.1',port: 3306,database: 'egg_example_dev',username: 'root',password: 'password',logging: console.log, // 显示 SQL 日志};// 开发环境日志配置config.logger = {level: 'DEBUG',consoleLevel: 'DEBUG',};return config;
};

3. 生产环境配置

// config/config.prod.js
module.exports = () => {const config = {};// 生产环境数据库配置config.sequelize = {dialect: 'mysql',host: process.env.DB_HOST || '127.0.0.1',port: process.env.DB_PORT || 3306,database: process.env.DB_NAME || 'egg_example',username: process.env.DB_USER || 'root',password: process.env.DB_PASSWORD || 'password',logging: false, // 生产环境关闭 SQL 日志};// 生产环境日志配置config.logger = {level: 'WARN',consoleLevel: 'WARN',dir: '/path/to/your/logs', // 日志文件目录};// 集群配置config.cluster = {listen: {path: '',port: 7001,hostname: '0.0.0.0',},};return config;
};

4. 插件配置

// config/plugin.js
module.exports = {// 数据库插件sequelize: {enable: true,package: 'egg-sequelize',},// 参数验证插件validate: {enable: true,package: 'egg-validate',},// 跨域插件cors: {enable: true,package: 'egg-cors',},// Redis 插件redis: {enable: true,package: 'egg-redis',},// 会话插件session: {enable: true,package: 'egg-session',},// JWT 插件jwt: {enable: true,package: 'egg-jwt',},
};

中间件开发

1. 认证中间件

// app/middleware/auth.js
module.exports = (options = {}) => {return async (ctx, next) => {// 从请求头获取 tokenconst token = ctx.get('Authorization');if (!token || !token.startsWith('Bearer ')) {ctx.throw(401, '未提供认证令牌');}const accessToken = token.slice(7);try {// 验证 JWT tokenconst decoded = ctx.app.jwt.verify(accessToken, ctx.app.config.jwt.secret);// 从数据库获取用户信息const user = await ctx.service.user.findById(decoded.userId);if (!user) {ctx.throw(401, '用户不存在');}// 将用户信息挂载到上下文ctx.state.user = user;ctx.state.userId = user.id;await next();} catch (error) {if (error.name === 'TokenExpiredError') {ctx.throw(401, '认证令牌已过期');} else if (error.name === 'JsonWebTokenError') {ctx.throw(401, '无效的认证令牌');} else {ctx.throw(401, '认证失败');}}};
};

2. 参数验证中间件

// app/middleware/validate.js
module.exports = (options = {}) => {return async (ctx, next) => {try {await next();} catch (error) {if (error.name === 'ValidationFailed') {ctx.status = 422;ctx.body = {success: false,message: '参数验证失败',errors: error.errors};return;}throw error;}};
};

3. 错误处理中间件

// app/middleware/error_handler.js
module.exports = (options = {}) => {return async (ctx, next) => {try {await next();// 处理 404if (ctx.status === 404 && !ctx.body) {if (ctx.acceptJSON) {ctx.body = { success: false, message: '接口不存在' };} else {ctx.body = '<h1>页面不存在</h1>';}}} catch (error) {// 记录错误日志ctx.app.emit('error', error, ctx);const status = error.status || 500;const message = status === 500 ? '内部服务器错误' : error.message;// 生产环境不返回错误堆栈const isProd = ctx.app.config.env === 'prod';ctx.status = status;if (ctx.acceptJSON) {ctx.body = {success: false,message,...(isProd ? {} : { stack: error.stack })};} else {ctx.body = isProd ? `<h1>${message}</h1>` : `<h1>${message}</h1><pre>${error.stack}</pre>`;}}};
};

数据库集成

1. Sequelize 模型定义

// app/model/user.js
module.exports = app => {const { STRING, INTEGER, DATE, BOOLEAN } = app.Sequelize;const User = app.model.define('user', {id: {type: INTEGER,primaryKey: true,autoIncrement: true,},username: {type: STRING(50),allowNull: false,unique: true,comment: '用户名',},email: {type: STRING(100),allowNull: false,unique: true,validate: {isEmail: true,},comment: '邮箱',},password: {type: STRING(255),allowNull: false,comment: '密码',},avatar: {type: STRING(255),allowNull: true,comment: '头像',},is_active: {type: BOOLEAN,defaultValue: true,comment: '是否激活',},last_login_at: {type: DATE,allowNull: true,comment: '最后登录时间',},created_at: DATE,updated_at: DATE,deleted_at: DATE,}, {tableName: 'users',paranoid: true, // 软删除underscored: true, // 字段名使用下划线});// 关联关系User.associate = function() {app.model.User.hasMany(app.model.Post, {foreignKey: 'author_id',as: 'posts',});};return User;
};
// app/model/post.js
module.exports = app => {const { STRING, TEXT, INTEGER, DATE, BOOLEAN, ENUM } = app.Sequelize;const Post = app.model.define('post', {id: {type: INTEGER,primaryKey: true,autoIncrement: true,},title: {type: STRING(200),allowNull: false,comment: '文章标题',},content: {type: TEXT('long'),allowNull: false,comment: '文章内容',},excerpt: {type: TEXT,allowNull: true,comment: '文章摘要',},status: {type: ENUM('draft', 'published', 'archived'),defaultValue: 'draft',comment: '文章状态',},view_count: {type: INTEGER,defaultValue: 0,comment: '阅读次数',},author_id: {type: INTEGER,allowNull: false,comment: '作者ID',},published_at: {type: DATE,allowNull: true,comment: '发布时间',},created_at: DATE,updated_at: DATE,deleted_at: DATE,}, {tableName: 'posts',paranoid: true,underscored: true,});// 关联关系Post.associate = function() {app.model.Post.belongsTo(app.model.User, {foreignKey: 'author_id',as: 'author',});app.model.Post.belongsToMany(app.model.Category, {through: 'post_categories',foreignKey: 'post_id',otherKey: 'category_id',as: 'categories',});};return Post;
};

认证与授权

1. JWT 配置

// config/config.default.js
module.exports = appInfo => {const config = {};// JWT 配置config.jwt = {secret: appInfo.name + '_jwt_secret_123456',expiresIn: '7d', // token 过期时间};return config;
};

2. 认证控制器

// app/controller/auth.js
const { Controller } = require('egg');class AuthController extends Controller {// 用户注册async register() {const { ctx, service } = this;const userData = ctx.request.body;// 参数验证ctx.validate({username: { type: 'string', required: true, min: 2, max: 50 },email: { type: 'email', required: true },password: { type: 'string', required: true, min: 6 }});const user = await service.user.create(userData);// 生成 tokenconst token = ctx.app.jwt.sign({ userId: user.id },ctx.app.config.jwt.secret,{ expiresIn: ctx.app.config.jwt.expiresIn });ctx.success({user: {id: user.id,username: user.username,email: user.email},token}, '注册成功');}// 用户登录async login() {const { ctx, service } = this;const { email, password } = ctx.request.body;ctx.validate({email: { type: 'email', required: true },password: { type: 'string', required: true }});// 查找用户const user = await service.user.findByEmail(email);if (!user) {ctx.throw(401, '邮箱或密码错误');}// 验证密码const isValidPassword = await ctx.comparePassword(password, user.password);if (!isValidPassword) {ctx.throw(401, '邮箱或密码错误');}// 更新最后登录时间await user.update({ last_login_at: new Date() });// 生成 tokenconst token = ctx.app.jwt.sign({ userId: user.id },ctx.app.config.jwt.secret,{ expiresIn: ctx.app.config.jwt.expiresIn });ctx.success({user: {id: user.id,username: user.username,email: user.email},token}, '登录成功');}// 退出登录async logout() {// JWT 是无状态的,客户端删除 token 即可ctx.success(null, '退出成功');}// 获取当前用户信息async me() {const { ctx, service } = this;const userId = ctx.state.userId;const user = await service.user.findById(userId);ctx.success({user: {id: user.id,username: user.username,email: user.email,avatar: user.avatar,last_login_at: user.last_login_at}}, '获取用户信息成功');}
}module.exports = AuthController;

定时任务

// app/schedule/clear_expired_tokens.js
const { Subscription } = require('egg');class ClearExpiredTokens extends Subscription {// 通过 schedule 属性来设置定时任务的执行间隔等配置static get schedule() {return {interval: '1h', // 1 小时间隔type: 'all', // 指定所有的 worker 都需要执行};}// subscribe 是真正定时任务执行时被运行的函数async subscribe() {const { ctx } = this;try {// 清理过期的 refresh tokens 等const result = await ctx.app.model.Token.destroy({where: {expires_at: {[ctx.app.Sequelize.Op.lt]: new Date()}}});if (result > 0) {ctx.logger.info(`Cleared ${result} expired tokens`);}} catch (error) {ctx.logger.error('Clear expired tokens error:', error);}}
}module.exports = ClearExpiredTokens;

部署与生产环境

1. 环境变量配置

# .env.production
NODE_ENV=production
EGG_SERVER_ENV=prod
DB_HOST=localhost
DB_PORT=3306
DB_USER=egg_blog
DB_PASSWORD=your_password
DB_NAME=egg_blog_prod
JWT_SECRET=your-jwt-secret-key
REDIS_HOST=localhost
REDIS_PORT=6379

2. PM2 配置

// ecosystem.config.js
module.exports = {apps: [{name: 'egg-blog',script: 'node',args: '--require egg-scripts/start',instances: 'max', // 根据 CPU 核心数启动实例exec_mode: 'cluster', // 集群模式watch: false,env: {EGG_SERVER_ENV: 'prod',NODE_ENV: 'production',},error_file: './logs/err.log',out_file: './logs/out.log',log_file: './logs/combined.log',time: true,merge_logs: true,instance_var: 'INSTANCE_ID',}],
};

3. Docker 配置

# Dockerfile
FROM node:14-alpine# 设置时区
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai# 创建应用目录
WORKDIR /app# 复制 package.json
COPY package*.json ./# 安装依赖
RUN npm install --production# 复制应用代码
COPY . .# 创建日志目录
RUN mkdir -p /app/logs# 暴露端口
EXPOSE 7001# 启动应用
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:app:build: .ports:- "7001:7001"environment:- NODE_ENV=production- EGG_SERVER_ENV=proddepends_on:- mysql- redisvolumes:- ./logs:/app/logsrestart: unless-stoppedmysql:image: mysql:8.0environment:MYSQL_ROOT_PASSWORD: rootpasswordMYSQL_DATABASE: egg_blogMYSQL_USER: egg_userMYSQL_PASSWORD: egg_passwordvolumes:- mysql_data:/var/lib/mysqlrestart: unless-stoppedredis:image: redis:6-alpinevolumes:- redis_data:/datarestart: unless-stoppedvolumes:mysql_data:redis_data:

总结

Egg.js 作为一个企业级的 Node.js 框架,通过其"约定优于配置"的设计理念,为团队提供了统一的开发规范和最佳实践。它基于 Koa 2.x,充分利用了现代 JavaScript 的特性,同时提供了完整的 MVC 解决方案和丰富的插件生态。

本文详细介绍了 Egg.js 的核心概念、目录结构、配置管理、中间件开发、数据库集成、认证授权。掌握这些知识后,你将能够使用 Egg.js 构建出高质量、可维护的企业级应用程序。

Egg.js 的插件机制和框架扩展能力使其具有极高的灵活性,能够满足各种复杂业务场景的需求。无论是小型项目还是大型企业应用,Egg.js 都能提供稳定可靠的解决方案。

http://www.dtcms.com/a/473847.html

相关文章:

  • 矩阵的求逆
  • 网页设计做网站wordpress主题添加双备案号
  • 已有备案网站增加域名咸阳网站建设价格
  • go-swagger学习笔记
  • Blender硬面建模灯光渲染材质修改器纹理烘焙插件 Rantools And P-Cutter All-In-One Addon V3.3.10
  • Autosar OS简介
  • 建设企业网站制作公司贵阳做网站公司排名
  • 设计模式篇之 桥接模式 Bridge
  • Spring IOC(控制反转)中常用注解
  • 常州建设银行网站安源网站建设
  • 【Linux学习笔记】线程的同步与互斥(一)
  • 【开题答辩全过程】以 基于Android的小区物业管理APP的设计与实现为例,包含答辩的问题和答案
  • 【数据结构】二叉树-图解广度优先搜索
  • 临汾市建设局网站wordpress hacker主题
  • 【机器学习入门】7.1 决策树 —— 像 “判断流程图” 一样做分类
  • 虚拟机可以做两个网站物流网站有哪些
  • C++ 模拟题 力扣495. 提莫攻击 题解 每日一题
  • Google Chrome 开发者工具
  • 微信公众号平台开发文档深圳网站建设模板乐云seo
  • GitHub 热榜项目 - 日榜(2025-10-12)
  • 结构化特征生成推进广度学习:2025年深度学习领域的重要突破
  • 达梦数据库全库透明加密(TDE)解决方案:实现静态数据高安全防护
  • 深圳模板网站多少钱政务中心网站建设方案
  • spring boot拦截器获取requestBody的巨坑
  • [2]python爬虫实践,爬取网易云音乐热歌榜排行版名称
  • 网站快速备案公司wordpress文章末尾加上相关文章
  • WebAssembly联调实践:Rust计算模块与Node.js后端的性能对比
  • 利用万网做网站建筑工程网下载
  • 麒麟系统开机启动
  • Redis-List