个人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...');
学完廖大的文章,模仿小改