1

这即将是一篇很长很长的文章...

从一个简单的服务器开始

// server.js
const http = require('http')

const server = http.createServer(() => {
    console.log('原地转300圈并listen3000端口')
})

server.listen(3000)

首先在终端运行node server.js使用浏览器在地址栏输入localhost:3000会发现控制台打印如下:

最简单的server

但是此时会发现浏览器地址栏始终在加载中,并且什么也没有显示,接下来让它显示一些东西出来。

http.createServer()的回调函数其实是有参数的,可以用来处理浏览器的请求,并进行响应。

const server = http.createServer((req, res) => {
    // 处理请求和响应
    console.log('原地转300圈并listen3000端口')
    res.write('lalala')
    res.end()
})

此时在终端运行node server.js使用浏览器在地址栏输入localhost:3000会发现浏览器显示如下:

最简单的有了内容的server

每一次修改都要使用ctrl+c停止服务器后再用node server.js重启很麻烦呐...那就推荐个工具:使用npm install -g supervisor安装supervisor,然后运行supervisor server.js就可以实现代码更改之后页面的自动更新。

并且会发现不论地址栏如何修改,页面内容都会是这样的:

最简单的内容始终一样的server

是因为服务器并没有对浏览器的请求做出响应。所以http.createServer()回调的第一个参数就派上了用场:

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    console.log(req.url)
    res.write('lalala')
    res.end()
})

此时控制台输出:

可获取请求路径的server

这里的/a/b/c.html/favicon.ico都是浏览器在访问http://localhost:3000/a/b/c.html时请求的内容,/favicon.ico是页面标题左侧的小图标,也就是使用<link rel="shortcut icon" href="/favicon.ico">设置的那个图标。

所以就可以根据请求的url来进行相应的处理~

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    switch(req.url) {
        case '/1.html': 
        res.write('111')
        break
        case '/2.html': 
        res.write('222')
        break
        default: 
        res.write('404')
        break
    }
    res.end()
})

可响应请求路径的server

可响应请求路径的server(返回404)

发现可以响应请求了是不是很棒棒!

但是每次都使用switch...case...肯定不是我们的本意啊,所以接下来肯定会进行一些懒人操作!

来呀,继续响应请求

这次是另一个模块的使用:fs

fs模块主要有两个方法:fs.readFile()fs.writeFile()

fs.readFile('文件名', (err, data) => {
    if (err) {
        // err的处理
        console.log(err)
    } else {
        // data的处理
        console.log(data.toString())
    }
})

这里之所以要使用data.toString()是因为readFiledatabuffer类型,这个buffer可以日后再说,当前知道使用toString()可以转换成我们之前输入的内容就好了。

这里如果发现出现了乱码则需要把aaa.txt的文件格式改成UTF-8,至于怎么改可以自行百度。
fs.writeFile('文件名', '内容', (err) => {
    console.log(err)
})

学习了fs的两个基本函数之后就可以来访问静态文件啦!

可以在当前目录下新建一个public文件夹,在public文件放一些静态文件,比如图片啊,html文件之类的。

// server.js
const http = require('http')
const fs = require('fs')

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    const file_name = '/public' + req.url
    fs.readFile(file_name, (err, data) => {
        if (err) {
            res.write('404')
        } else {
            res.write(data)
        }
        res.end()
    })
    
})
server.listen(3000)
此处注意res.end()的位置,readFile是异步操作,需要在回调中进行下一步的服务响应。此处返回的data不需要进行toString操作,因为浏览器可以识别buffer格式。

例如在public下放两个html文件:

<!-- 1.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    div {
        width: 100px;
        height: 100px;
        background-color: #f3f3f3;
        border: #ccc 1px solid;
    }
    </style>
</head>
<body>
    <div>11111</div>
</body>
</html>
<!-- 2.html,别的内容都不变,只修改body下面的 -->
<body>
    <div>22222</div>
</body>

然后对相应的路径进行访问:

可响应静态文件请求的server

啦啦啦可以直接访问文件啦!

这是和前端联系很紧密的一节

