egg 文档 https://eggjs.org/zh-cn/intro/index.html
Nunjucks https://adonis-china.org/docs/3.2/templating
Mongoose 5.0 https://mongoosedoc.top/docs/index.html
本项目 github : https://github.com/guxiansheng1991/blog-egg.git
一.目标
学习阿里开源的egg这个node框架,熟悉node的相关技术
二.项目搭建及功能
(1) 搭建使用egg-init官方脚手架搭建的,简单方便
(2) 实现功能
- 博客crud
- 支持博客分类
- 登录注册
- 评论功能可选(还没做)
- 点赞功能可选(还没做)
(3) 使用技术
- 框架使用egg
- 数据库 MongoDB
- 模板view egg-view-nunjucks
三.项目开发
(1) 配置
配置统一在/root_path/config/config.default.js中
配置文件中可以配置模板文件,数据库,session,中间件等等,比较丰富
'use strict';
const path = require('path');
module.exports = appInfo => {
const config = exports = {};
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1534300430738_2489';
// add your config here
config.middleware = [ 'authentication' ];
// config.middleware = [];
config.authentication = {
excepts: [ '/user/login' ],
};
// view模板配置
config.view = {
// 配置view目录,可以多个,只需要在root数组中多个即可
root: [
path.join(appInfo.baseDir, 'app/view'),
].join(','),
// 配置模板引擎的后缀信息
mapping: {
'.nj': 'nunjucks',
},
// 默认的模板引擎
defaultViewEngine: 'nunjucks',
// 默认的view文件渲染后缀,配置此项可以在render函数中省略后缀名
defaultExtension: '.nj',
};
// MongoDB配置
config.mongoose = {
client: {
url: 'mongodb://127.0.0.1/blogegg',
options: {},
},
};
// session设置
config.session = {
renew: true,
};
return config;
};
(2) 数据库设计
数据库使用MongoDB,设计文件如下
以Blog为例, 这里将文档之间的对应关系采用引用存储,类似于Mysql的外键
// /root_path/app/model/blog.js
'use strict';
module.exports = app => {
const mongoose = app.mongoose;
const Schema = mongoose.Schema;
const BlogSchema = new Schema({
title: { type: String },
content: { type: String },
category: { type: String },
userId: { type: String },
});
return mongoose.model('Blog', BlogSchema);
};
(2) 鉴权
需要使用中间件,我是自己定义的中间件,配置如下:
鉴权行为是放在 await next(); 后,不知道对还是不对,确实有效
// /root_path/config/config.default.js
// add your config here
config.middleware = [ 'authentication' ];
config.authentication = {
excepts: [ '/user/login' ],
};
// /root_path/app/middleware/authentication.js
'use strict';
module.exports = options => {
return async function authentication(ctx, next) {
await next();
const user = ctx.session.user;
let res = false;
if (!user) {
const routeStr = ctx.request.url;
for (let i = 0; i < options.excepts.length; i++) {
if (options.excepts[i] === routeStr) {
res = true;
break;
}
}
} else {
res = true;
}
if (!res) {
ctx.redirect('/user/login');
}
};
};
(3) crud
这里以Blog为例
这里程序主要采用类似于java的MVC架构, 类似于Spring MVC,
controller 调用 service
service 调用 dao(这里的dao就是利用mongoose操作MongoDB)
路由文件(遵循RESTful 风格)
// /root_path/app/router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/blog/list/:page', controller.blog.list);
router.get('/blog/add', controller.blog.add);
router.post('/blog/addAction', controller.blog.addAction);
router.get('/blog/detail/:id', controller.blog.detail);
router.get('/blog/edit/:id/:currentPage', controller.blog.edit);
router.post('/blog/editAction/:currentPage', controller.blog.editAction);
router.get('/blog/delete/:id/:currentPage', controller.blog.delete);
};
controller文件(这里对鉴定参数空值不知道怎么优雅的实现)
'use strict';
const Controller = require('egg').Controller;
// const verify = require('../util/verify');
class BlogController extends Controller {
async list() {
const page = this.ctx.params.page;
const pageSize = 10;
const res = await this.ctx.service.blog.list(page, pageSize);
await this.ctx.render('blogs', res);
}
async add() {
const data = {
msg: '',
title: '',
content: '',
categoryId: '4',
categoryList: [],
};
const categoryList = await this.ctx.service.category.list();
if (categoryList) {
data.categoryList = categoryList;
} else {
const data = {
msg: '获取类别列表失败, 请重试!',
};
await this.ctx.render('error', data);
}
await this.ctx.render('blogAdd', data);
}
async addAction() {
const title = this.ctx.request.body.title;
const content = this.ctx.request.body.content;
const category = this.ctx.request.body.category;
let msg = '';
if (!title) {
msg += 'title不能为空值, ';
}
if (!content) {
msg += 'content不能为空值, ';
}
if (!category) {
msg += 'category不能为空值, ';
}
if (!msg) {
const res = await this.ctx.service.blog.add(title, content, category);
if (res) {
this.ctx.redirect('/blog/list/1');
} else {
const data = {
msg: '新增失败, 请重试',
title,
content,
category,
};
await this.ctx.render('/blog/add', data);
}
} else {
const data = {
message: msg,
title,
content,
category,
};
await this.ctx.render('/blog/add', data);
}
}
async edit() {
const id = this.ctx.params.id;
// const currentPage = this.ctx.params.currentPage;
const res = await this.ctx.service.blog.findOne(id);
const data = {
msg: '',
data: res,
categoryList: [],
};
const categoryList = await this.ctx.service.category.list();
if (categoryList) {
data.categoryList = categoryList;
} else {
const data = {
msg: '获取类别列表失败, 请重试!',
};
await this.ctx.render('error', data);
}
await this.ctx.render('blogEdit', data);
}
async editAction() {
const id = this.ctx.request.body.id;
const currentPage = this.ctx.params.currentPage;
const update = {
title: this.ctx.request.body.title,
content: this.ctx.request.body.content,
category: this.ctx.request.body.category,
};
const res = await this.ctx.service.blog.editAction(id, update);
if (res) {
this.ctx.redirect(`/blog/list/${currentPage}`);
} else {
this.ctx.redirect(`/blog/edit/${id}/${currentPage}`);
}
}
async delete() {
const id = this.ctx.params.id;
const currentPage = this.ctx.params.currentPage;
await this.ctx.service.blog.delete(id);
this.ctx.redirect(`/blog/list/${currentPage}`);
}
async detail() {
const id = this.ctx.params.id;
const res = await this.ctx.service.blog.findOne(id);
let category = null;
const errorData = {
msg: '',
};
if (res || res.category) {
category = await this.ctx.service.category.findOne(res.category);
} else {
errorData.msg = '获取该博客信息失败,请重试!';
await this.ctx.render('error', errorData);
}
const data = {
msg: '',
data: res,
categoryName: '',
};
if (category) {
data.categoryName = category.name;
}
await this.ctx.render('blogDetail', data);
}
}
module.exports = BlogController;
service文件
'use strict';
const Service = require('egg').Service;
class BlogService extends Service {
async list(page, pageSize) {
const user = this.ctx.session.user;
let data = null;
try {
const res = await Promise.all([
this.ctx.model.Blog.find({
userId: user._id,
}).skip(pageSize * (page - 1)).limit(pageSize),
this.ctx.model.Blog.find().count(),
]);
data = {
list: res[0],
totalPage: Math.ceil(res[1] / pageSize),
currentPage: page,
};
} catch (error) {
data = null;
console.error('blog list error', error);
}
return data;
}
async findOne(id) {
const user = this.ctx.session.user;
let data = null;
try {
const res = await this.ctx.model.Blog.findOne({
_id: id,
userId: user._id,
});
data = res;
} catch (error) {
data = null;
console.error('blog findOne error', error);
}
return data;
}
async add(title, content, category) {
let res = null;
const user = this.ctx.session.user;
try {
res = this.ctx.model.Blog.create({
title,
content,
category,
userId: user._id,
});
} catch (error) {
res = null;
console.error('error, 创建失败', error);
}
return res;
}
async editAction(id, update) {
let res = null;
try {
res = await this.ctx.model.Blog.findByIdAndUpdate(id, update);
} catch (error) {
res = null;
console.error('error, 更新微博失败', error);
}
return res;
}
async delete(id) {
let res = null;
try {
res = await this.ctx.model.Blog.remove({
_id: id,
});
} catch (error) {
res = {
n: 0,
ok: 0,
};
console.error('error, 删除失败', error);
}
return res;
}
}
module.exports = BlogService;
四.总结
虽然做的很丑,但是主要目的是熟悉技术
还有很多需要做的,等待完善,例如数据库查询优化,三方登录,安全性等
本项目 github : https://github.com/guxiansheng1991/blog-egg.git