模块化

模块化:将大文件拆成独立并互相依赖的多个小模块。可提高代码的复用性,可维护性,实现按需加载。

nodejs遵循CommonJS模块化规范。

commonjs规定

模块导入

require加载模块

require可以加载需要的内置模块、用户自定义模块、第三方模块(包)。

1
2
3
4
//内置模块
const fs = require('fs')
//自定义模块
const fs = require('myJS.js')//后缀可省略,可写作require('myJS')

require引入时会直接运行一遍模块的所有代码,并将其放入缓存。例如,若myJS.js中有一句console.log('hello'),引入后会直接打印出hello

缓存后的模块不会被重复加载,即多次调用require不会多次执行模块代码

模块导出

模块作用域

模块作用域:在自定义模块中定义的变量、方法等成员,只能在当前模块内部被访问。

模块作用域

通过模块作用域,可以防止不同模块使用相同变量名导致的全局变量污染

module.exports

js的module对象存储了当前js文件的有关信息

image-20240507210517571

其中的exports对象存储着此模块共享成员的信息。通过module.exports可共享模块内的成员,供外界使用。require()引入自定义模块,得到的就是module.exports对象。

module.exports默认为空,可直接挂载对象和内容:

image-20240507211829880
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 写法一
// 挂载属性
module.exports.Username= 'sam'
// 挂载方法
module.exports.nameGet = function(){
console.log('hello')
}

// 写法二
module.exports = {
Username: 'amy',
nameGet(){
console.log('hi')
}
}

两种方式均可挂载数据。

注意:第二种方法更改了module.exports的指向,会导致先前添加的内容丢失。

exports

一般情况下,module.exports可简写为exports,能起到相同作用。但两者并不完全相同,最好不要混用。

两者一起使用时,module.exports的优先级更高。exports会失效。

包与npm

包:nodejs中的第三方模块

npm:包管理工具Node Package Manager,通过npm可下载并管理包

引用包时,在终端中输入npm i 名字安装后,即可直接require('名字')使用

image-20240507230959231

npm日常操作使用:node.js - 短小精悍的npm入门级保姆教程,一篇包会 - 果冻想 - SegmentFault 思否

详细知识:【Node.js】npm与包【万字教学~超超超详细】_npm.js-CSDN博客



fs-读写文件

方法

读取文件:

  • 异步读取:readFile
  • 同步读取:readFileSync
  • 流式读取:createReadStream

写入文件:

  • 异步写入:writeFile(file, data[, options], callback)

    • 注意只能创建文件,不能创建路径
  • 同步写入:writeFileSync

  • 异步追加写入:appendFile(file, data[, options], callback)

  • 同步追加写入:appendFileSync(file, data)

  • 流式写入:createWriteStream(file)

普通读写与流式读写:

  • 普通读取是一次性将整个文件内容读取到内存中,然后进行处理。适用于文件较小、可完全放入内存的情况;

  • 流式读取逐块地从文件中读取数据,可以减少内存占用,并且在处理大型文件时更有效率。

普通:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//第一种,用readFile和writeFile
//导入fs模块,用于操作文件
const fs = require('fs')

//fs.readFile('路径', '编码格式(可不写)',回调函数)
fs.readFile('./new.txt' , 'UTF-8', function(err, dataStr){
//err为失败结果,dataStr为成功结果
//如果读取成功,则err为NULL
//如果读取失败,则err为错误对象,dataStr为undefined
if(err){console.log(err.message)}
else{console.log(dataStr)}
})

//fs.write('路径',内容, 回调函数)
fs.writeFile('./1.txt', '4ui12', function(){
//如果写入成功,则err为null
//如果写入失败,则err为错误对象
if(err){console.log(err.message)}
})

流式:

1
2
3
4
5
6
7
8
9
10
11
12
//第二种,用流式操作
const fs = require('fs');

//创建读取流对象
const rs = fs.createReadStream('./1.txt');
//创建写入流对象
const ws = fs.createWriteStream('./2.txt');

//绑定data事件
rs.on('data',chunk => {
ws.write(chunk);
})

其他方法请参考Nodejs中的fs模块_node安装fs-CSDN博客

path-路径

文件路径错误问题