前端请求后端数据的常用的两种方式:GET和POST。GET的数据是放在url中,POST的数据不在url中。所以对这两种方式,后端需要不同的处理方式。

修改一下上节的1.html:

<!-- 别的内容都不变,只修改body下面的 -->
<body>
    <div>11111</div>
    <form action="http://localhost:3000" method="get">
        用户:<input type="text" name="user" value=""><br>
        密码:<input type="password" name="pass" value=""><br>
        <input type="submit" value="提交">
    </form>
</body>

一个很丑的get方式的表单

GET

对于GET方式,需要处理的是req.url部分。所以可以先尝试输出:

// server.js
const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    res.write(req.url)
    res.end()
})
server.listen(3000)

点提交之后,来观察一下页面的变化:

一个很low的get方式的表单

emmmm...虽说这确实太不安全了,但是就先学习一下思路嘛...

对于/?user=user&pass=123,问号前面的部分是路径/,问号后面的就是添加在url后面的参数啦,所以可以用最基本的split()方法处理数据啊...但是要考虑那个/favicon.ico,它没有?没有&,所以是要进行排雷的。

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    var get = {}
    var url = req.url
    if (url.indexOf('?') !== -1) {
        var arr = url.split('?')
        var arr1 = arr[1].split('&')

        for (let i = 0; i < arr1.length; i++) {
            var arr2 = arr1[i].split('=')
            get[arr2[0]] = arr2[1]
        }
        res.write(JSON.stringify(get))
    }
    res.end()
})
server.listen(3000)
res.write()参数只能是string或buffer,所以不能直接res.write(get)

此时会看到已经成功的处理了req.url

一个打印出密码的get方式的表单

但是不想这么麻烦啊...于是还真的有简单的模块可以用哦!

例如:

const queryString = require('querystring')

var query = queryString.parse('user=user&pass=123')
console.log(query) // { user: 'user', pass: '123' }

所以就可以使用这个模块来将处理方式变得简单点~

// server.js
const http = require('http')
const queryString = require('querystring')

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    var get = {}
    var url = req.url
    if (url.indexOf('?') !== -1) {
        var arr = url.split('?')
        get = queryString.parse(arr[1])
        res.write(JSON.stringify(get))
    }
    res.end()
})
server.listen(3000)

但是这里还需要split()一次,所以呢,还有个更简单的模块~

const urlLib = require('url')

var url = urlLib.parse('localhost:4000/?user=user&pass=123')
console.log(url)
// Url {
//     protocol: 'localhost:',
//     slashes: null,
//     auth: null,
//     host: '4000',
//     port: null,
//     hostname: '4000',
//     hash: null,
//     search: '?user=user&pass=123',
//     query: 'user=user&pass=123',
//     pathname: '/',
//     path: '/?user=user&pass=123',
//     href: 'localhost:4000/?user=user&pass=123' }

主要看的就是Url.query,但是现在并没有根据&切开怎么办呢,这时需要给urlLib.parse()设置它的第二个参数为true,说明要解析query,如下:

const urlLib = require('url')

var url = urlLib.parse('localhost:4000/?user=user&pass=123', true)
console.log(url.query)//{ user: 'user', pass: '123' }
// server.js
const http = require('http')
const urlLib = require('url')

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    var obj = urlLib.parse(url, true) 
    var get = obj.query
    var url = obj.pathname
    res.write(JSON.stringify(get))
    res.end()
})
server.listen(3000)

POST

上面的一路看过来已经get到点了...所以来post一下。

先修改之前的1.html,将get方式改为post即可

<form action="http:localhost:3000" method="post">
    用户:<input type="text" name="user" value=""><br>
    密码:<input type="password" name="pass" value=""><br>
    <input type="submit" value="提交">
</form>

因为post的数据是放在HTTP报文主体中的,数据获取就是一个很大的问题,所以首先看一下node怎么接收post数据。

node接收post数据主要是使用req的事件处理:

req.on('data', (data) => {}) // 每次接收数据时触发
req.on('end', () => {}) // 接收完毕数据时触发

