# egg-实战
https://eggjs.org/zh-cn/
# 创建项目
# 创建项目
npm i egg-init -g
egg-init egg-server
cd egg-server
npm i
# 启动项目
npm run dev
open localhost:7001
# 添加swagger-doc
添加Controller方法
// app/controller/user.js
const Controller = require('egg').Controller
/**
* @Controller 用户管理
*/
class UserController extends Controller {
constructor(ctx) {
super(ctx)
}
/**
* @summary 创建用户
* @description 创建用户,记录用户账户/密码/类型
* @router post /api/user
* @request body createUserRequest *body
* @response 200 baseResponse 创建成功
*/
async create() {
const { ctx } = this
ctx.body = 'user ctrl'
}
}
module.exports = UserController
contract
// app/contract/index.js
'use strict';
module.exports = {
baseRequest: {
id: { type: 'string', description: 'id 唯⼀键', required: true, example: '1' },
},
baseResponse: {
code: { type: 'integer', required: true, example: 0 },
data: { type: 'string', example: '请求成功' },
errorMessage: { type: 'string', example: '请求成功' },
},
};
// /app/contract/user.js
'use strict';
module.exports = {
createUserRequest: {
mobile: { type: 'string', required: true, description: '手机号', example: '18801731528', format: /^1[34578]\d{9}$/ },
password: { type: 'string', required: true, description: '密码', example: '111111' },
realName: { type: 'string', required: true, description: '姓名', example: 'Tom' },
},
};
# 添加SwaggerDoc功能
npm install egg-swagger-doc-feat -S
// config/plugin
swaggerdoc : {
enable: true,
package: 'egg-swagger-doc-feat',
}
// config.default.js
config.swaggerdoc = {
dirScanner: './app/controller',
apiInfo: {
title: '测试接口',
description: '测试接口 swagger-ui for egg',
version: '1.0.0',
},
schemes: [ 'http', 'https' ],
consumes: [ 'application/json' ],
produces: [ 'application/json' ],
enableSecurity: false,
// enableValidate: true,
routerMap: true,
enable: true,
};
http://localhost:7001/swagger-ui.html
http://localhost:7001/swagger-doc
# 增加异常处理中间件
// /middleware/error_handler.js
'use strict';
module.exports = (options, app) => {
return async function(ctx, next) {
try {
await next();
} catch (err) {
// 所有的异常都在 app 上触发⼀个 error 事件,框架会记录⼀条错误日志
app.emit('error', err, this);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
const error = status === 500 && app.config.env === 'prod' ? 'Internal Server Error' : err.message;
// 从 error 对象上读出各个属性,设置到响应中
ctx.body = {
code: status,
error,
};
if (status === 422) {
ctx.body.derail = err.errors;
}
ctx.status = 200;
}
};
};
// config.default.js
config.middleware = ['errorHandler']
# helper方法实现统⼀响应格式
Helper 函数用来提供⼀些实用的 utility 函数。
它的作用在于我们可以将⼀些常用的动作抽离在 helper.js 里面成为⼀个独⽴的函数,这样可以用JavaScript 来写复杂的逻辑,避免逻辑分散各处。另外还有⼀个好处是 Helper 这样⼀个简单的函数,可以让我们更容易编写测试用例。
框架内置了⼀些常用的 Helper 函数。我们也可以编写自定义的 Helper 函数。
'use strict';
const moment = require('moment');
// 格式化时间
exports.formatTime = time => moment(time).format('YYYY-MM-DD HH:mm:ss');
// 处理成功响应
exports.success = ({ ctx, res = null, msg = '请求成功' }) => {
ctx.body = {
code: 0,
data: res,
msg,
};
ctx.status = 200;
};
# Validate检查参数
npm i egg-validate -S
// config/plugin.js
validate: {
enable: true,
package: 'egg-validate',
},
async create() {
const { ctx, service } = this
// 校验参数
ctx.validate(ctx.rule.createUserRequest)
}
# 添加Model层
npm install egg-mongoose -S
// plugin.js
mongoose : {
enable: true,
package: 'egg-mongoose',
},
config.mongoose = {
url: 'mongodb://127.0.0.1:27017/egg_x',
options: {
// useMongoClient: true,
autoReconnect: true,
reconnectTries: Number.MAX_VALUE,
bufferMaxEntries: 0,
},
};
// model/user.js
'use strict';
module.exports = app => {
const mongoose = app.mongoose;
const UserSchema = new mongoose.Schema({
mobile: { type: String, unique: true, required: true },
password: { type: String, required: true },
realName: { type: String, required: true },
avatar: { type: String, default:
'https://1.gravatar.com/avatar/a3e54af3cb6e157e496ae430aed4f4a3?s=96&d=mm' },
extra: { type: mongoose.Schema.Types.Mixed },
createdAt: { type: Date, default: Date.now },
});
return mongoose.model('User', UserSchema);
};
# 添加Service层
npm install egg-bcrypt -S
bcrypt : {
enable: true,
package: 'egg-bcrypt'
}
// service/user.js
const Service = require('egg').Service
class UserService extends Service {
/**
* 创建用户
* @param {*} payload
*/
async create(payload) {
const { ctx } = this
payload.password = await this.ctx.genHash(payload.password)
return ctx.model.User.create(payload)
}
}
module.exports = UserService
# Controller调用
/**
* @summary 创建用户
* @description 创建用户,记录用户账户/密码/类型
* @router post /api/user
* @request body createUserRequest *body
* @response 200 baseResponse 创建成功
*/
async create() {
const { ctx, service } = this;
// 校验参数
ctx.validate(ctx.rule.createUserRequest);
// 组装参数
const payload = ctx.request.body || {};
// 调用 Service 进行业务处理
const res = await service.user.create(payload);
// 设置响应内容和响应状态码
ctx.helper.success({ ctx, res });
}
# 通过生命周期初始化数据
https://eggjs.org/en/basics/app-start.html#mobileAside
// /app.js
'use strict';
/**
* 全局定义
* @param app
*/
class AppBootHook {
constructor(app) {
this.app = app;
app.root_path = __dirname;
}
configWillLoad() {
// Ready to call configDidLoad,
// Config, plugin files are referred,
// this is the last chance to modify the config.
}
configDidLoad() {
// Config, plugin files have been loaded.
}
async didLoad() {
// All files have loaded, start plugin here.
}
async willReady() {
// All plugins have started, can do some thing before app ready
}
async didReady() {
// Worker is ready, can do some things
// don't need to block the app boot.
console.log('========Init Data=========');
const ctx = await this.app.createAnonymousContext();
await ctx.model.User.remove();
await ctx.service.user.create({
mobile: '18911111111',
password: '111111',
realName: 'zhao',
});
}
async serverDidReady() {
}
async beforeClose() {
// Do some thing before app close.
}
}
module.exports = AppBootHook;
# 用户鉴权模块
注册jwt模块
npm i egg-jwt -S
// plugin.js
jwt: {
enable: true,
package: 'egg-jwt',
}
// config.default.js
config.jwt = {
secret: 'Great4-M',
enable: true, // default is false
match: /^\/api/, // optional
}
# Service层
'use strict';
const { Service } = require('egg');
class ActionTokenService extends Service {
async apply(_id) {
const { ctx } = this;
return ctx.app.jwt.sign({
data: {
_id,
},
exp: Math.floor(Date.now() / 1000 + (60 * 60 * 7)),
}, ctx.app.config.jwt.secret);
}
}
module.exports = ActionTokenService;
// service/userAccess.js
'use strict';
const { Service } = require('egg');
class UserAccessService extends Service {
async login(payload) {
const { ctx, service } = this;
const user = await service.user.findByMobile(payload.mobile);
console.log('88888mobile' + payload.moblie);
if (!user) {
ctx.throw(404, 'user not found');
}
const verifyPsw = await ctx.compare(payload.password, user.password);
if (!verifyPsw) {
ctx.throw(404, 'user password is error');
}
// 生成Token令牌
return { token: await service.actionToken.apply(user._id) };
}
async logout() {
}
async current() {
const { ctx, service } = this;
// ctx.state.user 可以提取到JWT编码的data
const _id = ctx.state.user.data._id;
const user = await service.user.find(_id);
if (!user) {
ctx.throw(404, 'user is not found');
}
user.password = 'How old are you?';
return user;
}
}
module.exports = UserAccessService;
# Contract层
'use strict';
// app/contract/userAccess.js
module.exports = {
loginRequest: {
mobile: { type: 'string', required: true, description: '手机号', example: '18801731528', format: /^1[34578]\d{9}$/ },
password: { type: 'string', required: true, description: '密码', example: '111111' },
},
};
# Controller层
// controller/userAccess.js
'use strict';
const Controller = require('egg').Controller;
/**
* @Controller 用户鉴权
*/
class UserAccessController extends Controller {
/**
* @summary 用户登入
* @description 用户登入
* @router post /auth/jwt/login
* @request body loginRequest *body
* @response 200 baseResponse 创建成功
*/
async login() {
const { ctx, service } = this;
// 校验参数
ctx.validate(ctx.rule.loginRequest);
// 组装参数
const payload = ctx.request.body || {};
// 调用 Service 进行业务处理
const res = await service.userAccess.login(payload);
// 设置响应内容和响应状态码
ctx.helper.success({ ctx, res });
}
/**
* @summary 用户登出
* @description 用户登出
* @router post /auth/jwt/logout
* @request body loginRequest *body
* @response 200 baseResponse 创建成功
*/
async logout() {
const { ctx, service } = this;
// 调用 Service 进行业务处理
await service.userAccess.logout();
// 设置响应内容和响应状态码
ctx.helper.success({ ctx });
}
}
module.exports = UserAccessController;
# 文件上传
npm i await-stream-ready stream-wormhole image-downloader -S
controller
'use strict';
const fs = require('fs');
const path = require('path');
const Controller = require('egg').Controller;
const awaitWriteStream = require('await-stream-ready').write;
const sendToWormhole = require('stream-wormhole');
const dowload = require('image-downloader');
/**
* @controller 上传
*/
class UploadController extends Controller {
/**
* @summary 上传单个文件
* @description 上传单个文件
* @router post /api/upload/single
*/
async create() {
const { ctx } = this;
// 要通过 ctx.getFileStream 便捷的获取到用户上传的文件,需要满⾜两个条件:
// 只支持上传⼀个文件。
// 上传文件必须在所有其他的 fields 后面,否则在拿到文件流时可能还获取不到fields。
const stream = await ctx.getFileStream();
// 所有表单字段都能通过 `stream.fields` 获取到
const filename = path.basename(stream.filename); // 文件名称
const extname = path.extname(stream.filename).toLowerCase(); // 文件扩展名称
const uuid = (Math.random() * 999999).toFixed();
// 组装参数 stream
const target = path.join(this.config.baseDir, 'app/public/uploads', `${uuid}${extname}`);
const writeStream = fs.createWriteStream(target);
// 文件处理,上传到云存储等等
try {
await awaitWriteStream(stream.pipe(writeStream));
} catch (error) {
// 必须将上传的文件流消费掉,要不然浏览器响应会卡死
await sendToWormhole(writeStream);
throw error;
}
// 调用 Service 进行业务处理
// 设置响应内容和响应状态码
ctx.helper.success({ ctx });
}
}
module.exports = UploadController;