如果提供的操作路径为相对路径,很容易出现路径动态拼接错误的问题:

代码运行会以node命令执行时所处的目录拼接相对路径,而不是用js文件目录拼接。

解决方法:提供完整路径即可。

直接提供绝对路径

例: C:\Users\username\Documents\files\1.txt

但这种方法移植性差,不利于维护。

__dirname

__dirname表示当前文件所处目录,更方便安全

将上方路径修改为:__dirname+'/files/1.txt'

另外,有变量**__filename**,可以定位到当前文件名

path

path.join([…paths])

拼接相对路径。起到添加分隔符的效果。由于操作系统的不同分隔符可能不同,所以在填写路径时最好采用此方式。

上方的路径可改为:path.join(__dirname+'/files/1.txt')

1
2
3
4
5
6
const path = require("path");
const fs = require("fs");

fs.readFile(path.join(__dirname,"hello.txt"),"utf8",(err,dataStr)=>{
/* code */
})

path.resolve([…paths])

将路径或路径片段的序列按照从右到左的顺序解析为绝对路径,并将它们连接在一起,最终返回一个解析后的绝对路径。

  • 若某路径片段是绝对路径(以斜杠 / 开头),则它会被返回,且后续的路径片段会被忽略。

  • 如果某个路径片段是相对路径(不以斜杠 / 开头),则它会与之前的路径片段进行连接,直到生成一个绝对路径。

  • 所有的路径片段都被解析完毕后,会返回最终的绝对路径。

1
2
3
4
5
6
7
8
path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file'

path.resolve('aa', 'files/png/', '../gif/image.gif');
// 若当前工作目录是 /home/node,则返回 '/home/node/aa/files/gif/image.gif'

path.basename(path[,ext])

返回路径指向的文件名,若添加后缀名,返回值则会删除后缀名。

path.extname(path)

返回路径指向的文件的扩展名,若无扩展名则返回空字符串。

path.dirname(path)

返回路经指向的目录名。

1
2
3
4
5
6
7
8
9
10
path.basename('/a/b/c/1.html');
// 返回: '1.html'
path.basename('/a/b/c/1.html', '.html'); // 添加后缀名后,返回结果将后缀名删除
// 返回: '1'

path.extname('index.html');
// 返回: '.html'

path.dirname('/a/b/c/d');
// 返回: 'c'

其他方法参考[nodejs] path 路径_path.basename-CSDN博客

示例:压缩html文件

通过fspath压缩html文件,删除换行和空格部分,变成一行的形式

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs')
const path = require('path')
// 读取html文件
fs.readFile(path.join(__dirname,'ep1.html'),'utf-8',(err,data)=>{
if(err) console.log(err)
else{
// 正则表达式查找换行符和空格并替换
const resultFile = data.toString().replace(/[\r\n\s]+/g,'')
console.log(resultFile)
fs.writeFile(path.join(__dirname,'/file/1.html'),resultFile,(err)=>{if(err) console.log(err)})
}
})


http-创建web服务器

负责消费资源的电脑叫做客户端,而负责对外提供网络资源的电脑叫做服务器。

通过http模块可以实现创建web服务器,从而对外提供web资源服务。

基础使用

通过以下的简单步骤实现服务器创建:

1
2
3
4
5
6
7
8
// 1.引入模块
const http = require('http')
// 2.创建服务器
const server = http.createServer()
// 3.添加监听事件,监测请求
server.on('request',(req,res)=>{/*函数体*/})
// 4.设置服务器监听端口
server.listen('端口')

监听事件的回调函数中,可以通过请求对象(req)和响应对象(res)实现客户端和服务器的交互。

req请求对象:访问客户端属性,便于向服务器请求数据

res响应对象:访问服务器数据属性,便于向客户端返回数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server.on('request',(req,res)=>{
console.log('you receive a request')

// req 请求对象 访问客户端属性
// request.url 获取客户端请求的url地址
const url = req.url
// request.method 获取客户端的请求方法(如GET/POST)
const method = req.method
// 打印在服务器终端
console.log(`the request URL is ${url},method: ${method}`)

// res 响应对象 访问服务器数据属性
// res.setHeader() 设置响应头Content-Type,为防止乱码通常需要设置中文编码utf-8
res.setHeader('Content-Type','text/html; charset=utf-8')
const str = `你的request URL是 ${url},方法为${method}`
// res.end() 向客户端发送指定内容,并结束请求,str的内容会以html形式打印在网页上
res.end(str)
})

