一、安装 Nodejs
1、下载Nodejs
下载网址:https://nodejs.org
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZ5Fddk9-1660484273428)(E:\3.插入到md的图片\图片.png)]
2、npm 包管理器
安装好了 nodejs 后,npm 就已经在电脑中
npm 仓库地址:https://www.npmjs.com/
npm -v----查询版本号
3、切换 npm 源
3.1使用nrm 管理 npm 镜像源
nrm 是一个 npm 源管理器,允许你快速地在 npm 源间切换。npm 默认情况下是使用 npm 官方
源(npm config list 来查看),如果直接修改 npm 源,如果后续需要连接到官方源才能工作,
这样来回切换源就变得麻烦了,nrm 通过简单的命令就可以解决此问题。
# 全局安装
npm i nrm -g
# 查看可用源
nrm ls
# 切换
nrm use 名称(npm)
4、生成 JSON 配置文件
# 初始化生成 package.json 文件 → 项目中使用 npm 安装软件的记录文件
npm init -y[不询问]
packename 包名(包名也不能和已经存在的包同名 wu-jquery)
version 版本
description 描述
main 入口文件
scripts 支持的脚本,默认是一个空的 test
keywords 关键字,有助于在人们使用 npm search 搜索时发现你的项目
author 作者
license: 版权许可证(默认:ISC)
dependencies 在生产环境中需要用到的依赖 –- 项目上线时会将 dependencies 下的包一起打
包, 项目上线以后依然可用.
devDependencies 在开发、测试环境中用到的依赖 -- 在本机开发时此时需要用到, 项目上线时不会
将 devDependencies 下的包打包, 也就是项目上线后不需要使用.
5、安装模块
# 安装模块 最新版本
npm install 模块名 或 npm i 模块名
# 安装模块 指定版本
npm install 模块名@版本号 或 npm i 模块名@版本号
# 卸载已安装模块
npm uninstall 模块名 或 npm uni 模块名
# 查看全局 node_modules 的地址
npm root -g
## 安装参数
npm i 模块名 --save模块名
npm i 模块名 --S记录生产环境所需模块并写入 dependencies(没有指定该参数时默认使用该参数)
pm i 模块名 --dev模块名
npm i 模块名 --D记录生产环境所需模块并写入 dependencies(没有指定该参数时默认使用该参数)
6、查看当前安装的模块
# 查看本项目已安装模块
npm list
# 查看包可用版本
npm view jquery versions
npm i -S jquery@2.*
7、自定义脚本命令
通过 package.json 文件中的 scripts 自定义脚本命令
{
"scripts": {
"test": "echo hello"
}
}
# 运行命令
npm run test
8、自动重启应用
# 全局安装 nodemon
npm i -g nodemon
# 执行 node 脚本
nodemon app.js
// 一个 web 服务,启动后会在内存在运行,而我们修改的是磁盘中的文件
// 修改后的文件不会立即更新到服务中,手动重启,在生产环境中正常,开发环境如果频繁的这样操作,
开发效率无从谈起。需要一个能够监听文件的修改,一旦有修改文件就是自动更新到内存服务中
// 启动文件
// 使用 nodemon 之前运行 nodejs 文件 node 文件名.js
// 使用 nodemon 之后运行 nodejs 文件 nodemon 文件名.js
二、 模块化
➢ 核心模块 - 安装 nodejs 时自带的模块
➢ 第三方模块 - 需要手动通过(npm/yarn)来进行安装
➢ 自定义模块 - 开发者自己编写的模块 (一个文件就是一个模块)
1.模块导入导出
导出 module.exports/exports
导入 require require 导入是以单例模式,导入相同的对象,全局只有一个实例
2、 常用内置模块
2.1、path 模块
path 模块用于操作文件和文件夹的路径
var path = require('path');
//path 内置模块, 处理文件路径(拼接)
var str1 = 'day01-nodejs';
var str2 = '../static';
var str3 = 'css';
var res = path.join(str1,str2,str3)
//一个文件的全路径( D:/xx/yy/zz.html )
//__dirname 当前文件所在文件夹的路径( 默认从盘符开始 )
var res = path.join( __dirname,'static','index.html' );
//拼接json文件的全路径(加上盘幅的那种)
var jsonfile = path.join( __dirname,'json','index.json' );
2 2、 url 模块
url 模块用于解析 url 地址字符串,URL 字符串是结构化的字符串,包含多个含义不同的组成
部分。 解析字符串后返回的 URL 对象,每个属性对应字符串的各个组成部分
var url = require('url');
//url 是内置模块, 解析url地址
//http://baidu.com/xxx/index.html?user=admin&pass=1234
var str = 'http://baidu.com/xxx/index.html?user=admin&pass=1234&email=1788847773@qq.com';
var res = url.parse( str );//得到的是一个对象形式的,找到里面的search
res.search======//?user=admin&pass=1234&email=1788847773@qq.com';
var res = querystring.parse( res.search.slice(1) )//user=admin&pass=1234&email=1788847773@qq.com';
console.log(res.user,res.pass);
//自己封装的函数
//?user=admin&pass=1234&email=1788847773@qq.com';
function parseSearch(data){
var arr1 = data.split('&'); //['user=aedmin','pass=1234']
var obj = {};
arr1.forEach((item)=>{
var arr2 = item.split('='); //['user','admin']
obj[arr2[0]] = arr2[1];//给obj对象添加新属性
})
return obj;
}
console.log( parseSearch( res.search.slice(1) ) );
2.3 、querystring 模块
用于解析和格式化 URL 查询字符串, 可以方便将 url 查询字符串解析为对象,或者将对象转
换为查询字符串形式.
const querystring = require('querystring')
# query 字符串转为对象
querystring.parse('foo=bar&abc=xyz')
# 对象转为 query 字符串
querystring.stringify({ foo: 'bar',abc: 'xyz'})
2.4 、fs 模块
fs 模块提供了用于与文件进行交互相关方法(读取文件内容,写入数据到文件)
const fs = require('fs')
# 读取文件中数据
fs.readFileSync(文件, 'utf8’)
# 写入数据
fs.writeFile(文件路径,数据)
# 检查文件是否存在 返回 true/false
fs.existsSync(path)
# 删除文件
fs.unlink(文件,err=>{})
var path = require('path');
var fs = require('fs'); // file system
//path 内置模块, 处理文件路径(拼接)
//fs 内置模块, 处理文件(做文件内容的读/写)
var str1 = 'day01-nodejs';
var str2 = '../static';
var str3 = 'css';
var res = path.join(str1,str2,str3)
//一个文件的全路径( D:/xx/yy/zz.html )
//__dirname 当前文件所在文件夹的路径( 默认从盘符开始 )
var res = path.join( __dirname,'static','index.html' );
//拼接json文件的全路径
var jsonfile = path.join( __dirname,'json','index.json' );
if( fs.existsSync( jsonfile ) ){ //如果条件成立, 说明 res 这个路径是正确的( 路径对应的文件是存在的 )
//1.读取文件内容
var jsondata = JSON.parse( fs.readFileSync( jsonfile ) ) ;
//2.修改内容( 在计算机内存中修改 )
jsondata.push( { "name":"张三"+(jsondata.length+1), age:20+jsondata.length+1 } );
//3.写入新的内容到文件
fs.writeFileSync( jsonfile, JSON.stringify( jsondata ) );//fs.writeFileSync() 会覆盖文件原有的内容
}
console.log(res);
运行node 文件名
[{"name":"张三01","age":21},{"name":"张三11","age":22},{"name":"张三21","age":23},{"name":"张三4","age":24}]
三、 页面渲染模式
3.1、ssr(服务器端渲染)
ssr (Server Side Rendering) :传统的渲染方式,由服务端把渲染的完整的页面响应给客户端。这样减少了一次客户端到服务端的一次 http 请求,加快相应速度,一般用于首屏的性能优化。
优缺点:
1、利用 SEO(搜索引擎)
2、页面渲染时间短
3、服务器压力过大
3.2、csr(客户端渲染)
CSR(Client Side Rendering):是一种目前流行的渲染方式,页面由 js 渲染,js 运行于浏览器端,所以称客户端渲染。
优缺点:
1、前后端并行开发,开发速度提升
2、首屏渲染时间比较长(首屏加载速度慢)
3、不利于 SEO
四、创建 web 服务
1.使用http模块创建web服务
// 引入http模块
// 如果要希望使用 http 模块创建 Web 服务器,则需要先导入它:
var http = require("http")
var path = require("path")
var fs = require("fs")
// 创建web服务并监听在指定端口,每当收到来自客户端的请求时候,都会自动调用回调函数
http.createServer((req, res) => {
//req 请求对象 , 包含了和本地请求相关的所有信息
//res 响应对象 , 用来给客户端响应数据
// url发送请求的网址
// 服务器的静态资源托管
console.log(req.url);//-----返回/因为后面没有网址,默认就是/
// 返回响应数据给客户端
// res.end("hello client")//index.html //favicon.ico
// 判断前端发的什么请求,来返回什么数据
// 返回响应数据给客户端,需要做一个if判断,来决定返回什么类型
if (req.url != "/favicon.ico") {
// if (req.url == "/index.html") {
// // 盘---文件夹地址,想要的url地址
// var filepass = path.join(__dirname, "static", req.url)
// // 用end发送给客户端
// res.end(fs.readFileSync(filepass))
// }
// else if (req.url == "/index.css") {
// var filepass = path.join(__dirname, "static", req.url)
// res.end(fs.readFileSync(filepass))
// }
// else if (req.url == "/font/iconfont.css") {
// var filepass = path.join(__dirname, "static", req.url)
// res.end(fs.readFileSync(filepass))
// }
// else if (req.url == "/font/iconfont.woff2?t=1638583891245") {
// var filepass = path.join(__dirname, "static", "/font/iconfont.woff2")
// res.end(fs.readFileSync(filepass))
// }
//可以简写成这样-------------------------------------------------------------------------------------------------
if (req.url == "/font/iconfont.woff2?t=1638583891245") {
var filepass = path.join(__dirname, "static", "/font/iconfont.woff2")
res.end(fs.readFileSync(filepass))
} else {
var filepass = path.join(__dirname, "static", req.url)
// 用end发送给客户端
res.end(fs.readFileSync(filepass))
}
}
// 服务器api接口--打开网页之后,在操作的时候起作用
if (req.url == "/useradd") {
res.end({ code: 200, msg: "添加成功!" })
} else if (req.url == '/login') {
res.end({ code: 200, msg: "登陆成功!" })
}
}).listen(3000, () => {//什么都没写,默认就是/
console.log("服务器运行在http://localhost:3000");
})
2.使用express模块-创建服务
2.1托管项目
// 1.先用npm iinit -y生成package文件
// 2.再用npm i express安装这个包
// 引入express模块
var express = require('express');
var path = require('path');
var fs = require('fs');
const { arrayBuffer } = require('stream/consumers');
// 2.创建express实例对象
var app = express()
// 静态托管
app.use(express.static("static"))
// 处理post参数// 解析json参数
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
//这里是注册用户----------------------------------------------------------------------------------------------
//这里是登陆代码-------------------------------------------------------------------------------------------
// 3.监听端口
app.listen(3000, () => {
console.log("服务器监听在http://localhost:3000");
})
2.2.注册用户
//获取 注册按钮
var registerBtn = document.querySelector('button');
//获取 表单
var inputs = document.querySelectorAll('input');
//注册事件
registerBtn.onclick = function(){
//获取表单数据,发起ajax请求
if( inputs[0].value &&
inputs[1].value &&
inputs[2].value &&
inputs[3].value ){
if( inputs[1].value == inputs[2].value )
{
ajax('post','/api/user/register',{
phone: inputs[0].value,
pass: inputs[1].value,
checkpass: inputs[2].value,
email:inputs[3].value
}).then((res)=>{
if( res.code == 200 ){
// alert(res.msg);
// location.href = './login.html'
}else{
alert(res.msg);
}
}).catch((error)=>{
console.log(error);
})
}else{
alert('两次输入密码不一致')
}
}else{
alert('注册信息不完整');
}
}
备注:// 处理post参数// 解析json参数加上这两个代码
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// 注册用户
app.post('/api/user/register', (req, res) => {
// req:请求对象,用户新增数据
// 怎么拿到参数,前段传过来的数据
console.log("req.body", req.body);//undefined了
var { checkpass, ...user } = req.body
// 1.拼接路径
var filepath = path.join(__dirname, "json", "user.json")
// 读取出来是个二进制数据,所以要转换为JSON格式的对象
var userlist = JSON.parse(fs.readFileSync(filepath))
// 传过来的确认密码和密码是一个值,可以进行解构
// 查看userlist里面有没有user
// 使用数组提供的indexOf filter some find findIndex 方法
// indexOf 不能比较对象
var index = userlist.findIndex((item) => {
return user.phone == item.phone
})
if (index != -1) {//代表找到了
res.send({ code: 400, msg: "用户已存在,注册失败" })
} else {
userlist.push(user)
// 写入要转为字符串
fs.writeFileSync(filepath, JSON.stringify(userlist));
// 响应对象
res.send({ code: 200, msg: "注册成功!" });
}
})
2.3.登陆账号
//获取 登陆按钮
var loginBtn = document.querySelector('button');
//获取 表单
var inputs = document.querySelectorAll('input');
//登陆事件
loginBtn.onclick = function(){
//获取表单数据,发起ajax请求
if( inputs[0].value && inputs[1].value ){
ajax('get','/api/user/login',{
phone: inputs[0].value,
pass: inputs[1].value
}).then((res)=>{
if( res.code == 200 ){
location.href = './index.html';
}else{
alert(res.msg);
}
}).catch((error)=>{
console.log(error);
})
}else{
alert('注册信息不完整');
}
}
// 登陆用户
app.get('/api/user/login', (req, res) => {
//req 请求对象 , 从 req.query 获取get参数
//res 响应对象 , 通过 res.send 发送数据到客户端(浏览器)
// 怎么拿到参数
// 解构一下账号和密码
var { phone, pass } = req.query;
// 1.先读取看数据库有没有
var filepath = path.join(__dirname, 'json', 'user.json');
var userlist = JSON.parse(fs.readFileSync(filepath));
console.log("req.query", req.query);//req.query { phonde: '222', pass: '111' }
// if (req.query.phone == "222" && req.query.pass == "111") {
// res.send({ code: 200, msg: "登陆失败" })
// } else {
// res.send({ code: 400, msg: "登陆成功" })
// }
// 在userlist里面查找
// phone和pass是前段传过来的
var index = userlist.findIndex((item) => {
return item.phone == phone && item.pass == pass;
});
if (index != -1) {
res.send({ code: 200, msg: "登陆成功!" });
} else {
res.send({ code: 400, msg: "登陆失败!" });
}
})
2.4.主页功能
tools封装函数
function ajax( method,url,data ){
return new Promise((resolve,reject)=>{
//1.创建xhr对象
var xhr = new XMLHttpRequest();
//2.设置请求参数
xhr.open(method, method=='get'? (url+'?'+toSearchString(data) ) : url );
console.log('参数', toSearchString(data) );
//3.发送请求
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
xhr.send( method=='post' ? toSearchString(data) : null );
//4.设置异步回调
xhr.onreadystatechange = function(res){
if( xhr.readyState == 4 && xhr.status == 200 ){
resolve( JSON.parse( xhr.responseText ) );
}
}
})
}
//对象转查询字符串
function toSearchString(obj){
var str = '';
for(var key in obj){
str += (key + '=' + obj[key] + "&")
}
return str.slice(0,str.length-1);
}
//渲染数据到dom
function render(data,page,maxpage){
//获取table-body
var tableBody = document.querySelector('.table-body');
//获取page页码标签
var pageEle = document.querySelector('.page');
//渲染数据到tableBody中
tableBody.innerHTML = data.map((item,index)=>{
return `<ul class="row">
<li>${item.email}</li>
<li>张三</li>
<li>${item.phone}</li>
<li>编辑组</li>
<li>是</li>
<li>正常</li>
<li>
<span class="iconfont icon-bianji editicon" id="101"></span>
<span class="iconfont icon-shanchu deleteicon" id="101"></span>
</li>
</ul>`
}).join('')
//渲染页码
pageEle.innerHTML = page + '/' + maxpage;
}
//获取搜索按钮
var searchBtn = document.querySelector('.search');
//获取搜索输入框
var input = document.querySelector('.searchbox input');
//获取下一页按钮
var next = document.querySelector('.next');
//获取上一页按钮
var prev = document.querySelector('.prev');
//获取table-body
var tableBody = document.querySelector('.table-body');
var page = 1;//页码
var count = 5;//每页条数
var maxpage = 1;//最大页码
window.onload = function(){
ajax('get','/api/user/search',{ phone: input.value,page,count }).then((res)=>{
if( res.code == 200 ){
maxpage = Math.ceil( res.total/count );
render( res.list,page,maxpage );//渲染列表
}
})
}
//给 搜索按钮 绑定点击事件
searchBtn.onclick = function(){
ajax('get','/api/user/search',{ phone: input.value,page,count }).then((res)=>{
if( res.code == 200 ){
maxpage = Math.ceil( res.total/count );
render( res.list,page,maxpage );//渲染列表
}
})
}
//给下一页按钮绑定点击事件
next.onclick = function(){
if( page + 1 >= 1 && page + 1 <= maxpage )
{
ajax('get','/api/user/search',{ phone: input.value,page: ++page ,count }).then((res)=>{
if( res.code == 200 ){
render( res.list,page,maxpage );//渲染列表
}
})
}
}
//给上一页按钮绑定点击事件
prev.onclick = function(){
if( page - 1 >= 1 && page - 1 <= maxpage )
{
ajax('get','/api/user/search',{ phone: input.value,page: --page ,count }).then((res)=>{
if( res.code == 200 ){
render( res.list,page,maxpage );//渲染列表
}
})
}
}
//给table-body绑定点击事件, 处理删除
tableBody.onclick = function(e){
e = e || window.event;
var ele = e.srcElement || e.target;
//if( ele.className.indexOf('deleteicon') != -1 ){ //点击了 删除按钮
if( ele.classList.contains('deleteicon') ){ //点击了 删除按钮
ajax('post','/api/user/delete',{phone: ele.parentNode.parentNode.children[2].innerHTML , page,count}).then((res)=>{
console.log(res);
maxpage = Math.ceil( res.total/count );
render( res.list,page,maxpage );//渲染列表
})
}
}
//1.引入express模块
var express = require('express');
var path = require('path');
var fs = require('fs');
//2.创建express实例对象
var app = express();
//app.use() 注册一个中间件(每个中间件的本质 是函数)
//自定义中间件(每个中间件的本质 是函数)
// app.use( (req,res,next)=>{
// next(); //移交控制器给下一个 中间件
// } )
//处理post参数
app.use( express.json() );
app.use( express.urlencoded({extended:false}) );
//静态资源托管
app.use( express.static('static') )
//api接口,处理用户搜索的请求
app.get('/api/user/search',(req,res)=>{
//req 请求对象 , 从 req.query 获取get参数
//res 响应对象 , 通过 res.send 发送数据到客户端(浏览器)
console.log( 'req.query', req.query );
var {phone,page,count} = req.query;
var filepath = path.join(__dirname,'json','user.json');
var userlist = JSON.parse( fs.readFileSync(filepath) );
//查找包含phone关键词的所有用户
var list = userlist.filter( (item)=>{
//return item.phone.indexOf(phone) != -1;
return new RegExp(phone,'i').test( item.phone ) ;
} );
//page 1 , 0 , 5
//page 2 , 5 , 10
//page 3 , 10 , 15
var finallist = list.slice( (page-1)*count, page*count );
res.send( { code:200, msg:"查询成功!",list:finallist, total:list.length } );
})
//api接口,处理删除用户的请求
app.post('/api/user/delete',(req,res)=>{
var {phone,page,count} = req.body;
var filepath = path.join(__dirname,'json','user.json');
var userlist = JSON.parse( fs.readFileSync(filepath) );
//查找phone对应用用户在userlist中的下标
var index = userlist.findIndex( (item)=>{
return item.phone == phone;
} );
if( index != -1 ){
userlist.splice(index,1);
fs.writeFileSync( filepath, JSON.stringify( userlist ) )
}
//分页
var finallist = userlist.slice( (page-1)*count, page*count );
res.send( { code:200, msg:"删除成功!",list:finallist, total:userlist.length } );
})
//3.监听端口
app.listen(3000,()=>{
console.log('服务器运行在 http://localhost:3000');
});
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-018vFOxn-1660484273430)(E:\3.插入到md的图片\微信截图_20220803231729.png)]
五、token
token是在服务端生成,浏览器关闭也会失效
1.在服务器端生成token------安装模块npm i jsonwebtoken (缩写jwt)
var jwt = require("jsonwebtoken")
app.get('/api/user/login',(req,res)=>{
//req 请求对象 , 从 req.query 获取get参数
//res 响应对象 , 通过 res.send 发送数据到客户端(浏览器)
console.log( 'req.query', req.query );
var {phone,pass} = req.query;
var filepath = path.join(__dirname,'json','user.json');
var userlist = JSON.parse( fs.readFileSync(filepath) );
//查找 用户是否存在
var index = userlist.findIndex( (item)=>{
return item.phone == phone && item.pass == pass;
} );
if( index != -1 ){
//服务器端生成token-----------------------------------------------------------------------------------------------
var token = jwt.sign({phone},'hello nodejs',{ algorithm: 'HS256', expiresIn: '1h' } );
//{phone}要加密的对象
//hello nodejs:秘钥:用它进行加密
//algorithm: 'HS256',这个办法进行加密()加密的算法
//expiresIn: '1h'--时间是一个小时
//登陆成功就携带这个token,前端拿到这个代码,就要进行在本地保存-------------------------------------------------
res.send( { code:200, msg:"登陆成功!", token } );
}else{
res.send( { code:400, msg:"登陆失败!" } );
}
})
2.在登陆完成之后生成token,token代码写入到登陆完成后,写到localStorage中,设置进去
loginBtn.onclick = function(){
//获取表单数据,发起ajax请求
if( inputs[0].value && inputs[1].value ){
ajax('get','/api/user/login',{
phone: inputs[0].value,
pass: inputs[1].value
}).then((res)=>{
if( res.code == 200 ){
//保存token字符串到本地
//保存到本地的localStorage, 登陆成功就会看到底下的加密字符串--------------------------------------------------------
localStorage.setItem('token',res.token);//在这里设置token,后端返回的token
//------------------------------------------------------------------------------------------------------
location.href = './index.html';
}else{
alert(res.msg);
}
}).catch((error)=>{
console.log(error);
})
}else{
alert('注册信息不完整');
}
}
//这个代码写在服务器端
var expressjwt = require('express-jwt').expressjwt
//下载这个在服务器端express-jwt
//设置中间件 验证token的有效性
app.use( expressjwt({
secret: 'hello nodejs',
algorithms: ['HS256']
}).unless({ path: [ //接口白名单, 这里的接口时免除token验证的
'/api/user/login',
'/api/user/register'
] }) )
3.要点击分页搜索要发请求,要携带这个token发送给服务器,得携带在请求头中
function ajax( method,url,data ){
return new Promise((resolve,reject)=>{
//1.创建xhr对象
var xhr = new XMLHttpRequest();
//2.设置请求参数
xhr.open(method, method=='get'? (url+'?'+toSearchString(data) ) : url );
console.log('参数', toSearchString(data) );
//3.发送请求
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
//设置请求头 Authorization----------------------------------------------------------------------------------
xhr.setRequestHeader('Authorization','Bearer ' + localStorage.getItem('token'));
//'Bearer '备注这里要加上空格
//设置请求头 Authorization----------------------------------------------------------------------------------
xhr.send( method=='post' ? toSearchString(data) : null );
//4.设置异步回调
xhr.onreadystatechange = function(res){
if( xhr.readyState == 4 && xhr.status == 200 ){
resolve( JSON.parse( xhr.responseText ) );
}else if( xhr.status == 401 ){
location.href = './login.html'
}
}
})
}
4.在header里面查看
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEzMzMzMzMzMzMiLCJpYXQiOjE2NTk1OTY3ODksImV4cCI6MTY1OTYwMDM4OX0.uWTM1bhkPFPPGh_bZSV4qNAlQ5I6FJPK8mISDK5Cx0E