egg.js学习笔记(上)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: egg.js学习笔记

安装egg


我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):


mkdir egg-example && cd egg-example
npm init egg --type=simple
npm i


启动项目:


npm run dev
open http://localhost:7001


目录结构


egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app(-----------核心------------)
|   ├── router.js(路由)
│   ├── controller(控制器)
│   |   └── home.js
│   ├── service (模型)
│   |   └── user.js
│   ├── middleware (中间件)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (静态资源)
│   |   └── reset.css
│   ├── view (模板视图)
│   |   └── home.tpl
│   └── extend (扩展)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js


路由相关


1. get传值


// router.js
router.get('/admin/:id', controller.admin.index);
// controller
async index(ctx) {
    // 获取路由get传值参数(路由:id)
    ctx.params;
    // 获取url的问号get传值参数
    ctx.query;
}


2. 4种配置方法


router.verb('path-match', app.controller.action);
router.verb('router-name', 'path-match', app.controller.action);// 第一个参数可以给name
router.verb('path-match', middleware1, ..., middlewareN, app.controller.action);
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);


重定向


1. ctx


async index() {
    this.ctx.status = 301; // 把重定向改为301
    this.ctx.redirect('/admin/add'); // 默认临时重定向 302
}


2. 路由重定向


app.router.redirect('/', '/home/index', 302);


3.路由分组


// app/router.js
module.exports = app => {
  require('./router/news')(app);
  require('./router/admin')(app);
};
// app/router/news.js
module.exports = app => {
  app.router.get('/news/list', app.controller.news.list);
  app.router.get('/news/detail', app.controller.news.detail);
};
// app/router/admin.js
module.exports = app => {
  app.router.get('/admin/user', app.controller.admin.user);
  app.router.get('/admin/log', app.controller.admin.log);
};


控制器


自定义 Controller 基类


// app/core/base_controller.js
const { Controller } = require('egg');
class BaseController extends Controller {
  get user() {
    return this.ctx.session.user;
  }
  success(data) {
    this.ctx.body = {
      success: true,
      data,
    };
  }
  notFound(msg) {
    msg = msg || 'not found';
    this.ctx.throw(404, msg);
  }
}
module.exports = BaseController;


此时在编写应用的 Controller 时,可以继承 BaseController,直接使用基类上的方法:


//app/controller/post.js
const Controller = require('../core/base_controller');
class PostController extends Controller {
  async list() {
    const posts = await this.service.listByUser(this.user);
    this.success(posts);
  }
}


模板引擎


1. 安装和使用ejs


(1)安装:


npm i egg-view-ejs --save


(2)配置:/config


config/config.default.js


module.exports = appInfo => {
    ...
    config.view = {
        mapping: {
            '.html': 'ejs',
        },
    };
  ...
};


config/plugin.js


module.exports = {
  // 配置ejs
    ejs: {
        enable: true,
        package: 'egg-view-ejs',
    }
};


(3)使用


app/controller


 async index() {
     const { ctx } = this;
     // 渲染变量
     let msg = "测试内容";
     let list = [1, 2, 3, 4, 5, 6];
     // 渲染模板(render需要加await)
     await ctx.render('index', {
         msg,
         list
     });
 }


app/view/index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!--渲染变量-->
    <%=msg%>
        <ul>
            <% for(var i=0; i < list.length; i++){ %>
                <li>
                    <%=list[i]%>
                </li>
                <% } %>
        </ul>
    <!--加载 app/public 下的资源文件-->
        <img src="/public/images/find.png">
</body>
</html>


服务(模型)


控制器调用 home 模型的 ceshi 方法


await this.service.home.ceshi();


模型之间相互调用(同上)


模型和数据库


配置和创建迁移文件


配置


  1. 安装并配置egg-sequelize插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和mysql2模块:


npm install --save egg-sequelize mysql2


  1. 在config/plugin.js中引入 egg-sequelize 插件


exports.sequelize = {
  enable: true,
  package: 'egg-sequelize',
};


  1. 在config/config.default.js


config.sequelize = {
    dialect:  'mysql',
    host:  '127.0.0.1',
    username: 'root',
    password:  'root',
    port:  3306,
    database:  'friends',
    // 中国时区
    timezone:  '+08:00',
    define: {
        // 取消数据表名复数
        freezeTableName: true,
        // 自动写入时间戳 created_at updated_at
        timestamps: true,
        // 字段生成软删除时间戳 deleted_at
        paranoid: true,
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        deletedAt: 'deleted_at',
        // 所有驼峰命名格式化
        underscored: true
    }
};


  1. sequelize 提供了sequelize-cli工具来实现Migrations,我们也可以在 egg 项目中引入 sequelize-cli。


