Node-相册小项目(中)

文件路径

操作文件的路径使用注意事项:

  • 如果是以 / 开头的路径,则就是去执行当前脚本所属的磁盘根路径去找
  • 如果是以 C:/dev/nvm/settings.txt ,则直接去找该绝对路径
  • 如果是以 ./ 或者 ../ 开头的,则是相对于执行 node 命令的时候所处的路径
1
2
3
4
5
6
7
const fs = require('fs');
fs.readFile('/README.md', 'utf8', (err, data) => {
if (err) {
throw err;
}
console.log(data);
});

如果使用 C:\Users\mhq\Desktop> node code/02_文件路径.js 这样执行 node 命令:

该文件中的相对路径是去执行node命令的目录地方去找了 'C:\Users\mhq\Desktop\README.md

又如:C:\Users\mhq> node .\Desktop\code\02_文件路径.js

找了这样 C:\Users\mhq\README.md 的目录

想要解决上面的问题:

每一个模块中都提供了两个属性: __dirname__filename

所以,如果是操作相对路径的文件,最好把相对路径转为绝对路径。

但是绝对路径又不能写死,可以使用每一个文件模块中都提供了两个属性:__dirname__filename

  • __dirname 用来获取当前文件模块所属目录的绝对路径
  • __filename 用来获取当前文件的绝对路径,这个属性用的比较少

然后在执行 node 命令,不会受路径影响。

__dirname 相对于当前所在文件,找到它的绝对路径,假如下面这段代码在 02.js 这个文件中,那么 __dirname 就是找的 02.js 的绝对路径。而这个 README.md 文件是和 02.js 同级,如果 02.jsREADME.md 的上一级,那么只需要加上 ../ 就可以自动拼接了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs');
const path = require('path');
fs.readFile(path.join(__dirname, 'README.md'), 'utf8', (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
fs.readFile(path.join(__dirname, 'README.md'), 'utf8', (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
console.log(__dirname); // => C:\Users\mhq\Desktop\code
console.log(__filename); // => C:\Users\mhq\Desktop\code\02_文件路径.js
console.log(path.join(__dirname, 'README.md')); // 把相对路径变为绝对路径

注意:加载自己写的相对路径模块不受执行 node 命令所处目录影响,也就是说,加载文件模块还是使用相对路径

node 中使用模板引擎

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
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);
}
}

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);
});
};

./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>

现在的问题是要使项目运行起来,不知道项目的入口文件。

如何解决? 利用某种规则。可以写一些 npm 运行脚本。如:package.json 中的 "script"

在 “script” 下加 "start": "node app.js"

现在启动网站直接可以使用 npm start 直接启动项目。其中 start 是在终端中要执行的命令名称。

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "album",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node app.js"
},
"keywords": [],
"author": "lpz <mail@lipengzhou.com> (http://www.lipengzhou.com/)",
"license": "MIT",
"dependencies": {
"bootstrap": "^3.3.7",
"formidable": "^1.0.17",
"jquery": "^3.1.1",
"mime": "^1.3.4",
"underscore": "^1.8.3"
}
}

使用模板引擎

在浏览器中使用模板引擎

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在浏览器中使用模板引擎</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="../public/img/icon.png" alt="">
</a>
<div class="caption">
<h3>相册名称</h3>
</div>
</div>
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="../public/img/icon.png" alt="">
</a>
<div class="caption">
<h3>相册名称</h3>
</div>
</div>
</div>
</div>
<script type="text/template" id="tpl">
<% arr.forEach(function (item) { %>
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="../public/img/icon.png" alt="">
</a>
<div class="caption">
<h3><%= item %></h3>
</div>
</div>
<% }) %>
</script>
<script src="../node_modules/underscore/underscore.js"></script>
<script>
var arr = ['人文', '地理', '美女'];
var result = _.template(document.getElementById('tpl').innerHTML)({
arr: arr;
});
console.log(result);
// 1. 模板引擎和 DOM 操作没有任何关系
// 2. 模板引擎并不一定操作 HTML 结构字符串
</script>
</body>
</html>

在 node 中使用模板引擎

在 node 中,模板引擎的使用方式和浏览器是一样的。

只不过模板字符串的存储方式变了,因为 node 中没有 DOM,没有 script 标签的概念。