nodejs终端返回内容:

you receive a request
the request URL is /favicon.ico,method: GET

网页返回内容:

http返回内容

设置服务器监听端口可以添加回调函数,如:

1
2
3
4
5
server.listen(80,()=>{
//127.0.0.1即localhost
//访问网址时,80端口可以省略':80'
console.log('server running at http://127.0.0.1:80')
})

通过httpfs,可以实现访问服务器内部的数据。

以下是一个浏览器访问网页的例子:

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')
const path = require('path')
const http = require('http')

const server = http.createServer()
server.on('request',(req,res)=>{
console.log('get one new request')
// 读取客户端url
const request_URL = req.url
// 若url为空,则将其重定向到html文件位置
if(request_URL === '/'){
// 发送 302 Found 状态码和重定向地址
res.writeHead(302, {
'Location': 'http://127.0.0.1/html_files/index.html'
});
res.end();}
// 若有url,则拼接地址并访问
const fpath = path.join(__dirname,request_URL)
fs.readFile(fpath,(err,dataStr)=>{
// 若无地址内容则返回404
if(err) return res.end('<h1>404 NOT FOUND</h1>')
else{
res.end(dataStr)
}
})
})
server.listen(80,()=>{
console.log('server on http://127.0.0.1/')
})

浏览器访问http://127.0.0.1/html_files/index.html可得到./html_files/index.html的内容,若文件内部包含其他路径地址,则会自动访问。若为相对路径,会从当前文件的路径(./html_files/index.html)进行拼接。



Express

Express 是一款受欢迎的开源 web 框架,构建语言是 JavaScript,可以在 node.js 环境运行。

npm安装:

express: npm install express -g

express命令工具:npm install express-generator -g

Express/Node 入门 - 学习 Web 开发 | MDN (mozilla.org)

基础使用

最基本的结构:

1
2
3
4
5
6
7
// 引入express
const express = require('express')
// 创建服务器
const app = express()
// 启动服务器
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')

监听请求

方法:app.method('地址',function(req,res)=>{})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.get('/',(req,res)=>{
// 通过req.query可接收客户端的查询参数,默认为空
console.log(req.query)
// res.send()可向客户端发送一个json数据
res.send({name:'Sam',age:18,words:'hello'})
})

app.post('/user',(req,res)=>{
// res.send()填入字符串会被转化为json
res.send('请求成功')
})

// :id和:name均为动态参数
app.get('/user/:id/:name',(req,res)=>{
// req.params 是动态匹配到的url参数,默认为空
console.log(req.params)
res.send(req.params)
})

express.static()

express.static()可以用于创建静态资源服务器:

1
app.use(express.static('public'))

可以访问public目录中的所有文件。

注意:express在指定静态目录中查找文件,存放静态文件的目录名(public)不会出现在url中

浏览文件时,直接加上文件在静态目录中的路径即可:

./public/index.html的文件:http://127.0.0.1/index.html

如需托管多个资源目录,多次调用express.static()即可,express会根据目录的添加顺序查找文件。文件重名时,会根据目录顺序选择优先的文件。

如果需要添加访问路径前缀,可使用此方式:

1
app.use('/whatcanisay',express.static('public'))

./public/index.html的文件:http://127.0.0.1/whatcanisay/index.html

nodemon

nodemon可以实时监听nodejs文件的变化并重启项目。修改代码时不用再频繁close open文件。

npm安装:npm i nodemon -g

启动nodejs文件时,node index.js改为nodemon index.js即可

路由

概念

广义上的路由即映射关系。

express中,路由指客户端亲贵和服务器处理函数之间的映射关系。

express的路由由三部分组成:请求类型,请求url地址和处理函数,格式为:

1
app.METHOD(PATH,HANDLER)

将路由挂载到app上是一个路由最简单的使用方法。如前文的监听请求。

