ejs模板
模板就是一个用于生成html文件的有独特语法的文件
<html>
<head></head>
<body>
<!-- 此处就是模板语法 -->
<div>${user.name}</div>
</body>
</html>
然后我们自己写个函数将他转换成html,如:
// 定义个渲染函数,用于将模板转换成html
function render(templateName, params) {
// 转化过程写在这里
}
// 调用渲染函数,将模板和对应的数据传入函数
// 函数将返回html内容
let html = render('template.rgb', {user: {name: '法外狂徒张三'}})
上述模板最终会被翻译成html,如下:
<html>
<head></head>
<body>
<!-- 此处就是转换出来的内容 -->
<div>法外狂徒张三</div>
</body>
</html>
模板引擎是什么
模板引擎就是用于解释翻译模板的工具,例如上述例子中的render函数,应该由模板引擎提供
模板引擎ejs
ejs 是个被许多框架使用的模板引擎
安装与使用
# 安装
npm install ejs
使用流程
创建模板文件.ejs
引入ejs模块
编译模板
返回html内容
代码参考《ejs-project》的 ejs-demo.js
express 中集成 pug
代码参考《ejs-project》的 ejs-express.js
express
ajax异步请求
两种情况可以考虑使用 ajax
网页中为了发送请求的同时,允许用户继续和页面进行交互
不希望使用 form 表单,导致页面跳转
如何发起 ajax 网络请求
发起 ajax 请求的工具 我们称为 Http Client(http客户端)
使用浏览器自带的 ajax
XMLHttpRequest
// 创建 xhr
const xhr = new XMLHttpRequest()
// 打开连接
// xhr.open(requestMethod, url, async)
// requestMethod 请求方法 get 或 post
// url 请求地址
// async 是否异步 默认为 true,若为 false 则,xhr.send 在收到服务器响应后才会返回
xhr.open('post', '/play', true)
// 监听收到服务器响应事件
xhr.addEventListener('load', ev=>{
ev.currentTarget // 该对象中可以获取服务器响应数据
})
// 发送请求
xhr.send()
fetch
// fetch api: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0
// fetch 是一个 promise 函数
// fetch(url, options)
// url 请求路径
// options 配置对象
fetch(`/doGet?name=${fd.get('name')}&age=${fd.get('age')}`, {
method: 'GET', // 请求方法 *GET, POST, PUT, DELETE, etc.
mode: 'same-origin', // 跨域策略 no-cors(不跨域), *cors(跨域), same-origin(同源)
// referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
// body: JSON.stringify(data) // 数据体 body data type must match "Content-Type" header
}).then(res => {
// res 服务器的响应对象
console.log(res)
// res.text() 用字符串格式读取服务器返回的结果
// 该函数返回的是 promise
// return res.text()
return res.json()
// res.json() 用json对象读取返回值
// res.blob() 用blob对象(二进制数据对象)读取返回值
}).then(text => {
console.log(text)
})
http
常用请求头和响应头
请求头就是浏览器上的 request header
响应头就是浏览器上的 response header
Request Method:请求方法
名称 | 特点 | 服务器取值方式 |
get | 传递参数在url上可见 | 通过url.parse(req.url).query取值 |
post | 传递参数在url上不可见 | 通过req.body取值 |
Content-Type:内容类型
有常见的以下几种类型
名称 | 含义 |
text/html | html文档 |
text/plain | 纯文本 |
image/jpeg | .jpg图片 |
image/png | .png图片 |
text/xml | xml文档 |
application/json | json数据 |
application/x-www-form-urlencoded | 表单url编码数据,例如:a=1&b=2&c=3 |
multipart/form-data | 上传文件时常看到 |
charset=utf-8 | 指定编码集 |
Status Code:状态码
状态码只指示请求或响应状态,不对业务负责
常见状态码
代码 | 含义 |
200 | 请求成功 |
302 | 资源重定向 |
304 | 资源未改变,使用浏览器缓存 |
400 | 客户端请求的语法错误,服务器无法理解 |
403 | 权限不足 限制访问 |
404 | 资源未找到 |
500 | 服务器内部错误 |
503 | 请求超时 |
mongodb
Mongoose
连接数据库
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test2', { useNewUrlParser: true })
const db = mongoose.connection; // 获取数据库连接对象
db.on('error', console.error.bind(console, 'connection error:')); // 绑定连接错误事件
db.once('open', function () { // 绑定一次打开数据库连接事件
// we're connected!
console.log("we're connected!")
})
其中 mongodb://localhost:27017/test2 是连接字串
localhost 是数据库地址,可以换成对应的ip地址
27017 是数据库端口
test2 是数据库库名
也可以追加登录数据库的用户名和密码,如:mongodb://admin:111111@localhost:27017/test2
admin: 账号111111: 密码
连接字串,可以参考 mongobooster 的是怎么写的。见图《如何查看数据库连接字串.png》
创建数据库表结构图
表结构图是用来设计表的对象,其定义了表字段的属性。
就像是一个工程图纸,根据图纸创建出来的就是具体的表。
语法:
const { Schema } = require('mongoose')
const gameSchema = new Schema({
name: { type: String, index: true, unique: true }, // 创建唯一标识
platform: { type: String, index: true }, // 创建索引
price: { type: Number, index: true },
createTime: { type: Date, default: Date.now(), index: true },
updateTime: { type: Date, default: Date.now(), index: true }
// 试试将时间的类型改成数字
// createTime: { type: Number, default: Date.now(), index: true },
// updateTime: { type: Number, default: Date.now(), index: true }
})
表结构中,通常都会有 createTime 和 updateTime 用来描述记录创建事件和修改事件
如何定义字段:
直接指定类型,字段类型是必须的,所以这种是最基础的定义方法,例如:
const gameSchema = new Schema({
name: String // 创建一个叫name的字段,类型是 String
})
指定字段初始值,例如:
const gameSchema = new Schema({
createTime: { type: Date, default: Date.now() }
})
创建索引 index 通常在建表的时候,请将需要用于查询的字段添加上索引 index
什么是索引呢?数据库有一个字典,如果创建索引,数据库会在字典上做记录,那么查询的时候,数据库会查询得更快。
const gameSchema = new Schema({
name: { type: String, index: true } // 创建一个叫name的字段,类型是 String
})
创建唯一索引 unique 唯一索引是指数据库的某个字段,不接受重复的值。例如有个 name 字段添加了唯一索引,那么这个表里面,name 是不能重复的。
const gameSchema = new Schema({
name: { type: String, unique: true } // 创建一个叫name的字段,类型是 String
})
创建数据模型
数据模型对应的就是数据库中的表,当我们基于数据模型进行数据保存的时候,如果数据库还不存在这张表,那么就会自动创建表。
所有数据库操作,都可基于数据库模型进行操作的
const { model } = require('mongoose')
const Game = model('game', gameSchema) // 第一个参数影响表名,且大小写不敏感
数据库基础操作
保存 save
保存操作用于对整个对象进行修改并保存。
代码里一顿操作猛如虎,但是不保存,就不会进入数据库
先创建用于保存的数据对象
const tlou = new Game({ // 直接 new 一个之前创建的数据模型
name: 'The Last Of Us',
platform: 'PS',
price: 400
})
如果创建对象的时候有 _id 字段,那么保存功能会覆盖数据库中有指定id的数据
如何理解呢?平时保存文件是,文件名相同的就会被覆盖掉。这里的 _id 相当于数据对象的“文件名”,如果数据库有这个id的数据,那么保存的时候就会被覆盖掉。
保存:
tlou.save((err, tlou) => {
if (err) console.error(err)
else console.log(tlou)
})
保存方法,接受一个 callback: (err, savedModel) => {} ,其中 savedModel 是保存后数据库返回的数据对象,如果是新增数据,会包含一个数据库分配的id
删除 delete
删除符合条件的数据,通常以id为查询条件进行删除,当然也能以其他字段为查询条件进行删除。
// 单个删除
Game.deleteOne({ _id: '5f0aa810a5770b0960a33314' }, (err) => {
if(err) console.error(err)
})
// 批量删除
Game.deleteMany({ _id: { $in: ['5f0aac77c783dc380cf15eef', '5f0aac77c783dc380cf15eee'] } }, (err) => {
if (err) console.error(err)
})
修改 update
修改能找到与指定条件相匹配的数据,修改指定的字段为指定的值
与save的区别,在于,save是整个覆盖数据,而update是修改指定的字段
普通修改最为普通,没有特点的update
Game.updateOne({ _id: '5f0aadc5dd9d313ee070fcee' }, {
name: 'TheLastOfUs' // 要修改的字段和值
}, (err, raw) => {
if (err) console.error(err)
else {
console.log('修改成功')
console.log(raw) // 修改结果文档
}
})
通常update的查询条件是id,updateOne的第二个参数就是指定要修改的字段和值
修改并查询这是个可以应对并发的方法,是查询和修改两个功能的合体。
语法:
// 查询并修改:具备数据库原子性
Game.findOneAndUpdate({ name: 'Super Mario Bros' }, {
platform: 'SFC',
price: 300,
updateTime: Date.now()
}, {
new: true, // 是否返回修改后的数据
upsert: true, // 是否不存在就增加一条新的
// 注意:upsert的时候不会去修改 updateTime 和 createTime 这种创建时自动赋值的数据,所以需要设置setDefaultsOnInsert
setDefaultsOnInsert: {
createTime: Date.now()
}
}, (err, doc, res) => {
if (err) console.error(err)
else {
console.log('查询并修改完成')
console.log(doc) // 返回查询到的数据
console.log(res)
}
})
第一个参数是查询条件
第二个参数是要修改的字段和值
第三个是可选附加功能,可以不填
第四个是callback,用来获取查询到的数据
对数字进行加减
如果说对数字进行直接覆盖,可以使用 update 或 findOneAndUpdate。但之前例子中,如:从钱包中扣钱或存钱,抢红包,这些场景就不能直接覆盖数字,而是在其现有基础上进行加减。
以update为例,语法:
// 单独对数字的修改
Game.updateOne({ name: 'Super Mario Bros' }, {
platform: 'SFC', // 这里依然可以修改其他字段的值
$inc: { price: -20 } // 使用$inc,增加数字的值,可以是负数
}, (err, raw) => {
// err 如果数据库报错会有err,否则就不存在
// raw 修改结果
// 修改的callback方法,基本没有用
})
第二个参数是修改的字段和值,其中只要声明一个 $inc 就可以了 inc是 increase(增加) 的缩写
查询 exists & find
exists 查询指定条件的数据是否存在
语法:
// 查询 name 字段为 The Last Of Us 的数据是否存在
Game.exists({ name: 'The Last Of Us' }, (err, res) => {
if (err) console.error(err)
console.log('exists: ' + res) // 这里的res是个boolean值,true代表存在,false代表不存在
})
find 条件查询,如果只查询一个值,可以使用 findOne方法
基础用法
语法:
// 查询 name 中包含 “2” 的数据
Game.find({ name: { $regex: /^(\s|\S)*2(\s|\S)*$/ } }, (err, docs) => {
if (err) console.error(err)
else console.log(docs)
})
第一个参数是查询条件
第二个参数是 callback: (err, games)=>{}
err:如果数据库报错,会包含错误信息
docs:查询出来的数据库数据,是个数组;如果用的 findOne 方法,docs是一个数据库数据对象,而不是数组
使用$where做查询条件
语法:
Game.find({
name: { $regex: /^(\s|\S)*2(\s|\S)*$/ }
$where: 'this.createTime.getTime()!=this.updateTime.getTime()'
}, (err, docs) => {
if (err) console.error(err)
else console.log(docs)
})
参数与基础用法相同
在第一个参数,查询条件中,多了一个 $where 指令。该指令接收一个字符串参数,该字符串是一个js表达式。表达式中的 this 代表正被查找的当前数据对象。所以可以通过 this.field 来引用自身数据的字段来进行逻辑判断。
用处:如果需要取表里两个及其以上字段来做逻辑判断时,就需要用 $where
举例:以 game 表为例。如果要查找 createTime 和 updateTime 相同的数据(也就是查找从来没被修改过的数据);或者查找 updateTime - createTime 小于三天的数据(也就是查找自创建以来,3天内都未作修改的数据)
查询排序 sort 与分页 skip & limit (size)
排序:
Game.find({},
null, // '-updateTime' 排除updateTime字段 'name'选择 name字段
{
sort: {
price: 1, // 按价格降序和最新数据排序
updateTime: -1 // 大于零 升序;小于零 降序
}
}, (err, docs) => {
console.log(docs)
})
注意:该方法参数与基础查询方法不一样
第一个参数:查询条件
第二个参数:选择返回的字段,类似sql中的select,null的话就返回所有表数据
参数值是个字符串,例如 'name platform price',字段间用空格隔开
字段名前加上 - 号,代表排除某字段,如:'-updateTime -createTime',这样数据库就不会返回 updateTime 和 createTime 的值。
选择字段和排除字段不能同时存在,也就是不能这么写 name -updateTime
第三个参数:可选配置
sort:按字段排序,值大于零升序,小于零降序
第四个参数:callback
分页 skip & limit
skip:查询时候,跳过多少条数据
limit:返回数据的总数
举例:如果我们按每页5条数据进行取值,第二页的数据该怎么查询呢?语法:
Game.find({}, // 分页的时候也可以加查询条件
null,
{
sort: {
price: 1, // 按价格降序和最新数据排序
updateTime: -1 // 大于零 升序;小于零 降序
},
skip: (2-1)*5, // 公式:skip:(page-1)*size
limit: 5 // 公式:limit:size
}, (err, docs) => {
console.log(docs)
})
写法基本和排序相同,但多了两个字段 skip 和 limit
如果令当前页为 page;每页记录数为 size;那么参数 skip 和 limit 就应该是:
skip: (page-1)*size,
limit: size
websocket
websocket 是一个服务器和客户端双向通信的协议(双工通道),主要用作服务器向客户端主动推送数据。
express 服务器也可以实现 websocket 协议,可以使用 express-ws 包
websocket 更常见的使用方法是做一个独立服务器,如直播弹幕服务器,聊天服务器等
完成上述服务器的 node.js 的框架有 socket.io
认识node,fs,promise
核心模块
模块的概念,当讲到引入第三方模块的时候,再进行深入学习。
简单理解模块的概念:一个封装好的软件包,开箱即用。
fs(file system)文件系统模块(重点)
node.js中的文件系统,是一个工具,用于在操纵系统上对文件和目录进行操作,基本操作如:创建文件夹,删除文件,读取文件,写入文件等。
let fs = require('fs')
fs.readFile('./a.txt', (err, data)=>{
if(err) {
console.error(err)
} else {
console.log(data)
let str = data.toString()
console.log(str)
}
})
http协议通信模块(重点)
let http = require('http')
let server = http.createServer()
os(operating system)操作系统模块
let os = require('os')
// 获取当前几期的CPU信息的
console.log(os.cpus())
path 路径模块
let path = require('path')
//获取一个路径中的扩展名部分 extname 扩展名
console.log(path.extname('./data/hello.txt'))
event 事件模块
该模块可以创建收发事件的实体
assert 断言
定义一个笃定的言论就叫断言,多用于做函数的参数判断,例如:加函数function add(x, y) 需要两个参数,那么就可以使用断言判断调用函数时参数的合法性
cluster 集群模块
集群模块可以让js代码在多个cpu核心上运行多个副本
常见全局变量
__dirname // 执行代码所在的目录
__filename // 执行代码的文件名称
console // 控制台对象
exports // 导出方法
require // 导入方法
global === globalThis // 全局对象,类似浏览器上的 window 对象
服务器理论,建立node.js服务器
第一个服务器
术语:
ip: 计算机在网络中的地址 其中本机地址为 127.0.0.1
一般情况可以使用 localhost 代表 127.0.0.1,若不成功则可以修改 C:\Windows\System32\drivers\etc\hosts 来定义 localhost
端口号: 一个应用程序与互联网连接通信的出入口
协议: 例如 http https 都是协议名,规定网络通信如何进行
http 协议默认端口: 80
https 协议默认端口: 443
服务器获取参数
术语:
url: 统一资源定位符, 其实就是一个网络资源在网络中的地址路径
uri: 统一资源定位标识, 任意资源在系统中的一个唯一标识符,并不局限于网络中。例如: 身份证号就是每个人的uri
路由器 Router
路由(route)
什么是路由?
动词: 通过不同请求地址的路径,分发请求
分发请求时,请求可能被重新分配到另一个服务器,或者被分配到自己服务器的另一个应用程序,又或者分配给接收请求的服务器某个处理函数等
名词: 包含访问地址url,访问参数等一系列和路径相关的信息
以下代码中的 if else 代码块就是个简单的路由器
server.on('request', (req, resp) => {
resp.setHeader('Content-Type', 'text/html;charset=utf-8')
let url = req.url
// 这段if else代码块,可以看作是个简单的路由器
if (url === '/') {
resp.end('感谢您的访问')
} else if (url === '/text') { // 读取一段文本
resp.setHeader('Content-Type', 'text/plain;charset=utf-8')
fs.readFile('./a.txt', (_, data) => {
resp.end(data)
})
} else if (url === '/favicon.ico') { // 读取一个图片
resp.setHeader('Content-Type', 'image/jpeg')
fs.readFile('./favicon.ico', (_, data) => {
resp.end(data)
})
} else {
resp.end('404 资源没找到')
}
})
请求文件
静态资源服务器
静态资源: 不会产生变化的文件
重定向
重定向就是当用户访问一个地址时,服务器返回另一个地址,然后浏览器重新访问该地址,这个过程称为重定向
例如:
服务器有个地址 /img/1.jpg
用户访问该地址 /img/1.jpg
服务器重新返回一个地址 /image/1.jpg
客户端重新访问地址 /image/1.jpg
以上过程称为重定向
重定向的一个特点就是地址栏会发生变化
基于路由创建简单的静态资源服务器
静态资源服务器就是一个只提供资源下载的服务器,常见的产品有nginx,apache等。之所以叫做“静态”是因为资源是静态的,不受服务器逻辑产生变化。有静态肯定就有动态,之后讲模板引擎的时候会进行说明。
也有使用node.js搭建的静态资源服务器。
主要方法:
指定静态资源文件夹
将路由地址匹配到文件目录
node.js模块化
什么是一个 node.js 项目
一个目录,若使用了 npm init 命令创建了一个 package.json 文件,那么我们称该目录为一个 node.js 项目
package.json
package.json 是 node.js 用于描述项目的文件
{
"name": "npm-demo", // 项目名称
"version": "1.0.0", // 项目版本号
"description": "第一个用来演示npm命令的项目", // 项目的描述
"main": "index.js", // 项目最终导出的文件
"scripts": { // 脚本
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node main.js" // 脚本中的内容实际就是项目目录下运行的命令
},
"author": "", // 作者
"license": "ISC", // 证书
"dependencies": { // 安装的依赖
"axios": "^0.27.2",
"bootstrap": "^5.1.3",
"jquery": "^3.6.0"
},
"devDependencies": { // 开发依赖
"@types/node": "^16.11.43"
}
}
package-lock.json
package-lock.json 是 npm 安装依赖包时的版本信息,它记录了安装依赖包时,当时的版本号
es模块化
es 模块化的语法 浏览器天然支持
node.js 需要较新的版本才能支持
导出模块
// export 导出时要同时进行声明
export let a = 1
export function fn(){}
// export default 导出一个内容 且 必须写在代码最下面
export default { a, fn }
导入模块
// 导入模块语法 path 路径 可以是一个文件路径 也可以是一个模块名称
// import ... from <path>
// 导入 export 的内容
import {age, sex} from './module.js'
// 导入 export default 的内容
import m1 from './module1.js'
// 同时导入 export default 和 export 的内容
import m1, {age, sex} from './module1.js'
// 使用 通配符 * 导入模块的所有内容
// as 就是 “当作” 的意思
// 下面的引入语句相当于: 将模块 m1.js 当作 m1 变量导入
import * as m1 from './m1.js'
// 若已有一个变量和模块中的内容同名
// 那么导入的时候需要添加别名
import {sex as se, age as ag} from './m1.js'
let age = 40
let sex = 'female'
node.js 上使用 es 模块化的方法
修改 package.json 文件,添加 "type": "module"
浏览器上使用 es 模块化的方法
浏览器上使用 es 模块化的条件有两个
script 标签需要添加 type 属性, 如: <script type="module"></script>
网页需要通过服务器访问