但是 node 支持文件操作,所以我们把模板字符串放到文件中。

使用的时候,通过 fs.readFile 读取出来就可以了。

.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const _ = require('underscore');
const fs = require('fs');
fs.readFile('./tpl.mhq', 'utf8', (err, data) => {
if (err) {
throw err;
}
var arr = ['人文', '地理', '美女'];
// 键名和引用名一样,只用一个就可以表示。
const result = _.template(data)({
arr;
});
console.log(result);
});

tpl.mhq

1
2
3
4
5
6
7
8
9
10
<% arr.forEach(function (item) { %>
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="../public/img/icon.png" alt="">
</a>
<div class="caption">
<h3><%= item %></h3>
</div>
</div>
<% }) %>

使用模板引擎处理别的字符串

.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
const fs = require('fs');
const _ = require('underscore');
const users = require('./data.json');
fs.readFile('./result.txt', 'utf8', (err, data) => {
if (err) {
throw err;
}
const result = _.template(data)({
users;
});
console.log(result);
});

data.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[
{
"name": "jack",
"age": 18,
"gender": "男",
"birthday": "1995-11-11",
"hobby": [
"eat",
"sleep",
"hitDouDou"
]
},
{
"name": "rose",
"age": 18,
"gender": "女",
"birthday": "1995-11-11",
"hobby": [
"eat",
"sleep",
"hitDouDou"
]
}
]

result.txt

1
2
3
4
5
6
7
8
9
<% users.forEach(function (user) { %>
姓名:<%= user.name %>
年龄:<%= user.age %>
性别:<%= user.gender %>
生日:<%= user.birthday %>
爱好:<%= user.hobby.join('、') %>
-----------------------------------
-----------------------------------
<% }) %>

处理添加相册

GET

/album/add?albumName=xxx&key=xxx

表单 GET 提交(表单的 method 属性默认就是 get):

表单会将表单中所有具有 name 属性的 input 按照: name=input-value&name1=input-value2&xxx=xxx, 拼接完之后,找到自己的 action(就是请求路径), 然后在 action 之后 加一个 ? ,后面跟上拼接好的查询字符串

最后,发起请求 albumName=xxx&xxx=xxx

/album/add?albumName=xxx&xxx=xxx

appjs

1
2
3
4
5
6
7
8
9
10
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.')
})

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 连接 -->
<a href="/album?albumName=<%= albumName %>" 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="/album/add" method="get">
<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>

