因为一直没有写博客的习惯,导致学过的东西过段时间又忘掉了,拼命回忆又记不拎清,然后又重新翻资料从头学习。最近电脑重装系统,不小心把所有硬盘都格式化后,终于痛定思痛,决定以后对学习的东西都进行书写整理博客发布。这是我的第一篇博文,希望能开个好头。
正文
最近新学node.js,写了个点击上传头像并页面显示头像的例子。实现效果如图:

前端
前端的代码比较简单,不多讲:
btn.onclick = function () {
var avatar = file.files[0];
if (!avatar) {
alert("请先上传文件");
return;
}
var formdata = new FormData();
formdata.append("file", avatar)
var xhr = new XMLHttpRequest()
xhr.open("POST", "/upload/avatar");
xhr.send(formdata);
xhr.onload = function () {
if (xhr.status === 200) {
img.src = xhr.responseText;
} else {
console.log("失败")
}
}
}
后台
首先先创建node服务器,使其能够返回主页:
const http = require("http")
const fs = require("fs")
const mime = require("mime")
const joinPath = require("path").join
http.createServer(function (req, res) {
if (req.url === "/") {
var path = joinPath(__dirname, "index.html")
fs.readFile(path, function (err, data) {
if (err) {
console.log(err)
}
var mimeType = mime.lookup(path)
var charset = mime.charsets.lookup(mimeType)
res.setHeader("content-Type", mimeType + (charset ? "; charset=" + charset : ""))
res.end(data)
})
} else {
res.writeHead(404)
res.end("Not found")
}
}).listen(3000)
mime模块获取文件相关的MIME类型:想要了解mime模块的更多信息请点击这里
mime.lookup("html"). //==> "text/html"
mime.charsets.lookup("text/html") //==>"UTF-8"
接下来进行文件上传的操作:
首先要引入multiparty模块:想要了解multiparyp模块更多信息请点击这里
const multiparty = require("multiparty")
if (req.url.match(/^\/upload(\/.*)/) && req.method.toLowerCase() === "post") {
var uploadDir = joinPath(__dirname, "file") //上传目录路径
var form = new multiparty.Form({
uploadDir: uploadDir
})
form.parse(req, function (err, fileds, files) {
if (err) {
console.log(err)
}
// console.log(JSON.stringify(files, null, 4))
// {
// "file": [
// {
// "fieldName": "file",
// "originalFilename": "cat.jpeg",
// "path": "/Users/dingfm/学习/node.js/文件上传/file/kEJw4R49trAUtQFLTZkvbpx3.jpeg",
// "headers": {
// "content-disposition": "form-data; name=\"file\"; filename=\"cat.jpeg\"",
// "content-type": "image/jpeg"
// },
// "size": 43382
// }
// ]
// }
var path = joinPath("avatar", files.file[0].path.replace(uploadDir, ""))
res.end(encodeURI(path))
})
}
在测试了N次之后,flie文件夹里面就有了好多张文件名不同的相同图片,为了改善这一现象,决定把图片文件名改成上传时的原文件名,把文件夹内有相同文件名的头像进行更新。对代码做了如下修改:
if (req.url.match(/^\/upload(\/.*)/) && req.method.toLowerCase() === "post") {
var uploadDir = joinPath(__dirname, "file")
var form = new multiparty.Form({
uploadDir: uploadDir
})
form.parse(req, function (err, fileds, files) {
if (err) {
console.log(err)
}
var originalFilename = files.file[0].originalFilename //原文件名
var newPath = joinPath(uploadDir, originalFilename) //文件路径
fs.exists(newPath, function (exists) {
if (exists) {
fs.unlink(avatarPath) //删除文件
}
fs.rename(files.file[0].path, newPath) //修改文件名
var imgPath = joinPath("avatar", originalFilename)
res.end(encodeURI(imgPath))
})
})
}
最后把头像显示出来:
const parseUrl = require("url").parse
if (req.url.match(/^\/avatar(\/.*)/)) {
var urlInfo = parseUrl(req.url)
var imgPathName = decodeURI(uriInfo.pathname) //上传的文件如果是中文名需要解码
var filePath = joinPath(__dirname, "file", imgPathName.replace("/avatar/", ""))
fs.readFile(filePath, function (err, data) {
if (err) {
console.log(err)
}
var mimeType = mime.lookup(filePath)
var charset = mime.charsets.lookup(mimeType)
res.setHeader("content-Type", mimeType + (charset ? "; charset=" + charset : ""))
res.end(data)
})
}
改良:
考虑到读取文件代码的复用性,我们可以把这一部分提取出来:
function sendFile(path, res) {
fs.readFile(path, function (err, data) {
if (err) {
console.log(err)
}
var mimeType = mime.lookup(path)
var charset = mime.charsets.lookup(mimeType)
res.setHeader("content-Type", mimeType + (charset ? "; charset=" + charset : ""))
res.end(data)
})
}
但是即使这样代码还是非常冗余、不美观、不易修改,最终修改如下:
const http = require("http")
const fs = require("fs")
const mime = require("mime")
const joinPath = require("path").join
const multiparty = require("multiparty")
const parseUrl = require("url").parse
var controllers = {}
//主页
controllers.home = function (req, res) {
sendFile(joinPath(__dirname, "./index.html"), res)
}
//文件上传
controllers.upload = function (req, res) {
var uploadDir = joinPath(__dirname, "file")
var form = new multiparty.Form({
uploadDir: uploadDir //上传目录
})
form.parse(req, function (err, fileds, files) {
if (err) {
console.log(err)
}
var originalFilename = files.file[0].originalFilename //原文件名
var avatarPath = joinPath(uploadDir, originalFilename) //文件路径
//文件是否已存在
fs.exists(avatarPath, function (exists) {
//如果文件已经存在就删除它
if (exists) {
fs.unlink(avatarPath)
}
//把上传上来的图片文件名修改回原文件名
fs.rename(files.file[0].path, avatarPath)
res.end(encodeURI(joinPath("avatar", originalFilename)))
})
})
}
//获取头像
controllers.avatar = function (req, res) {
var imgPath = decodeURI(joinPath(__dirname, "/file", req.params[1])) //中文文件名解码
sendFile(imgPath, res)
}
//404
function notFound (req, res) {
res.writeHead(404)
res.end("not found")
}
const rules = [
{path: "/", controller: controllers.home},
{path: /^\/upload(\/.*)/, controller: controllers.upload, method: "POST"},
{path: /^\/avatar(\/.*)/, controller: controllers.avatar}
]
function find (arr, match) {
for (var i = 0, length = arr.length; i < length; i++) {
if (match(arr[i])) {
return arr[i]
}
}
}
http.createServer(function (req, res) {
var urlInfo = parseUrl(req.url)
var rule = find(rules, function (rule) {
if (rule.method) {
if (req.method.toLowerCase() !== rule.method.toLowerCase()) {
return false
}
}
if (rule.path instanceof RegExp) {
req.params = urlInfo.pathname.match(rule.path)
return req.params
}
return urlInfo.pathname === rule.path
})
var controller = rule && rule.controller || notFound
controller(req, res)
}).listen(3000)
代码目录结构:
|+ [ 文件上传 ]
|—— +[ node_modules ]
|—— + [ file ]
|—— app.js
|—— index.html
写在最后
现学了markdown的语法,艰难的写完了这篇博文。主要还是写给自己看的,等哪天我又记不得怎么写的时候可以翻回来看看。从没接触过后端,nodejs也是在慢慢摸索当中,若有写得不好的地方,还请各位小伙伴能帮忙指正。
ps:Typora要怎样才能设置代码块里的缩进为4个空格!
网友评论