每个请求到达服务器都需要先经过路由的匹配,匹配成功后才会调用对应处理函数。

匹配时按照路由定义的先后顺序匹配,请求类型url需要同时匹配成功才会调用处理函数。

模块化路由

image-20240509163927598

路由模块示例:

1
2
3
4
5
6
7
8
9
10
// 1.导入express
const express = require('express')
// 2.创建路由对象
const router = express.Router()
// 3.挂载具体路由
router.get('/a',(req,res)=>{
res.send("hello")
})
// 4.导出路由对象
module.exports = router

主文件应用即可:

1
2
3
4
5
6
7
// 导入路由模块
const router = require('./router')
//使用app.use注册模块
//app.use作用:注册全局中间件
app.use(router)
//若要为路由模块添加访问前缀:
app.use('前缀地址',router)

中间件

中间件,特指业务处理的中间处理环节。

中间件的概念与格式

express的中间件,本质上是一个function处理函数,格式如下:

1
2
3
4
5
6
7
// 中间件的形参列表中必须包含next参数,而路由处理参数中只有res,req
app.get('/',function(req,res,next){
/*任务*/
// 中间件的任务处理完毕后,调用next()
// 通过next()实现多个中间件连续调用,表示把流转关系转交给下一个路由或中间件
next()
})

全局生效的中间件:客户端发起的任何请求,到达服务器后都会触发的中间件.

使用app.use()定义即可:

1
2
3
4
5
6
7
8
9
10
11
const myf = function(req,res,next){
console.log('666')
next()
}
app.use(myf)
// 可简写为:
app.use((req,res,next)=>{
console.log('666')
next()
})
//调用任意路由和中间件即可打印出666

可以连续定义多个全局中间件,服务器会按照定义的先后顺序进行调用。

中间件作用:连续调用多个中间件时共享同一份res和req。可以在上游中间件中,统一为req和res对象添加自定义的属性或方法(res.属性 = 属性),供下游中间件使用。

局部生效的中间件:不使用app.use()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义中间件函数
const func1 = function(res,req,next){
res.name = {name:'Amy'}
next()
}
const func2 = function(res,req,next){
res.name = {name:'Amy'}
next()
}
// 单个中间件
// func1只在当前路由中生效,不会影响其他中间件
app.get('/name',func1,(res,req)=>{
console.log(res.name)
req.send(res.name)
})
// 多个中间件
// 或者写成app.get('/name',[func1,func2],(res,req)=>{})
app.get('/name',func1,func2,(res,req)=>{
console.log(res.name,res.age)
name = res.name
age = res.age
req.send({name,age})
})

注意:

  • 在路由之前注册中间件;
  • 不要忘记next()函数,为防止代码混乱,next()后不要再写额外代码;
  • 连续调用多个中间件时共享同一份res和req。

中间件的分类

常见中间件共有5类:

**应用级别中间件:**通过app.use(),app.get(),app.post()绑定到app实例上的中间件.

**路由级别中间件:**通过express.Router()绑定到router实例上的中间件

**错误级别中间件:**专门捕获异常错误,防止项目崩溃的中间件。必须有四个形参:

1
2
3
app.use(function(err,req,res,next){
res.send('Error:'+err.message)
})

注意:错误级别中间件必须注册在所有路由之后

express内置中间件:

express.static:托管静态网站的内置中间件

express.json:解析json格式的请求体数据(有兼容性)

express.urlencoded:解析URL-encoded格式的请求体数据(有兼容性):app.use(express.urlencoded({extended:false}))

第三方中间件:npm下载、require导入、app.use()调用

接口

接口是两个独立系统之间同步数据或访问对方程序的途径。

具体来说,通过接口可以将前后端连起来。

一个基本的post/get接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// apiRouter.js 路由模块
const express = require('express')
const router = express.Router()

router.get('/get',(req,res)=>{
// 获取查询参数
const query = req.query
res.send({
status: 0,
msg:'GET请求成功!',
data:query
})
})
router.post('/post',(req,res)=>{
// 获取请求体中包含的url-encoded格式的数据
const body = req.body
res.send({
status:0,
msg:'POST请求成功',
data:body
})
})
module.exports = router

应用在一个基本的服务器上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express')
const app = express()