因为post数据可以很大,所以对较大的数据包会进行分块再处理。分块可以避免由于种种错误而产生的大块数据重新传输,会浪费资源。

const server = http.createServer((req, res) => {
    console.log('原地转300圈并listen3000端口')

    var post = ''
    req.on('data', (data) => {
        post += data
    })
    req.on('end', () => {
        res.write(JSON.stringify(post))
        res.end()
    })
})
server.listen(3000)

可以看到显示的数据是这样的;

一个打印出密码的post方式的表单

之所以要监听两个事件是因为POST数据较大时数据也是分块发送的,所以读者朋友可以试一下增加个<texearea>然后多输入一些数据,比如设置个递增的变量来感受一下data事件的触发情况。

这里使用字符串拼接的方式来处理数据确实有一些low了,比如传输的是文件格式那就不能在字符串里面放了啊,但是先这样,目的是了解POST传输的特点啦~

对上图中的数据的处理方式还记得不?当然是牛逼哄哄的queryString啦!对数据进行以下方式的处理,就会得到json格式的数据:

const queryString = require('querystring')

//...
req.on('end', () => {
    var POST = queryString.parse(post)
    res.write(JSON.stringify(POST))
    res.end()
})

一个简单的应用

呐呐...来应用一下噻,比如说制作一个注册登录的界面。目前因为没有应用到数据库,就可以直接使用存储几个账号密码的map来模拟一下~

具体代码可以看reg&login

接下来是express的部分~

express

使用之前应该先安装npm install express

然后server.js内容如下:

// server.js
const express = require('express')

const server = express()
server.listen(3000)

emmmm...这个时候使用supervisor server.js然后打开localhost:3000会发现出现了Cannot GET /,意思就是没什么东西嘛...那接下来就让他显示一些东西出来。

server.listen(3000)前加这样的代码:

server.use('/', (req, res) => {
    res.send('这是首页')
    res.end()
})

此时就会发现首页上有东西啦!

一个express启动的简易首页

express中的reqres其实是进行封装过的,和原生node回调中的reqres兼容,这里的res.send完全可以改成res.write,只是res.write的参数必须是bufferstring类型,而res.send则没有这个限制。

发现了吗,express使用的server.use('/', callback)代替了原来对req.url的复杂处理,不用进行parseswitch...case了,方便了不少~

接下来看express怎么处理用户请求~

html文件还是之前的1.html,先<form>元素的method设置为get,随后再设置为post,来观察提交后的情况。server.js中更改如下:

const server = express()
server.get('/', (req, res) => {
    res.send('get到了')
    res.end()
})
server.post('/', (req, res) => {
    res.send('post到了')
    res.end()
})
server.listen(3000)

一个简易的get请求的express

而使用server.use的时候则是无论哪种请求都会触发,大家可以自己试试。

在express中,读取静态文件的方法是使用express.static设置一个放置静态文件的目录。

server.use(express.static('public'))

有了上面一句之后就可以访问public目录下的静态页面1.html啦~如下:

一个可以访问到静态页面的express

在express中处理url中的参数也有简单的方法:req.query,若代码如下:

server.get('/', (req, res) => {
    res.send(req.query)
    res.end()
})

就可以像下图一样:

一个打印出了密码的简陋express

所以具备了用express搞注册登录的基础知识,可以来一波实践哦~

express实现注册登录

代码可以见2-reg-login

express处理POST请求

上文学习了express使用req.query可以获取到GET方式的请求内容,这次来学习怎么接收POST请求并处理数据。

这里大家可以先去看看express的中间件的概念,上文中出现的express.static()其实就是一个托管静态文件的中间件。

这里就需要用到一个叫做body-parser的中间件,但是这个中间件就不是express内置的了,使用前需要npm install body-parser

使用方法很简单,还是使用之前的1.html,将form元素的method改为post:

<form action="http://localhost:3000/" method="post">
    用户:<input type="text" name="user" value=""><br>
    密码:<input type="password" name="pass" value=""><br>
    <input type="submit" value="提交">
</form>

