MVC(node-koa应用)


个人blog-1: 拾忆生活
个人blog-2: 极简-拾忆生活
欢迎大家来踩,同步更新


文件目录

MVC/
|
+- .vscode/
|  |
|  +- launch.json       <-- VSCode 配置文件
|
+- package.json         <-- 项目描述文件
|
+- node_modules/        <-- npm安装的所有依赖包
|
+- controllers/         <-- Controller控制器,处理异步请求函数(async函数,直接moudle.esports暴露)
   |
   +- index.js          <--定义一个async函数处理首页URL
   |
   +- login.js          <--定义一个async函数处理登录请求
|
+- views/               <-- html模板文件和html文件
   |
   +- base.html         <- (第一层模板文件)
   |
   +- index.html        <- 首页
   |
   +- login-success.html<- 登录成功
   |
   +- login-failed.html <- 登录失败
|
+- static/              <-- 静态资源文件
   |
   +- css/              <- 存放bootstrap.css等
   |
   +- fonts/            <- 存放字体文件
   |
   +- js/               <- 存放bootstrap.js等
|
+- controller.js        <-- 扫描注册Controller控制器
|
+- static-file.js        <-- 编写处理静态文件的middleware的js文件
|
+- app.js               <-- 使用koa的js(主js)

controllers

index.js

//定义一个async函数处理首页URL/
//最后一步调用ctx.render(view, model)就完成了页面输出。
module.exports = {
    'GET /': async (ctx, next) => {
        ctx.render('index.html', {
            title: 'Welcome(登录界面)'
        });
    }
};

login.js

//再定义一个async函数处理登录请求/login
//直接moudle.esports暴露
//最后一步调用ctx.render(view, model)就完成了页面输出。
//登录成功时我们用login-success.html渲染,
//登录失败时我们用login-failed.html渲染
module.exports = {
    'POST /signin': async (ctx, next) => {
        var
            email = ctx.request.body.email || '',
            password = ctx.request.body.password || '';
        if (email === 'Lewis@qq.com' && password === '123456') {
            console.log('login success!');
            //寻找views目录下的html文件
            ctx.render('login-success.html', {
                title: 'Sign In OK',
                name: 'Mr Lewis'
            });
        } else {
            console.log('login failed!');
            //寻找views目录下的html文件
            ctx.render('login-failed.html', {
                title: 'Sign In Failed!'
            });
        }
    }
};

static

静态文件

views

base.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="description" content="learn javascript">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="/static/css/bootstrap.css">
    <script src="/static/js/bootstrap.js"></script>
</head>

<body>
    <header class="navbar navbar-static-top">
        <div class="container">
            <div class="navbar-header">
                <a href="/" class="navbar-brand">Learn JavaScript</a>
            </div>
            <nav class="collapse navbar-collapse" id="bs-navbar">
                <ul class="nav navbar-nav">
                    <li><a target="_blank" href="http://www.baidu.com/">Get one</a></li>
                    <li><a target="_blank" href="http://www.baidu.com/">Get two</a></li>
                    <li><a target="_blank" href="http://www.baidu.com/">Get three</a></li>
                </ul>
            </nav>
        </div>
    </header>
    <div id="important" style="color:#cdbfe3; background-color:#6f5499; padding:30px 0; margin:-20px 0 20px 0;">
        <div class="container">
            <h1 style="color:#fff; font-size:60px">Getting started</h1>
            <p style="font-size:24px; line-height:48px">Learn JavaScript</p>
        </div>
    </div>
    <!-- 这两行为后续提供模板,插入使用 -->
    {% block main %} 
    {% endblock %}
    
    <footer style="background-color:#ddd; padding: 20px 0;">
        <div class="container">
            <p>
                <a target="_blank" href="https://www.baidu.com/">A</a> - 
                <a target="_blank" href="https://www.baidu.com/">B</a> - 
                <a target="_blank" href="https://www.baidu.com/">C</a>
            </p>
            <p>created by <a target="_blank" href="#">Lewis</a>.</p>
        </div>
    </footer>