// 使用cors中间件解决跨域问题
// 注意放在路由前面
const cors = require('cors')
app.use(cors())
// 配置解析urlencoded的中间件
// 注意放在路由前面,保证可以先被调用
app.use(express.urlencoded({extended:false}))

// app.js 导入并注册
const router = require("./apiRouter")
app.use('/api',router)

app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})

CORS解决跨域问题:cors由一系列http响应头组成,这些响应头决定浏览器是否阻止前端js代码跨域获取资源。配置cors可以解除浏览器端的跨域访问限制

npm安装:npm i cors并在服务器中引用

CORS响应头部:

image-20240509233348715

Access-Control-Allow-Origin设为*可使浏览器允许来自任何域的请求(跨域)。

image-20240509234221099 image-20240509234434995

更多关于cors跨域问题,请参考:使用CORS解决跨域问题 - 简书 (jianshu.com)

实例:基于ajax实现的网页-服务器交互

中间件multiparty:Express—multiparty (图片上传+表单)_express multiparty-CSDN博客

html端的表单提交代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 表单内容
<div class="user_will">
<form id="will">
<input type="text" name='letter' id="loverletter">告诉我你对牢大的爱吧!
<button type="submit">提交</button>
</form>
</div>
// axios实现
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
<script>
document.querySelector('#will').addEventListener("submit",(event)=>{
console.log('调用')
event.preventDefault()
// 装载并检查表单内容
const data =new FormData()
const letter = document.querySelector('#loverletter').value
data.append("words",`${letter}`)
// axios提交请求,接收返回内容
axios.post('/api/post',data).then(result=>{
console.log('前端接收express的返回值:'+result.data)
}).catch(error=>{console.log(error)})
})
</script>

api接口部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// apiRouter.js 路由模块
const express = require('express')
const router = express.Router()
// 中间件处理formdata
const multiparty = require('multiparty')

//post请求
router.post('/post',(req,res)=>{
// 通过multiparty获取axios请求中的formdata数据
let form = new multiparty.Form()
form.parse(req, function(err,fields){
console.log('后端成功接收数据:'+fields.words);
res.send(fields.words);
});
})
module.exports = router

express的app.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())

// 配置解析urlencoded的中间件
// 注意放在路由前面,保证可以先被调用
app.use(express.urlencoded({extended:false}))
// appRouter.js 导入并注册
const router = require("./apiRouter")
app.use('/api',router)
// html网页地址
app.use(express.static('public'))

app.listen(801,()=>{
console.log('express server running at http://127.0.0.1:801')
})


SQL

MySQL表的增删改查(基础)_mysql增删改查-CSDN博客

MySQL Workbench 操作详解(史上最细)-CSDN博客

SQL 教程 | 菜鸟教程 (runoob.com)

基本使用

sql语法不关心大小写,大小写均可。

增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 数据库为my_db_01 数据表为users 包含id, username, password, userstatus四项
-- SELECT 查找
-- 查询所有数据
-- SELECT * FROM my_db_01.users;
-- 查询username和password对应的数据
-- SELECT username, password FROM my_db_01.users;
-- 查询不重复的值SELECT DISTINCT(相同的数据只会返回一次)
-- SELECT DISTINCT userstatus FROM my_db_01.users;

-- insert into 插入
-- insert into users (username, password, userstatus) values ('daming','daming123','0')

-- update 修改
-- 修改id为1的用户的密码
-- update users set password='12345' where id=1
-- 修改id为2的用户的密码和状态
-- update users set password='12345',userstatus=1 where id=2

-- delete 删除
-- 一定要注意where,不然会把整张表都删掉
-- delete from users where id=3

where, and, or的使用

image-20240513171604108

order by排序

1
2
3
4
5
-- asc按id正序排列,不添加参数默认也为正序排列
SELECT * FROM my_db_01.users order by id asc

-- desc按id倒序排列
SELECT * FROM my_db_01.users order by id desc

count()统计数目

1
2
-- 统计所有userstatus=0的数据的总数目
select count(*) from users where userstatus=0

as 为列名称设置别名

1
2
-- count会以total为名显示(total可以省略)
select count(*) as total from users where userstatus=0