server.js代码如下:

const bodyParser = require('body-parser')

server.use(bodyParser.urlencoded())
server.use('/', (req, res) => {
    res.send(req.body) // 使用了bodyParser之后body属性上才会有值
    res.end()
})

然后打开1.html输入数据后,结果如下:

一个可以显示post数据的express

于是会发现控制台会有这样一段提示:

body-parser需要的extended提示

这里的extended如果值为true则是开启扩展模式,还有另一个参数是limit表示限制大小,默认100k。但是一般用不到扩展模式,可以直接设置为false,如下:

server.use(bodyParser.urlencoded({
    extended: false,   
    limit: 2 * 1024 * 1024      // 限制2M
}))

这个时候就会发现上图中的提示没有了。

express的一些操作

链式操作

server.use('/', (req, res, next) => {
    console.log('a')
    // next()
})

server.use('/', (req, res) => {
    console.log('b')
})

如上述代码,当没有next时控制台只打印一个a,但是有了next之后,控制权可以交给下一个server.use('/', () => {}),所以可以看到控制台打印了a和b。

中间件

server.use参数为函数时,是对所有的请求都作用到。如上面的body-parser

server.use((req, res, next) => {
    req.on('data', )
})

server.use('/', (req, res, next) => {
    res.send(req.body) // 下方可以获取到
})

运行后如图:

自行添加的req.body

那么就可以模仿body-parser来搞一个中间件~

server.use((req, res, next) => {
    var str = ''
    req.on('data', (data) => {
        str += data
    })
    req.on('end', () => {
        req.body = str
        next() // 注意这里next的位置
    })
})

server.use('/', (req, res, next) => {
    res.send(req.body)
})

还用之前那个1.html,结果如下图:

自己做的中间件

这时候想处理成json的话就可以用之前的querystring的parse。

依然是跟前端联系很紧密的一节

呐呐这次要了解的是cookie和session~

cookie设置和读取

可以在chrome浏览器F12 -> Application -> Cookies看到网页的cookie,可以使用dcument.cookie获取或修改当前页面的cookie。

server.use('/', (req, res) => {
    res.cookie('user', 'oneday', {
        maxAge: 24 * 1000 * 3600 // 设置时间
    })
    res.send()
})

此时查看浏览器控制台就会发现有了这样一个cookie:

自己设置的1天后过期的cookie

不设置maxAge的话cookie会在浏览器关闭后失效。

也可以在res.cookie()中为cookie设置路径如下:

server.use('/', (req, res) => {
    res.cookie('user', 'oneday', {
        path: '/one', // 设置路径
    })
    res.send()
})

如果使用path属性设置了/one的话,那么当访问localhost:3000时无法读取到cookie,只有在localhost:3000/one下的路径都可以访问到该cookie,如localhost:3000/one/a.html

在express中读取cookie则需要一个中间件cookie-parser

const cookieParser = require('cookie-parser')

server.use(cookieParser())
server.use('/', (req, res) => {
    res.cookie('user', 'oneday', {
        path: '/one',
        maxAge: 24 * 1000 * 3600 
    })
    res.send(req.cookies) // 使用了cookie-parser中间件后才有的cookies属性
})

这时如果访问localhost:3000是不会看到有输出的,原因同上,但是/one下的路径就会有,如下图:

一个显示了cookie的express

cookie加密

server.use(cookieParser())
server.use('/', (req, res) => {
    req.secret = '愿所有的爱和付出都不会被辜负' // 签名密钥
    res.cookie('user', 'oneday', {
        maxAge: 24 * 1000 * 3600,
        signed: true
    })
    res.send(req.cookies)
})

可以看到如下图的输出:

签了名的cookie

可以看到该签了名的cookie很明显的将原始的cookie包含在了内容中,emmmm内容还是可以看到的。但是一旦cookie被修改了就能看得出来啊,所以签名只是能做到防篡改,不能做到直接加密。

签过名的cookie以s开头,签名后的cookie长度较长,因此经cookie-parser处理过的带签名的cookie会放在req.signedCookies中,未签过名的就放在req.cookies中。

