Node-相册小项目(上)

完成相册新建、上传图片等简单的功能小项目。

项目初始化

  1. 把前端用到的静态资源放到 public 目录下
  2. 把所有的页面都放到 views 目录下
  3. 在项目根路径下创建一个 app.js 作为后台的启动入口
  4. 使用 npm 安装和管理项目的依赖项
  5. 将所有的相册放到项目根路径下的 uploads 目录小

划分哪些资源公共开放

  • public
    • css
    • js
    • img
  • uploads
  • node_modules

设计路由

请求方法 请求路径 响应处理
GET / views/index.html
GET /album views/album.html
GET /album/add 处理添加相册请求
POST /upload 处理照片上传请求

处理相册静态资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const mime = require('mime');
http
.createServer()
.on('request', (req, res) => {
// 指定第二个参数,parse 方法会将查询字符串解析为一个对象挂载给返回结果的 query 属性
// 该方法,会将一个完整的 url 路径解析为一个对象,方便我们取各个部分的数据
const urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname; // 拿到请求路径,不包含查询字符串
const queryObj = urlObj.query; // 拿到通过 url.parse 方法解析出来的查询字符串(已经自动转为对象了)
if (pathname === '/') {
fs.readFile('./views/index.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
} else if (pathname.startsWith('/public/') || pathname.startsWith('/uploads/') || pathname.startsWith('/node_modules/')) {
fs.readFile(`.${pathname}`, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found');
res.end();
}
res.writeHead(200, {
'Content-Type': mime.lookup(pathname)
})
res.end(data);
});
}
})
.listen(3000, () => {
console.log('Server is running at port 3000.');
});

路由:根据不同的请求路径做处理。

app.js

1
2
3
4
5
6
7
8
9
10
11
const http = require('http');
const router = require('./router');
http
.createServer()
.on('request', (req, res) => {
router(req, res);
})
.listen(3000, () => {
console.log('Server is running at port 3000.');
});

提取的路由模块

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const fs = require('fs');
const path = require('path');
const url = require('url');
const mime = require('mime');
module.exports = funciton(req, res) {
const urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname;
const queryObj = urlObj.query;
// 一个请求对应了一个处理流程代码
if (pathname === '/') {
fs.readFile('./views/index.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
} else if (pathname.startsWith('/public/') || pathname.startsWith('/uploads/') || pathname.startsWith('/node_modules/')) {
fs.readFile(`.${pathname}`, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found');
res.end();
}
res.writeHead(200, {
'Content-Type': mime.lookup(pathname)
})
res.end(data);
});
}
}

再次封装处理请求的代码, 处理模块,暴漏一系列的方法

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fs = require('fs');
const path = require('path');
const url = require('url');
const mime = require('mime');
const handler = require('./handler');
module.exports = funciton(req, res) {
const urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname;
const queryObj = urlObj.query;
// 一个请求对应了一个处理流程代码
if (pathname === '/') {
handler.showIndex(req, res);
} else if (pathname.startsWith('/public/') || pathname.startsWith('/uploads/') || pathname.startsWith('/node_modules/')) {
handler.showPublic(req, res);
}
}

hander.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const fs = require('fs');
// 处理渲染首页
exports.showIndex = (req, res) => {
fs.readFile('./views/index.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
};
// 处理添加相册
// 处理渲染相册页面
// 处理上传照片请求
// 处理静态资源请求
exports.showPublic = (req, res) => {
fs.readFile(`.${req.url}`, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found');
res.end();
}
res.writeHead(200, {
'Content-Type': mime.lookup(req.url)
})
res.end(data);
});
};

模块清晰-职责单一

客户端 -> app.js (1.启动服务器; 2. 将请求传递到 router 模块中) -> router.js 路由模块 (根据不同的请求路径调用不同的处理函数) -> handler.js 请求处理函数

使用模板引擎

template.js (先看有关 underscore 模板的使用的小例子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const _ = require('underscore');
// 1. 将模板字符串传递给 _.template 方法
// 该方法返回一个编译函数
// 2. 调用编译函数,传入要注入的数据对象
const complied = _.template(`hello <%= name %>
<ul>
<% fruits.forEach(function (fruit)){ %>
<li><%= fruit %><li>
<% }) %>
</ul>
`);
console.log(complied({
name: "Hiraku",
fruits: [
'栗子',
'苹果',
'橙子'
]
}));

现在将模板字符串利用读文件的方式读出来。(tpt.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<ul>
<% fruits.forEach(function (fruit)){ %>
<li><%= fruit %><li>
<% }) %>
</ul>
</body>
</html>

现在可以这样写 template.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _ = require('underscore');
const fs = require('fs');
fs.readFile('./tpl.html', 'utf8', (err, data) => {
if (err) {
throw err;
}
const complied = _.template(data);
console.log(complied({
fruits: [
'栗子',
'苹果',
'橙子'
]
}));
});

完成首页渲染相册表功能

app.js

1
2
3
4
5
6
7
8
9
10
11
const http = require('http');
const router = require('./router');
http
.createServer()
.on('request', (req, res) => {
router(req, res);
})
.listen(3000, () => {
console.log('Server is running at port 3000.');
});

hander.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const fs = require('fs');
const _ = require('underscore');
const url = require('mine')
// 处理渲染首页
exports.showIndex = (req, res) => {
fs.readdir('./uploads', (err, files) => {
if (err) {
throw err;
}
fs.readFile('./views/index.html', 'utf8', (err, data) => {
if (err) {
throw err;
}
const result = _.template(data)(
albumNames: files
);
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(result);
});
});
};
// 处理添加相册
// 处理渲染相册页面
// 处理上传照片请求
// 处理静态资源请求
exports.showPublic = (req, res) => {
fs.readFile(`.${req.url}`, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found');
res.end();
}
res.writeHead(200, {
'Content-Type': mime.lookup(req.url)
})
res.end(data);
});
};

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const path = require('path');
const url = require('url');
const handler = require('./handler');
module.exports = funciton(req, res) {
const urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname;
const queryObj = urlObj.query;
// 一个请求对应了一个处理流程代码
if (pathname === '/') {
handler.showIndex(req, res);
} else if (pathname.startsWith('/public/') || pathname.startsWith('/uploads/') || pathname.startsWith('/node_modules/')) {
handler.showPublic(req, res);
}
}

./views/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的相册</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="public/css/main.css">
</head>
<body>
<div class="container-fluid">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">我的相册</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="/">首页 <span class="sr-only">(current)</span></a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="" data-toggle="modal" data-target="#exampleModal" data-whatever="@mdo">新建相册</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<div class="container">
<!-- 使用模板引擎 -->
<div class="row">
<% albumNames.forEach(function(albumName){ %>
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="public/img/icon.png" alt="">
</a>
<div class="caption">
<h3><%= albumName %></h3>
</div>
</div>
<% }) %>
</div>
</div>
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="/" method="post">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="exampleModalLabel">新建相册</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="recipient-name" class="control-label">相册名称:</label>
<input type="text" class="form-control" name="albumName" placeholder="请输入相册名称">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="submit" class="btn btn-success">点击添加</button>
</div>
</form>
</div>
</div>
</div>
<script src="node_modules/jquery/dist/jquery.js"></script>
<script src="node_modules/bootstrap/dist/js/bootstrap.js"></script>
</body>
</html>

请求处理模块调用流程图

感谢您的支持!