album.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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的相册 -</title>
<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">
<% albumPaths.forEach(function (imgSrc) { %>
<div class="col-xs-6 col-md-3">
<div class="thumbnail">
<img src="<%= imgSrc %>" alt="">
</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="/upload?albumName=<%= albumName %>" method="post" enctype="multipart/form-data">
<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="file" class="form-control" name="pic">
</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>

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const path = require('path');
const url = require('url');
const handler = require('./handler');
module.exports = function (req, res) {
// 指定第二个参数,parse 方法会将查询字符串解析为一个对象挂载给返回结果的 query 属性
// 该方法,会将一个完整的 url 路径解析为一个对象,方便我们取各个部分的数据
const urlObj = url.parse(req.url, true);
// 拿到请求路径,不包含查询字符串
const pathname = urlObj.pathname;
// 拿到通过 url.parse 方法解析出来的查询字符串(已经自动转为对象了)
const queryObj = urlObj.query;
// 拿到当前请求方法
const method = req.method.toLowerCase();
// 在这里,将解析到的查询字符串以属性的形式挂载给 req 请求对象
// 在后面的处理方法中就可以直接通过 req.query 来获取查询字符串中的数据了
req.query = queryObj || {};
// 302 重定向
// 浏览器收到 302 状态码的时,浏览器会自动找响应报文中的 Location,然后对 Location 指向的地址发起请求
// 在这里给 res 对象挂载一个 redirect 方法,在后续的处理中就可以直接使用该方法,就可以实现重定向跳转
res.redirect = url => {
res.writeHead(302, {
'Location': url;
})
res.end();
}
// 一个请求其实就是对应一个处理函数,每一个请求处理函数无非都需要用到 req 对象和 res 对象
// 所以 handler 中都定义成接收 req 和 res 的一个一个的小函数,用来处理对应的请求
if (pathname === '/') {
handler.showIndex(req, res);
} else if (method === 'get' && (pathname.startsWith('/public/') || pathname.startsWith('/uploads/') || pathname.startsWith('/node_modules/'))) {
handler.showPublic(req, res);
} else if (method === 'get' && pathname === '/album/add') {
handler.doAddAlbum(req, res);
} else if (method === 'get' && pathname === '/album') {
handler.showAlbum(req, res);
} else if (method === 'post' && pathname === '/upload') {
handler.doUpload(req, res);
} else if (method === 'get' && pathname === '/register') {
handler.showRegister(req, res);
} else if (method === 'post' && pathname === '/register') {
handler.doRegister(req, res);
}
}

handler.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
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
const fs = require('fs');
const path = require('path');
const _ = require('underscore');
const mime = require('mime');
const config = require('./config');
const qstring = require('querystring');
const formidable = require('formidable');
exports.showIndex = (req, res) => {
// 1. 将所有的相册名称读取出来
// 2. 把相册名称数据和文件中的模板字符串编译替换
// 3. 发送给客户端浏览器
fs.readdir(config.uploadDir, (err, files) => {
if (err) {
throw err;
}
fs.readFile('./views/index.html', 'utf8', (err, data) => {
if (err) {
throw err;
}
// 使用 underscore 中的 template 模板引擎在后台处理一下源数据
// 这里是把 index.html 文件内容当成模板字符串了(整体)
// 也就是说你在 index.html 页面可以随便使用模板引擎语法,因为后台在发送页面内容之前会对文件内容做处理
const result = _.template(data)({
albumNames: files
});
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(result);
});
});
};
/**
* GET /public
* /uploads
* /node_modules
* 这是一个处理公共资源的请求处理方法
*/
exports.showPublic = (req, res) => {
// 解决中文路径解析问题
const url = decodeURI(req.url);
fs.readFile(`.${url}`, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found');
res.end();
}
res.writeHead(200, {
'Content-Type': mime.lookup(req.url);
})
res.end(data);
});
};
/**
* GET /album/add
* query: { albumName: 'xxx' }
*/
exports.doAddAlbum = (req, res) => {
// 1. 接收客户端的请求数据
// 2. 处理请求:创建目录
// 3. 发送响应:告诉请求端,成功与否
const albumName = req.query.albumName ? req.query.albumName.trim() : '';
// 校验添加相册名称的合法性 \/:*?"<>|
if (/(\\|\/|\:|\*|\?|"|\<|\>|\|)|^$/.test(albumName)) {
return res.end('albumName param invalid error.');
}
// 1. 万一 handler.js 不在项目的根路径下了
// 2. 万一 上传的目录不想叫 uploads
// 3. 万一 上传的路径不在 uploads 中
// 由于在多个地方都会使用到 uploads 目录的路径,所以以上三种可能万一变化,就要手动全部修改
// 所以这里就把可能变化并且多个地方引用的元素放到了 config.js 配置文件中
// 这样的话在使用的时候,就通过 config.js 配置文件来拿
// 好处就是:只要配置文件中的属性发生了变化,那么所有使用了该属性的地方都会跟着变
fs.access(path.join(config.uploadDir, albumName), err => {
if (!err) {
// 如果没有错误发生,说明该相册目录已存在
return res.end('albumName already exists');
}
fs.mkdir(path.join(config.uploadDir, albumName), err => {
if (err) {
throw err;
}
// 跳转页面,302 重定向
// 当浏览器收到 302 状态码的时候,浏览器会自动找到相应报文中的 Location
// 对 Loacation 指向的地址发起请求
// '/' 表示当前网站的根路径
res.writeHead(302, {
'Location': '/'
});
res.end();
});
});
};
/**
* GET /album
* query: { albumName: 'xxx' }
*/
// 首先在 index.html 里面修改 a 标签的连接
exports.showAlbum = (req, res) => {
// 1. 解析接收客户端提交的数据
// 2. 根据数据处理客户端请求
// 3. 对处理结果发送响应
const albumName = req.query.albumName ? req.query.albumName.trim() : '';
fs.access(path.join(config.uploadDir, albumName), err => {
if (err) {
return res.end('album not exists.');
}
fs.readdir(path.join(config.uploadDir, albumName), (err, files) => {
if (err) {
throw err;
}
fs.readFile(path.join(config.viewPath, 'album.html'), 'utf8', (err, data) => {
if (err) {
throw err;
}
const result = _.template(data)({
albumName: albumName,
albumPaths: files.map(fileName => `/uploads/${albumName}/${fileName}`)
});
res.writeHead(200, {
'Content-Type': "text/html; charset=utf-8"
});
res.end(result);
});
});
});
};
/**
* POST /upload
* query { albumName: 'xxx' }
*/
exports.doUpload = (req, res) => {
// 1. 解析表单提交数据:这里使用 formidable 解析表单 post 文件提交
// formidable 文档:album/node_modules/formidable/Readme.md
// 2. 处理客户端请求
// 3. 根据处理结果发送响应
const albumName = req.query.albumName ? req.query.albumName : '';
fs.access(path.join(config.uploadDir, albumName), err => {
if (err) {
return res.end('album not exists');
}
const form = new formidable.IncomingForm();
// 自定义文件上传的路径
form.uploadDir = path.join(config.uploadDir, albumName);
// formidable 默认会去掉扩展名,这里配置为保留文件的扩展名
form.keepExtensions = true;
// 限制上传数据的大小,单位是字节
form.maxFieldsSize = 10 * 1024 * 1024;
// fields 就是 formidable 解析到的普通字段数据
// files 是一个一个的文件信息对象
// formidable 会自动将上传的文件解析到当前操作系统的临时目录
// 它会把帮你解析完并且保存的文件的路径、文件名等信息作为对象的形式挂载给 files 参数
form.parse(req, (err, fields, files) => {
if (err) {
return res.end('The default size is 10MB.');
}
// 封装了重定向函数
res.redirect(encodeURI(`/album?albumName=${albumName}`));
});
});
};
/* 以下关于注册的处理函数是为了演示如何使用 node 处理普通表单 POST 请求 */
exports.showRegister = (req, res) => {
fs.readFile(path.join(config.viewPath, 'register.html'), (err, data) => {
if (err) {
throw err;
}
res.end(data);
});
};
exports.doRegister = (req, res) => {
let buffers = [];
// 监听 req 对象的 data 事件 和 end 事件
req.on('data', data => {
buffers.push(data);
});
// 基本用不到
req.on('end', () => {
const file = Buffer.concat(buffers);
fs.writeFile('./a', file, err => {
if (err) {
throw err;
}
console.log('writed success');
});
console.log(file.length);
// console.log(qstring.parse(body));
});
};

config.js

1
2
3
4
5
const path = require('path');
module.exports = {
uploadDir: path.join(__dirname, 'uploads'),
viewPath : path.join(__dirname, 'views')
};

处理普通表单 POST 提交(没有文件的表单)

表单会将表单中的所有具有 name 的 input 中的 name 和 value 按照 name=value&name=value,这样的形式拼接成一个查询字符串,然后将查询字符串放到请求报问体中。

  • 请求头
    • 请求首行
    • 请求首部字段
  • 空行
  • 请求体 name=value&name=value

处理有文件的表单 POST 提交

  1. 请求方法必须是 post
  2. 将表单的 enctype 设置为 multipart/form-data
    • 表单会把普通字段(不是文件,具有name的input)和文件,否放到请求报问体中。

注意:这里的请求报问体中就不是查询字符串了,所以后台通过查询字符串的解析方式就解析不到了

如何解析:

  • 在 Node 后台,可以使用一个第三方包:formidable 来辅助解析有文件的表单提交
  • 使用文档可以查看:album/node_modules/formidable/Readme.md

简单登录页(仅仅用来案例演示)

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登陆</title>
</head>
<body>
<form action="/register" method="post" enctype="multipart/form-data">
<p>
<label for="">用户名</label>
<input type="text" name="username">
</p>
<p>
<label for="">密码</label>
<input type="password" name="password">
</p>
<p>
<label for="">是否同意</label>
<input type="checkbox" name="agree">
</p>
<p>
<label for="">性别</label>
<input type="radio" value="1" name="gender">
<input type="radio" value="0" name="gender">
</p>
<p>
<label for="">头像</label>
<input type="file" name="avatar">
</p>
<p>
<input type="submit" value="点击注册">
</p>
</form>
</body>
</html>
感谢您的支持!