// 告诉cookieParser加密使用的字符串
server.use(cookieParser('愿所有的爱和付出都不会被辜负'))
server.use('/', (req, res) => {
    req.secret = '愿所有的爱和付出都不会被辜负' // 签名密钥
    res.cookie('user', 'oneday', {
        maxAge: 24 * 1000 * 3600,
        signed: true
    })
    res.send(req.signedCookies) // 就可以将签过名的cookie原样输出
})

原样输出cookie

删除cookie

删除cookie则是使用res.clearCookie(name)就可以了。

session的设置和读取

想处理session则需要一个叫cookie-session的中间件。但是要记得在使用cookie-session中间件之前先使用cookie-parser中间件。因为不处理cookie哪来的session呢~

server.js代码改成如下所示:

const cookieParser = require('cookie-parser')
const cookieSession = require('cookie-session')

server.use(cookieParser())
server.use(cookieSession())

server.use('/', (req, res) => {
    res.send(req.session)
})

会发现报错了~~

一个报了错的session

呐呐来介绍一下这个keys。它的存在是为了预防session劫持的发生,keys是一个密钥数组,可以用来加密,express会循环使用密钥来加密cookie。

server.use(cookieSession({
    keys: ['one', 'day', 'oneday'] // keys加密数组,注意使用方法
}))

server.use('/', (req, res) => {
    if (req.session['count'] == null) {
        req.session['count'] = 1
    } else {
        req.session['count']++
    }
    console.log(req.session['count'])
    res.send('OK')
})

会发现每次控制台的session都会刷出来两个数字,那是因为也访问到了/favicon.ico,这个可以暂时不用管。

打开浏览器后可以看到cookie的地方有两个:

有两个cookie

其中的session代表的是访问次数,session.sig代表的是签名后的session,刷新会发现session变化不大但是session.sig则会发生很大的变化,这样就可以防止别人拿着上次的session假冒用户进行操作了。

session也可以进行一些自行的设置~

server.use(cookieSession({
    name: 'ses',
    keys: ['one', 'day', 'oneday'],
    maxAge: 2 * 3600 * 1000 // 2小时
}))

session的删除

服务器端使用delete req.session[name]删除session。

ejs模板引擎

ejs相对来说是温和性的,可以和html共存的一个模板引擎,个人比较喜欢ejs,所以这里不介绍jade的相关知识...大家可以自己找教程~

首先当然是要使用npm install ejs下载一下~

<!-- index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    我的名字叫:<%= name %>
</body>
</html>
// ciews下ejs.js
const ejs = require('ejs')

ejs.renderFile("./views/index.ejs", {name: "oneday"}, (err, data) => {
    if(err) {
        console.log("编译失败")
    } else {
        console.log(data)
    }
})

使用node ejs.js之后,可以得到控制台的如下输出:

第一次使用ejs

ejs中的某些语法(空格不是必须的):

  • 输出变量值:<%= name %>
  • js语句:
<% for (var i = 0; i < 3; i++) { %>
    <div><%= arr[i].name %></div>
<% } %>
  • 不转义的字符串:<%- <div></div> %>
  • 引用别的文件:<% include filepath+filename %>,这里的filepath+filename不能是变量。

ejs与express配合

emmmm可以看express的文档模板引擎。主要就是两句代码:

server.set('views', './views')
server.set('view engine', 'ejs')

server.get('/index', (req, res) => {
    res.render('index.ejs', {name: 'oneday'})
})

文件上传

这节主要讲怎么用express实现一个文件上传功能。

将1.html内容进行更改:

<form action="http://localhost:3000/" method="post">
    文件:<input type="file" name="f1"><br>
    <input type="submit" value="上传">
</form>
const express = require('express')
const bodyParser = require('body-parser')

const server = express()

server.use(bodyParser.urlencoded({extended: false}))
server.post('/', (req, res) => {
    res.send(req.body)
})

server.listen(3000)