</body>

</html>

index.html

{% extends "base.html" %}

{% block main %}
<div class="container">
    <div class="row">
        <div class="col-md-6">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><span class="glyphicon glyphicon-user"></span> Please sign in</h3>
                </div>
                <div class="panel-body">
                    <form action="/signin" method="post">
                        <div class="form-group">
                            <label>Email address</label>
                            <input type="email" name="email" class="form-control" placeholder="Email">
                            <p class="help-block">Use email: admin@example.com</p>
                        </div>
                        <div class="form-group">
                            <label>Password</label>
                            <input type="password" name="password" class="form-control" placeholder="Password">
                            <p class="help-block">Use password: 123456</p>
                        </div>
                        <button type="submit" class="btn btn-primary">Sign In</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <h1>Get more ...</h1>
        </div>
    </div>
</div>
{% endblock %}

login-failed.html

{% extends "base.html" %} 

{% block main %}

<!-- 测试格式是否输入正确 -->
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h1>Sign in failed!</h1>
            <div class="alert alert-danger"> <strong>Sorry!</strong> Your email or password does not match! Please try again.
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-6">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><span class="glyphicon glyphicon-user"></span> Please sign in</h3>
                </div>
                <div class="panel-body">
                    <form action="/signin" method="post">
                        <div class="form-group">
                            <label>Email address</label>
                            <input type="email" name="email" class="form-control" placeholder="Email">
                            <p class="help-block">示例: admin@example.com</p>
                        </div>
                        <div class="form-group">
                            <label>Password</label>
                            <input type="password" name="password" class="form-control" placeholder="Password">
                            <p class="help-block">示例: 123456</p>
                        </div>
                        <button type="submit" class="btn btn-primary">Sign In</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

login-success.html

{% extends "base.html" %} 

{% block main %}
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h1>Sign in successfully!</h1>
            <div class="alert alert-success"> <strong>Well done!</strong> You successfully signed in as {{ name }}!
            </div>
            <p><a href="/">Back to home</a></p>
        </div>
    </div>
</div>
{% endblock %}

根目录

controller.js


const fs = require('fs');

// add url-route in /controllers:

function addMapping(router, mapping) {
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else if (url.startsWith('PUT ')) {
            var path = url.substring(4);
            router.put(path, mapping[url]);
            console.log(`register URL mapping: PUT ${path}`);
        } else if (url.startsWith('DELETE ')) {
            var path = url.substring(7);
            router.del(path, mapping[url]);
            console.log(`register URL mapping: DELETE ${path}`);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}

//后面调用时,寻找controllers目录下的以js结尾的文件
function addControllers(router, dir) {
    fs.readdirSync(__dirname + '/' + dir).filter((f) => {
        return f.endsWith('.js');
    }).forEach((f) => {
        console.log(`process controller: ${f}...`);
        let mapping = require(__dirname + '/' + dir + '/' + f);
        addMapping(router, mapping);
    });
}

//寻找controllers目录下
module.exports = function (dir) {
    let
        controllers_dir = dir || 'controllers',
        router = require('koa-router')();
    addControllers(router, controllers_dir);
    return router.routes();
};

static-file.js

const path = require('path');
const mime = require('mime');
const fs = require('mz/fs');

// url: 类似 '/static/'
// dir: 类似 __dirname + '/static'
//接收两个参数:URL前缀和一个目录,然后返回一个async函数
function staticFiles(url, dir) {

    return async (ctx, next) => {
        let rpath = ctx.request.path;
        // 判断是否以指定的url开头:
        if (rpath.startsWith(url)) {
            // 获取文件完整路径:
            let fp = path.join(dir, rpath.substring(url.length));
            // 判断文件是否存在:
            if (await fs.exists(fp)) {
                // 查找文件的mime:
                ctx.response.type = mime.getType(rpath);
                // 读取文件内容并赋值给response.body:
                ctx.response.body = await fs.readFile(fp);
            } else {
                // 文件不存在:
                ctx.response.status = 404;
            }
        } else {
            // 不是指定前缀的URL,继续处理下一个middleware:
            await next();
        }
    };
}

module.exports = staticFiles;


//关于mime
//1.lookup() renamed to getType()
//2.extension() renamed to getExtension()
//3.charset() and load() methods have been removed

templating.js

const nunjucks = require('nunjucks');


function createEnv(path, opts) {
    var
        autoescape = opts.autoescape === undefined ? true : opts.autoescape,
        noCache = opts.noCache || false,
        watch = opts.watch || false,
        throwOnUndefined = opts.throwOnUndefined || false,
        env = new nunjucks.Environment(
            new nunjucks.FileSystemLoader(path, {
                noCache: noCache,
                watch: watch,
            }), {
                autoescape: autoescape,
                throwOnUndefined: throwOnUndefined
            });
    if (opts.filters) {
        for (var f in opts.filters) {
            env.addFilter(f, opts.filters[f]);
        }
    }
    return env;
}

//集成Nunjucks的middleware
function templating(path, opts) {
    // 创建Nunjucks的env对象:
    var env = createEnv(path, opts);
    return async (ctx, next) => {
        // 给ctx绑定render函数:(内部渲染模板)
        ctx.render = function (view, model) {
            // 把render后的内容赋值给response.body:
            ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
            // 设置Content-Type:
            ctx.response.type = 'text/html';
        };
        // 继续处理请求:
        await next();
    };
}

module.exports = templating;


// ctx.render内部渲染模板时,Model对象并不是传入的model变量,
// 而是:Object.assign({}, ctx.state || {}, model || {})
// 解析:
// Object.assign()会把除第一个参数外的其他参数的所有属性复制到第一个参数中。
// 第一个参数:ctx.state || {},这个目的是为了能把一些公共的变量放入ctx.state并传给View。
// 第二个参数:model || {}确保了即使传入undefined,model也会变为默认值{}。
// 进阶:
// 例如,某个middleware负责检查用户权限,它可以把当前用户放入ctx.state中:
// 这样就没有必要在每个Controller的async函数中都把user变量放入model中。
// app.use(async (ctx, next) => {
//     var user = tryGetUserFromCookie(ctx.request);
//     if (user) {
//         ctx.state.user = user;
//         await next();
//     } else {
//         ctx.response.status = 403;
//     }
// });

app.js(主运行js文件)

const Koa = require('koa');

const bodyParser = require('koa-bodyparser');

//导入控制器处理URL路由和集成Nunjucks模板
const controller = require('./controller');
const templating = require('./templating');

const app = new Koa();

//生产环境上必须配置环境变量NODE_ENV = 'production',
//开发环境不需要配置,实际上NODE_ENV = 'undefined'.
//定义了一个常量isProduction,它判断当前环境是否是production环境。
//如果是,就使用缓存,如果不是,就关闭缓存
const isProduction = process.env.NODE_ENV === 'production';

// 第一个middleware是记录URL以及页面执行时间:
app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    var
        start = new Date().getTime(),
        execTime;
    await next();
    execTime = new Date().getTime() - start;
    ctx.response.set('X-Response-Time', `${execTime}ms`);
});

// 第二个middleware处理静态文件:
//内部let块,导入staticFiles自定义包(即目录+暴露js的文件名)
//接收两个参数:URL前缀和一个目录,url: 类似 '/static/';dir: 类似 __dirname + '/static'
//if (! isProduction)即根据环境变量判断NODE_ENV,生产production,开发undefined
//判断结果应为开发环境
if (! isProduction) {
    let staticFiles = require('./static-files');
    app.use(staticFiles('/static/', __dirname + '/static'));
}

// 第三个middleware解析POST请求:
app.use(bodyParser());

// 第四个middleware负责给ctx加上render()来使用Nunjucks:
//集成Nunjucks,调用这个方法来渲染模板
app.use(templating('views', {
    noCache: !isProduction,
    watch: !isProduction
}));

//最后一个middleware处理URL路由:
app.use(controller());

app.listen(3000);
console.log('app started at port 3000...');

学完廖大的文章,模仿小改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值