npm install --save-dev sequelize-cli


  1. egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在database目录下,所以我们在项目根目录下新建一个.sequelizerc配置文件:


'use strict';
const path = require('path');
module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};


  1. 初始化 Migrations 配置文件和目录


npx sequelize init:config
npx sequelize init:migrations
// npx sequelize init:models


  1. 行完后会生成database/config.json文件和database/migrations目录,我们修改一下database/config.json中的内容,将其改成我们项目中使用的数据库配置:


{
  "development": {
    "username": "root",
    "password": null,
    "database": "test",
    "host": "127.0.0.1",
    "dialect": "mysql",
    "timezone": "+08:00"
  }
}


  1. 创建数据库


npx sequelize db:create


创建数据迁移表


npx sequelize migration:generate --name=init-users


1.执行完命令后,会在database / migrations / 目录下生成数据表迁移文件,然后定义


'use strict';
module.exports = {
    up: async (queryInterface, Sequelize) => {
        const { INTEGER, STRING, DATE, ENUM } = Sequelize;
        // 创建表
        await queryInterface.createTable('users', {
            id: { type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true },
            username: { type: STRING(30), allowNull: false, defaultValue: '', comment: '用户名称', unique: true},
            email: { type: STRING(160), allowNull: false,  defaultValue: '', comment: '用户邮箱', unique: true },
            password: { type: STRING(200), allowNull: false, defaultValue: '' },
            avatar_url: { type: STRING(200), allowNull: true, defaultValue: '' },
            mobile: { type: STRING(20), allowNull: false, defaultValue: '', comment: '用户手机', unique: true },
            prifix: { type: STRING(32), allowNull: false, defaultValue: '' },
            abstract: { type: STRING(255), allowNull: true, defaultValue: '' },
            role_id:{
                type: INTEGER,
                //  定义外键(重要)
                references: {
                    model: 'users', // 对应表名称(数据表名称)
                    key: 'id' // 对应表的主键
                },
                onUpdate: 'restrict', // 更新时操作
                onDelete: 'cascade'  // 删除时操作
            },
            gender: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '男', comment: '用户性别'},
            created_at: DATE,
            updated_at: DATE
        }, { engine: 'MYISAM' });
        // 添加索引
        queryInterface.addIndex('users', ['gender']);
        // 添加唯一索引
        queryInterface.addIndex('users', {
            name: "name", // 索引名称
            unique: true, // 唯一索引
            fields: ['name'] // 索引对应字段
        });
    },
    down: async queryInterface => {
        await queryInterface.dropTable('users')
    }
};


  • 执行 migrate 进行数据库变更


# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all


已创建新增字段


1.创建迁移文件:


npx sequelize migration:generate --name=user-addcolumn


2.执行完命令后,会在database / migrations / 目录下生成数据表迁移文件,然后定义


'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction((t) => {
      return Promise.all([
          queryInterface.addColumn('user', 'role_id', {
              type: Sequelize.INTEGER
          }, { transaction: t }),
          queryInterface.addColumn('user', 'ceshi', {
              type: Sequelize.STRING,
          }, { transaction: t })
      ])
    })
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction((t) => {
      return Promise.all([
          queryInterface.removeColumn('user', 'role_id', { transaction: t }),
          queryInterface.removeColumn('user', 'ceshi', { transaction: t })
      ])
    })
  }
};


3.执行 migrate 进行数据库变更


npx sequelize db:migrate


创建模型


// app / model / user.js
'use strict';
module.exports = app => {
  const { STRING, INTEGER, DATE } = app.Sequelize;
  // 配置(重要:一定要配置详细,一定要!!!)
  const User = app.model.define('user', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: STRING(30),
    age: INTEGER,
    created_at: DATE,
    updated_at: DATE,
  },{
    timestamps: true, // 是否自动写入时间戳
    tableName: 'users', // 自定义数据表名称
 });
  return User;
};


这个 Model 就可以在 Controller 和 Service 中通过 app.model.User 或者 ctx.model.User 访问到了,例如我们编写 app/controller/users.js:


// app/controller/users.js
const Controller = require('egg').Controller;
function toInt(str) {
  if (typeof str === 'number') return str;
  if (!str) return str;
  return parseInt(str, 10) || 0;
}
class UserController extends Controller {
  async index() {
    const ctx = this.ctx;
    const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
    ctx.body = await ctx.model.User.findAll(query);
  }
  async show() {
    const ctx = this.ctx;
    ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
  }
  async create() {
    const ctx = this.ctx;
    const { name, age } = ctx.request.body;
    const user = await ctx.model.User.create({ name, age });
    ctx.status = 201;
    ctx.body = user;
  }
  async update() {
    const ctx = this.ctx;
    const id = toInt(ctx.params.id);
    const user = await ctx.model.User.findByPk(id);
    if (!user) {
      ctx.status = 404;
      return;
    }
    const { name, age } = ctx.request.body;
    await user.update({ name, age });
    ctx.body = user;
  }
  async destroy() {
    const ctx = this.ctx;
    const id = toInt(ctx.params.id);
    const user = await ctx.model.User.findByPk(id);
    if (!user) {
      ctx.status = 404;
      return;
    }
    await user.destroy();
    ctx.status = 200;
  }
}
module.exports = UserController;


最后我们将这个 controller 挂载到路由上:


// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.resources('users', '/users', controller.users);
};


针对 users 表的 CURD 操作的接口就开发完了


模型其他参数


// 配置(重要)
  const User = app.model.define('user', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: STRING(30),
    age: INTEGER,
    created_at: DATE,
    updated_at: DATE,
  },{
    // 自定义表名
      'freezeTableName': true,
      'tableName': 'xyz_users',
      // 是否需要增加createdAt、updatedAt、deletedAt字段
      'timestamps': true,
      // 不需要createdAt字段
      'createdAt': false,
      // 将updatedAt字段改个名
      'updatedAt': 'utime',
      // 将deletedAt字段改名
      // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
      'deletedAt': 'dtime',
      'paranoid': true,
 });


sequelize 命令


image.png


外键约束(重要)


// 迁移文件
queryInterface.addConstraint('tableName', ['user_id'], {
    type: 'foreign key',
    name: 'user_id',
    references: { //Required field
        table: 'users',
        field: 'id'
    },
    onDelete: 'cascade',
    onUpdate: 'cascade'
});


创建第一个种子


假设我们希望在默认情况下将一些数据插入到几个表中. 如果我们跟进前面的例子,我们可以考虑为 User 表创建演示用户.


要管理所有数据迁移,你可以使用 seeders. 种子文件是数据的一些变化,可用于使用样本数据或测试数据填充数据库表.


让我们创建一个种子文件,它会将一个演示用户添加到我们的 User 表中.


npx sequelize seed:generate --name demo-user


这个命令将会在 seeders 文件夹中创建一个种子文件.文件名看起来像是 XXXXXXXXXXXXXX-demo-user.js,它遵循相同的 up/down 语义,如迁移文件.

现在我们应该编辑这个文件,将演示用户插入User表.


'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Users', [{
        firstName: 'John',
        lastName: 'Doe',
        email: 'demo@demo.com',
        createdAt: new Date(),
        updatedAt: new Date()
      }], {});
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('Users', null, {});
  }
};


运行种子


在上一步中,你创建了一个种子文件. 但它还没有保存到数据库. 为此,我们需要运行一个简单的命令.


npx sequelize db:seed:all


这将执行该种子文件,你将有一个演示用户插入 User 表.


注意: _ seeders 执行不会存储在任何使用 SequelizeMeta 表的迁移的地方. 如果你想覆盖这个,请阅读 存储 部分_


撤销种子


Seeders 如果使用了任何存储那么就可以被撤消. 有两个可用的命令


如果你想撤消最近的种子


npx sequelize db:seed:undo


如果你想撤消特定的种子


npx sequelize db:seed:undo --seed name-of-seed-as-in-data


如果你想撤消所有的种子


npx sequelize db:seed:undo:all


关联操作


一对一


模型层:


// 一个用户对应一个用户资料
// app/model/user.js
module.exports = app => {
    const { STRING, INTEGER, DATE } = app.Sequelize;
    const User = app.model.define('user', {
        id: { type: INTEGER, primaryKey: true, autoIncrement: true },
        name: STRING(30),
        age: INTEGER,
        created_at: DATE,
        updated_at: DATE,
    });
    // 关联关系
    User.associate = function(models) {
        // 关联用户资料 一对一
        User.hasOne(app.model.Userinfo);
    }
    return User;
};
// app/model/userinfo.js
module.exports = app => {
    const { STRING, INTEGER, DATE } = app.Sequelize;
    const userinfo = app.model.define('userinfo', {
        nickname: STRING,
        user_id: INTEGER
    }, {});
    // 关联用户表
    userinfo.associate = function(models) {
        app.model.Userinfo.belongsTo(app.model.User);
    };
    return userinfo;
};


控制器调用:


// app/controller/users.js
// 显示单条
async show() {
    // 根据主键查询 查询一条用findOne
    this.ctx.body = await this.ctx.model.User.findOne({
        // 主表查询字段限制
        attributes:['name'],
        // 关联查询
        include: [{
            // 需要查询的模型
            model: this.app.model.Userinfo,
            // 副表查询的字段
            attributes: ['nickname']
        }],
        // 主表条件
        where: {
            id: 3
        }
    });
}


一对多


class City extends Model {}
City.init({ countryCode: Sequelize.STRING }, { sequelize, modelName: 'city' });
class Country extends Model {}
Country.init({ isoCode: Sequelize.STRING }, { sequelize, modelName: 'country' });
// 在这里,我们可以根据国家代码连接国家和城市
Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});


多对多


User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })


关联常用操作


// 获取关联模型对象,n对一不需要加s
let userinfo = await user.getUserinfo();
// n对多需要加s
await user.getPosts({
    attributes: ['title'],
    where: {
        id: 3
    }
});
// 关联操作
// 1:用户创建文章(一对多)
await this.ctx.model.Post.create({
    title: "第一篇文章",
    user_id: user.id
});
// 2.获取当前用户所有文章
await user.getPosts();
await user.getPosts({
    attributes: ['id'],
    where:{
        title:"测试"
    }
});
// 3:用户删除文章(一对多)
// (1) 获取当前用户的所有文章
let posts = await user.getPosts({
    attributes: ['id']
});
posts = posts.map(v => v.id);
await this.ctx.model.Post.destroy({
    where: {
        id: posts
    }
});
// 场景三:用户关注话题(多对多)
await this.ctx.model.TopicUser.bulkCreate([{
    user_id: user.id,
    topic_id: 1
},{
    user_id: user.id,
    topic_id: 2
}]);
// 用户关注话题(多对多)
await this.ctx.model.TopicUser.destroy({
    where: {
        user_id: user.id,
        topic_id: [1, 2]
    }
});


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
JavaScript 前端开发 API
Vue学习笔记3:对比纯JavaScript和Vue实现数据更新的实时视图显示
Vue学习笔记3:对比纯JavaScript和Vue实现数据更新的实时视图显示
|
1月前
|
JavaScript 前端开发
【干货分享】JavaScript学习笔记分享
【干货分享】JavaScript学习笔记分享
57 0
|
2月前
|
Web App开发 前端开发 JavaScript
HTML/CSS/JS学习笔记 Day3(HTML--网页标签 下)
HTML/CSS/JS学习笔记 Day3(HTML--网页标签 下)
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的云的学习笔记系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的云的学习笔记系统附带文章源码部署视频讲解等
41 0
|
6月前
|
移动开发 JavaScript 前端开发
webgl学习笔记3_javascript的HTML DOM
webgl学习笔记3_javascript的HTML DOM
65 0
webgl学习笔记3_javascript的HTML DOM
|
6月前
|
JavaScript 前端开发 Java
webgl学习笔记2_javascript基础快速学习
webgl学习笔记2_javascript基础快速学习
51 0
|
6月前
|
前端开发 JavaScript API
JavaScript学习笔记(一)promise与async
JavaScript学习笔记(一)promise与async
|
6月前
|
存储 JavaScript
【ES6系列第二篇】适用JS初学者的Set对象和Map对象学习笔记
【ES6系列第二篇】适用JS初学者的Set对象和Map对象学习笔记
49 0
|
存储 JavaScript 前端开发
【js】函数概述学习笔记(8)
【js】函数概述学习笔记(8)
43 0
|
存储 JavaScript
【js】数组学习笔记(7-2)
【js】数组学习笔记(7-2)
66 0
下一篇
无影云桌面