此时运行一下试试会发现只打印出来了图片的名称,意思是只上传了文件名。原因是body-parser只能处理表单中enctype为application/x-www-form-urlencoded的项,上传文件需要将enctype改为multipart/form-data,且不能用body-parser处理了。这时就需要引入一个新的中间件:multer

npm install multer之后,可以按如下操作:

<form action="http://localhost:3000/" method="post" enctype="multipart/form-data">
    文件:<input type="file" name="f1"><br>
    <input type="submit" value="上传">
</form>
const multer = require('multer')

var objmulter = multer()
const server = express()

server.use(objmulter.any()) // 可以是single(指定的名称)

server.post('/', (req, res) => {
    res.send(req.files)
})

这时再运行就会发现有很多buffers数据出来了...但是只要这样的数据肯定是没有什么用处的啊,毕竟我们要的是上传的文件嘛...那就可以像下面这样对数据进行一下处理:

var objmulter = multer({dest: './www/upload'}) // 只改这一行代码,dest指定目标文件夹

这是再运行会发现www/upload下面真的会有一个文件,但是是一个很长名字且没有后缀,也就是没有文件类型的一个文件,接下来我们要做的就是给它加一个文件扩展名,要用到一个叫path的包。

const path = require('path')

var str = "c:\\www\\aaa\\nbb.png"

var obj = path.parse(str)
console.log(obj)

会发现输出如下:

一个处理了路径的path包

所以通过以下代码可以完成一个文件上传的小应用~

const multer = require('multer')
const pathLib = require('path')
const fs = require('fs')

var objmulter = multer({dest: './www/upload'})
const server = express()

server.use(objmulter.any())

server.post('/', (req, res) => {

    var newName = req.files[0].path + pathLib.parse(req.files[0].originalname).ext
    // 重命名临时文件
    fs.rename(req.files[0].path, newName, (err) => { // fs.rename
        if (err) {
            res.send('上传失败')
        } else {
            res.send('上传成功')
        }
    })
})

选择文件并上传后,会发现在www/upload下是真的有该文件的,而且可以正常打开~

数据库操作

这里用到的数据库是mysql,管理工具是Navicat Premium。在node中需要首先npm install mysql,随后先进行数据库连接:

const mysql = require('mysql')

const db = mysql.createConnection({
    host: 'localhost', // 目标
    user: 'root',  // 用户名
    port: 3306,  // 端口号
    password: '123456', // 密码 
    database: 'user_table' // 数据库名
})

数据库操作语法统一格式为:db.query('操作语句', (err, data) => {// callback})

SQL语句标准写法要求:1、关键字要求大写;2、库名、表名、字段名需要加上``

记录一些简单操作语句:

  • 增-INSERT
-- INSERT INTO 表(字段列表) VALUES(值列表)
INSERT INTO `user_table` (`username`, `password`) VALUES ('one', '123456')
  • 删-
-- DELETE FROM 表名 (WHERE 条件)
DELETE FROM `user_table` WHERE `username`='one'
  • 改-
-- UPDATE 表名 SET 字段=值, 字段=值, 字段=值...(WHERE 条件)
UPDATE `user_table` SET `username`='oneday', `password`='233333' WHERE `username`='one'
  • 查-SELECT
-- SELELT (内容) FROM 表名 (WHERE 条件)
SELECT * FROM `user_table`

一些子句

  • where
WHERE `age` >= 18
WHERE `age` >= 18 AND `score` < 60
WHERE `cash` > 100 OR `score` > 1000
  • order
-- ASC -> 升序 | DESC -> 降序
ORDER BY `age` ASC/DESC
-- 按价格升序,随后再按销量降序,多条件排序就用逗号分隔
ORDER BY `price` ASC, `sales` DESC
  • group
-- 按班级计数
SELECT `class`, COUNT(class) FROM `student_table` GROUP BY `class`
-- 每个班级的平均分
SELECT `class`, AVG(score) FROM `student_table` GROUP BY `class`
-- 每个班级的最高分和最低分
SELECT `class`, MAX(score), MIN(score) FROM `student_table` GROUP BY `class`

oneday
279 声望4 粉丝