基于Koa和Mysql的Web Server框架

最近在给一个自定义的项目写接口,第一次使用node.js实现,翻了几天的书和资料,本来计划使用express + mongodb实现的,后来发现了Koa,good boy,数据库最终选定的还是mysql,老搭子嘛,搭建了一个基于Koa + Mysql框架:

Koa

koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

总体框架

总体框架

项目主要分为configapp
config:配置文件
app:遵循MVC架构,应用文件

第三方包

第三方包

sequelizenode下的ORM框架,很好很强大,下面的实例中会进行展示。

主要配置

Server.js


/**
 * Created by vslimit on 2017/9/8.
 */
'use strict';
require('dotenv').config();
const Koa =require('koa');
const app = new Koa();
const fs = require('fs');
const join = require('path').join;
const bodyParser = require('koa-bodyparser');
const model = join(__dirname, 'app/model');
var Router = require('koa-router');
var router = new Router();
const rest = require('./config/rest');

const config = require('./config');

const port = process.env.PORT || 3000;

module.exports = app;
app.use(bodyParser());
// app.use(async ctx => {
//     ctx.body = ctx.request.body;
// });
app.use(rest.restify());
app.use(router.routes()).use(router.allowedMethods());

fs.readdirSync(model)
    .filter(file => ~file.search(/^[^\.].*\.js$/))
    .forEach(file => require(join(model, file)));


// let files = fs.readdirSync(model);


require('./config/routes')(router);

listen();

module.exports = model;

function listen() {
    if (router.get('env') === 'test') return;
    app.listen(port);
    console.log('Express app started on port ' + port);
}


开发实例

我们应用此框架开发一个功能注册、登录、加载功能的Restful接口,先看下model

User

/**
 * Created by vslimit on 2017/9/10.
 */
const db = require('../util/db');
const crypto = require('crypto');
const uuid = require('node-uuid');

const User = db.defineModel('users', {
    name: {
        type: db.STRING(),
        allowNull: true
    },
    email: {
        type: db.STRING(),
        unique: true,
        allowNull: true
    },
    password: db.VIRTUAL(),
    mobile: {
        type: db.STRING(),
        unique: true
    },
    provider: db.STRING(),
    hashed_password: db.STRING(),
    salt: db.STRING(),
    auth_token: {
        type: db.STRING(),
        allowNull: true
    },
    access_token: {
        type: db.STRING(),
        allowNull: true
    }

});

User.beforeValidate(function (user) {
    if (user.isNewRecord) {
        let salt = this.methods.makeSalt();
        user.set('salt', salt);
        user.set('hashed_password', this.methods.encryptPassword(user.password, salt));
    }
});

User.afterCreate(function (user) {
    console.log(JSON.stringify(user));
    user.access_token = this.methods.makeAccessToken(user.id);
    console.log(user.access_token);
    user.save();
});

User.methods = {
    authenticate: function (password, salt, hashed_password) {
        return this.encryptPassword(password, salt) === hashed_password;
    },

    /**
     * Make salt
     *
     * @return {String}
     * @api public
     */

    makeSalt: function () {
        return Math.round((new Date().valueOf() * Math.random())) + '';
    },

    /**
     * Encrypt password
     *
     * @param {String} password
     * @return {String}
     * @api public
     */

    encryptPassword: function (password, salt) {
        if (!password) return '';
        try {
            return crypto
                .createHmac('sha1', salt)
                .update(password)
                .digest('hex');
        } catch (err) {
            return '';
        }
    },

    makeAccessToken: function (id) {
        return crypto
            .createHmac('sha1', id.toString())
            .update(uuid.v4() + Date.now())
            .digest('hex');
    },

   load: function (condition) {
        return User.findOne({where: condition});
    },

    count: function (condition) {
        return User.count({where: condition});
    },
};

module.exports = User;

然后是controller

users

/**
 * Created by vslimit on 2017/9/12.
 */
'use strict';
const User = require('../model/User');
const ApiResult = require('../../config/rest').APIResult;

/**
 *  Create user
 */

exports.create = async(ctx, next) => {
    let mobile = ctx.request.body.mobile;
    let password = ctx.request.body.password;

    console.log(mobile);
    console.log(password);
    if (!mobile || !password) {
        ctx.rest(ApiResult("", -102, "手机号或密码不能为空"));
    } else {
        let count = await User.methods.count({mobile: mobile});
        console.log(count);
        if (count > 0) {
            ctx.rest(ApiResult("", -101, "手机号已存在"));
        } else {
            let user = await User.create({
                mobile: mobile,
                password: password,
                provider: 'local'
            });
            ctx.rest(ApiResult(user.access_token));
        }
    }

};

exports.login = async(ctx, next) => {
    let mobile = ctx.request.body.mobile;
    let password = ctx.request.body.password;
    if (!mobile || !password) {
        ctx.rest(ApiResult("", -102, "手机号或密码不能为空"));
    } else {
        let user = await User.methods.load({mobile: mobile});
        if (user) {
            if (User.methods.authenticate(password, user.salt, user.hashed_password)) {
                ctx.rest(ApiResult({
                    name: user.name,
                    mobile: user.mobile,
                    access_token: user.access_token
                }));
            } else {
                ctx.rest(ApiResult("", -105, "用户密码错误"));
            }
        } else {
            ctx.rest(ApiResult("", -103, "用户不存在"));
        }
    }
};


exports.load = async(ctx, next) => {
    var u = await User.findById(ctx.params.id);
    ctx.rest(ApiResult(u));
};

配置route

    app.post('/api/users', users.create);
    app.post('/api/login', users.login);
    app.get('/api/users/:id', users.load);

运行

创建用户
响应
登录响应
加载个人信息

至此,基于KoaMysql的Web Server框架搭建完成,并实现了注册、登录、加载用户个人信息功能。

参考资料

项目中主要参考了

总体框架
sequelize文档

部分代码源自廖雪峰的官网
官网
git

代码

本文中的所有代码已经提交到git上了,大家如果喜欢就去git下star下吧。

Koa-Server的代码详见:[https://github.com/vslimit/koa-server.git)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容