NodeJS 入门笔记

news2024/11/23 7:57:53

文档地址

课程地址

源码 提取码:963h

hello wrold

console.log('hello, world');
node hello.js

nodejs 中不能使用 DOM(document) 和 BOM(window) 的 API:

  • document
  • window
  • history
  • navigator
  • location

在这里插入图片描述

但是下面的 API 是相通的:

  • console
  • timer
console.log('hello, world');

setTimeout(() => {
    console.log('fuck the world');
}, 1000);

nodejs 中的顶级对象为 global,也可以使用 globalThis 访问顶级对象:

console.log(global);
console.log(global === globalThis);		// true

Buffer

字节序列

let buf1 = Buffer.alloc(10);
let buf2 = Buffer.allocUnsafe(10);	// 有旧数据
let buf3 = Buffer.from('hello');	// str -> buffer
let buf4 = Buffer.from([101, 102, 103, 104]);

let s4 = buf4.toString();
console.log(buf3[0]);
buf3[0] = 361;		// 溢出

fs

writeFile

fs.write(file, data[, option], callback);
const fs = require('fs');

fs.writeFile('./hello.txt', 'hello, world\n', err => {		// async
    if (err) {
        console.log('write failed');
    } else {
        console.log('write success');
    }
});

异步与同步

writeFile 默认是异步的,对于下面的代码:

const fs = require('fs');

fs.writeFile('./hello.txt', 'hello, world\n', err => {		// async
    if (err) {
        console.log('write failed');
    } else {
        console.log('write success');
    }
});

console(1 + 1);

2 会先输出。原因在于写文件被主线程派发给一个子线程去执行,写文件不会阻塞主线程

下面是使用同步写入:

const fs = require('fs');
fs.writeFileSync('./test.txt', 'test');
console(1 + 1);

appendFile

const fs = require('fs');

fs.appendFile('./hello.txt', 'fuck, world\n', err => {		// async
    if (err) {
        console.log('append failed');
    } else {
        console.log('appeend success');
    }
});

同步版本:

fs.appendFileSync('./hello.txt', 'fuck, world\n')

使用 writeFile 实现追加写入:

fs.writeFile('./hello.txt', 'fuck, world\n', {flag: 'a'}, err => {		// async
    if (err) {
        console.log('append failed');
    } else {
        console.log('appeend success');
    }
});

流式写入

fs.createWriteStream(path[, options]);
let ws = fs.createWriteStream('./观书有感.txt');
ws.write('半亩方塘一鉴开\r\n');
ws.write('天光云影共徘徊\r\n');
ws.write('问渠那得清如许\r\n');
ws.write('为有源头活水来\r\n');
ws.end();

文件读取

异步读取

const fs = require('fs');

fs.readFile('hello.txt', (err, data) => {		// async
    if (err) {
        console.log('append failed');
    } else {
        console.log(data.toString());
    }
});

同步读取

let data = readFileSync('hello.txt');

流式读取

 fs.createReadStream(path[, options])
//创建读取流对象
let rs = fs.createReadStream('./观书有感.txt');
//每次取出 64k 数据后执行一次 data 回调
rs.on('data', data => {
	console.log(data);
	console.log(data.length);
});
//读取完毕后, 执行 end 回调
rs.on('end', () => {
	console.log('读取完成');
});

文件复制

同步读写

const fs = require('fs');
let data = fs.readFileSync('hello.txt');
fs.writeFileSync('hello-2.txt', data);

流式读写

const rs = fs.createReadStream('hello.txt');
const ws = fs.createWriteStream('hello-3.txt');
rs.on('data', chunk => {
	ws.write(chunk);
});

或者如下简写:

rs.pipe(ws);

文件重命名

文件重命名通过 rename() 实现:

fs.rename(oldPath, newPath, callback);
fs.renameSync(oldPath, newPath);
fs.rename('./观书有感.txt', './论语/观书有感.txt', (err) =>{
	if(err) throw err;
	console.log('移动完成')
});
fs.renameSync('./座右铭.txt', './论语/我的座右铭.txt');

文件删除

fs.unlink(path, callback)
fs.unlinkSync(path)
const fs = require('fs');
fs.unlink('./test.txt', err => {
	if(err) throw err;
	console.log('删除成功');
});
fs.unlinkSync('./test2.txt');

使用 rm 方法也可以

文件夹操作

mkdir

fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])
//异步创建文件夹
fs.mkdir('./page', err => {
	if(err) throw err;
	console.log('创建成功');
});
//递归异步创建
fs.mkdir('./1/2/3', {recursive: true}, err => {
	if(err) throw err;
	console.log('递归创建成功');
});
//递归同步创建文件夹
fs.mkdirSync('./x/y/z', {recursive: true});

readdir

fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])
//异步读取
fs.readdir('./论语', (err, data) => {
	if(err) throw err;
	console.log(data);
});
//同步读取
let data = fs.readdirSync('./论语');
console.log(data);

rmdir

fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])
//异步删除文件夹
fs.rmdir('./page', err => {
	if(err) throw err;
	console.log('删除成功');
});
//异步递归删除文件夹
fs.rmdir('./1', {recursive: true}, err => {
    if(err) {
    	console.log(err);
    }
	console.log('递归删除')
});
//同步递归删除文件夹
fs.rmdirSync('./x', {recursive: true})

查看资源状态

fs.stat(path[, options], callback)
fs.statSync(path[, options])
//异步获取状态
fs.stat('./data.txt', (err, data) => {
    if(err) throw err;
    console.log(data);
    console.log(data.isFile());
    console.log(data.isDirectory());
});
//同步获取状态
let data = fs.statSync('./data.txt');

在这里插入图片描述

绝对路径:

__dirname

__dirname 保存着当前文件所在目录的绝对路径,可以使用 __dirname 与文件名拼接成绝对路径

批量重命名练习

const fs = require('fs');
const files = fs.readdirSync('./code');

files.forEach(item => {
    let [num, name] = item.split('-');
    if (Number(num) < 10) {
        num = '0' + num;
    }
    let newName = num + '-' + name;
    fs.renameSync(`./code${item}`, `./code/${newName}`);
    console.log(item);
});

path

在这里插入图片描述

const path = require('path');
//获取路径分隔符
console.log(path.sep);
//拼接绝对路径
console.log(path.resolve(__dirname, 'test'));
//解析路径
let pathname = 'D:/program file/nodejs/node.exe';
console.log(path.parse(pathname));
//获取路径基础名称
console.log(path.basename(pathname))
//获取路径的目录名
console.log(path.dirname(pathname));
//获取路径的扩展名
console.log(path.extname(pathname));

HTTP 协议

在这里插入图片描述

请求行

在这里插入图片描述

请求方法

在这里插入图片描述

url

在这里插入图片描述

协议版本号

请求头

一系列键值对

请求体

请求体的内容格式是非常灵活的,可以设置任意内容

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

响应行

协议版本号

响应状态码

在这里插入图片描述

响应状态字符串

响应头

也是一系列键值对,可以自定义

响应体

响应体内容的类型是非常灵活的,常见的类型有 HTML、CSS、JS、图片、JSON

http

const http = require('http');

const server = http.createServer((request, response) => {
    response.end('<h1>hello, http server</h1>');
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

解决中文乱码:

response.setHeader('content-type','text/html;charset=utf-8');

发送 post 请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:9000" method="post">
        <input type="text" name="username">
        <input type="text" name="password">
        <input type="submit" value="提交">
    </form>
</body>
</html>

获取 HTTP 请求报文

在这里插入图片描述

获取请求体

const http = require('http');

const server = http.createServer((request, response) => {
    response.setHeader('content-type','text/html;charset=utf-8');
    let body = '';
    request.on('data', chunk => {
        body += chunk;
    });
    request.on('end', () => {
        console.log(body);
        response.end('hello, nodejs http')

    });
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

获取请求路径和查询字符串

const http = require('http');
const url = require('url');

const server = http.createServer((request, response) => {
    response.setHeader('content-type','text/html;charset=utf-8');
    console.log(url.parse(request.url).pathname);
    console.log(url.parse(request.url, true).query);
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

或者使用内置的 URL 类:

const http = require('http');

const server = http.createServer((request, response) => {
    let url = new URL(request.url, 'http://localhost');
    console.log(url.pathname);
    console.log(url.searchParams.get('keyword'));
    response.end('url new');
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

练习

在这里插入图片描述

const http = require('http');

const server = http.createServer((request, response) => {
    response.setHeader('content-type','text/html;charset=utf-8');
    let {url, method} = request;
    if(url == '/login' && method == 'GET') {
        response.end('登录');
    } else if (url == '/reg' && method == 'GET') {
        response.end('注册');
    } else {
        response.end('wrong');
    }
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

设置响应报文

在这里插入图片描述

const http = require('http');

const server = http.createServer((request, response) => {
    response.statusCode = 200;	// 响应行
    response.setHeader('content-type','text/html;charset=utf-8');	// 响应头
    response.end('response');	// 响应体
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

设置响应头:

response.setHeader('server','nodejs');
response.setHeader('myheader','test test test');
response.setHeader('test',['a', 'b', 'c']);		// 设置多个同名响应头

设置响应体:

response.write('hello');

练习

搭建 HTTP 服务,响应一个 4 行 3 列的表格,并且要求表格有隔行换色效果 ,且点击单元格能高亮显示

服务端代码:

const http = require('http');
const fs = require('fs');

const server = http.createServer((request, response) => {
    response.setHeader('content-type','text/html;charset=utf-8');
    fs.readFile('table.html', (err, data) => {
        response.end(data);
    });
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    td {
        padding: 20px 40px;
    }
    table tr:nth-child(odd) {
        background-color: #aef;
    }
    table tr:nth-child(even) {
        background-color: #fcb;
    }
    table, td {
        border-collapse: collapse;
    }
</style>
<body>
    <table border="1">
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
    </table>
</body>
<script>
    let tds = document.querySelectorAll('td');
    tds.forEach(item => {
        item.onclick = function() {
            this.style.background = '#bfa';
        }
    });
</script>
</html>

网页资源的加载都是循序渐进的,首先获取 HTML 的内容, 然后解析 HTML 在发送其他资源的请求,如 CSS,Javascript,图片等

练习扩展

将前端的 html css js 三者分离:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<link rel="stylesheet" href="table.css">
<body>
    <table border="1">
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
        <tr><td></td><td></td><td></td></tr>
    </table>
</body>
<script src="table.js"></script>
</html>
// table.js
let tds = document.querySelectorAll('td');
tds.forEach(item => {
    item.onclick = function() {
        this.style.background = '#bfa';
    }
});
// table.css
td {
    padding: 20px 40px;
}
table tr:nth-child(odd) {
    background-color: #aef;
}
table tr:nth-child(even) {
    background-color: #fcb;
}
table, td {
    border-collapse: collapse;
}

服务端:

const http = require('http');
const fs = require('fs');

const server = http.createServer((request, response) => {
    let {pathname} = new URL(request.url, 'http://localhost');
    if(pathname === '/') {
        fs.readFile('table.html', (err, data) => {
            response.end(data);
        });
    } else if (pathname === '/table.js') {
        fs.readFile('table.js', (err, data) => {
            response.end(data);
        });
    } else if (pathname === '/table.css') {
        fs.readFile('table.css', (err, data) => {
            response.end(data);
        });
    } else {
        response.end('error');
    }
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

获取 page 下的静态资源服务:

const http = require('http');
const fs = require('fs');

const server = http.createServer((request, response) => {
    let {pathname} = new URL(request.url, 'http://localhost');
    let filePath = __dirname + '/page' + pathname;
    fs.readFile(filePath, (err, data) => {
        if (err) {
            response.statusCode = 500;
            response.end('read file failed');
        }
        response.end(data);
    });
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

在这里插入图片描述

<link rel="stylesheet" href="/css/app.css">

MIME 类型

在这里插入图片描述

错误处理

const http = require('http');
const fs = require('fs');

const server = http.createServer((request, response) => {
    if(request.method !== 'GET') {
        response.statusCode = 405;
        response.end('<h1> 405 Method not allowed </h1>');
        return;
    }
    let {pathname} = new URL(request.url, 'http://localhost');
    let filePath = __dirname + '/page' + pathname;
    fs.readFile(filePath, (err, data) => {
        if (err) {
            console.log(err);
            switch(err.code) {
                case 'ENOENT':
                    response.statusCode = 404;
                    response.end('<h1> 404 Not Found </h1>');
                case 'EPERM':
                    response.statusCode = 403;
                    response.end('<h1> 403 Forbidden </h1>');
                default:
                    response.statusCode = 500;
                    response.end('error');
            }
        }
        response.end(data);
    });
});

server.listen(9000, () => {
    console.log('listening on 9000');
});

GET 与 POST 场景区别

在这里插入图片描述

在这里插入图片描述

模块化

其中拆分出的每个文件就是一个模块 ,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用

// me.js
function tiemo() {
    console.log("tiemo");
}

function niejiao() {
    console.log('niejiao');
}

module.exports = {
    tiemo,
    niejiao
};
const me = require('./me.js');       // 不能省略 ./

me.tiemo();
me.niejiao();

在这里插入图片描述

导入文件夹

// package.json
{
    "main": "./app.js"
}
// app.js
module.exports = "this is module1";
// index.js
const m1 = require('./module1');       // 不能省略 ./
console.log(m1);		// "this is module1"

导入模块的流程

在这里插入图片描述

CommonJS 模块化规范

在这里插入图片描述

包管理工具

npm

node package manager

初始化包

npm init

npm init 命令的作用是将文件夹初始化为一个『包』, 交互式创建 package.json 文件

{
  "name": "test",
  "version": "1.0.0",
  "description": "learning npm",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "daniel",
  "license": "ISC"
}

在这里插入图片描述

搜索包

下载安装包

npm install uniq

在这里插入图片描述

使用包:

const uniq = require('uniq');

let a1 = [1, 2, 3, 4, 5, 3, 2, 1];

const result = uniq(a1);

console.log(result)

开发依赖与生产依赖

npm i -S jquery		# 生产依赖
npm i -D less		# 开发依赖

全局安装

npm i -g nodemon
npm root -g	# 查看全局安装位置

安装所有依赖:

在这里插入图片描述

指定版本与删除包

npm i jquery@1.11.2
npm remove uniq		# 局部删除
npm r -g nodemon	# 全局删除

配置命令别名

{
...
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
...
}

配置完成之后,可以使用别名执行命令

npm run server
npm run start

在这里插入图片描述

cnpm

cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为『淘宝镜像』,网址https://npmmirror.com/

cnpm 服务部署在国内阿里云服务器上 , 可以提高包的下载速度 官方也提供了一个全局工具包 cnpm ,操作命令与 npm 大体相同

npm install -g cnpm --registry=https://registry.npmmirror.com

用 npm 也可以使用淘宝镜像:

使用命令行配置:

npm config set registry https://registry.npmmirror.com/

或者使用 nrm 工具配置:

nrm = npm registry manager

npm i -g nrm
nrm use taobao
nrm use npm

yarn

npm i -g yarn

在这里插入图片描述

nvm = node version manager

express 框架

基于 nodejs 的 web 开发框架

node init
node i express

一个最简单的 webserver:

const express = require('express');

const app = express();

app.get('/home', (req, res) => {
    res.end('express hello');
});

app.listen(3000, () => {
    console.log('listening...');
});

路由

在这里插入图片描述

const express = require('express');

const app = express();

app.get('/home', (req, res) => {
    res.end('express hello');
});

app.get('/', (req, res) => {
    res.end('home');
});

app.post('/login', (req, res) => {
    res.end('login');
});

app.all('/test', (req, res) => {
    res.end('test');
});

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});

发送 post 请求:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:3000/login" method="post">
        <button>登录</button>
    </form>
</body>
</html>

获取请求报文参数

//导入 express
const express = require('express');
//创建应用对象
const app = express();
//获取请求的路由规则
app.get('/request', (req, res) => {
    //1. 获取报文的方式与原生 HTTP 获取方式是兼容的
    console.log(req.method);
    console.log(req.url);
    console.log(req.httpVersion);
    console.log(req.headers);
    //2. express 独有的获取报文的方式
    //获取查询字符串
    console.log(req.query); // 『相对重要』
    console.log(req.path);
    // 获取指定的请求头
    console.log(req.get('host'));
    res.send('请求报文的获取');
});
//启动服务
app.listen(3000, () => {
	console.log('启动成功....')
});

获取路由参数

路由参数指的是 URL 路径中的参数(数据),使用 req.params 获取

app.get('/:id.html', (req, res) => {		//:id 是占位符
	res.send('商品详情, 商品 id 为' + req.params.id);
});
const express = require('express');
const {singers} = require('singers.json');

const app = express();

app.get('/singer/:id.html', (req, res) => {
    let {id} = req.params;
    let singer = singers.find(item => {
        if (item.id === Number(id)) {
            return true;
        }
    });
    if (!singer) {
        res.statusCode = 404;
        res.end('404 not found');
    } else {
        res.end(singer);
    }
});

响应设置

express 框架封装了一些 API 来方便给客户端响应数据,并且兼容原生 HTTP 模块的获取方式

//获取请求的路由规则
app.get("/response", (req, res) => {
    //1. express 中设置响应的方式兼容 HTTP 模块的方式
    res.statusCode = 404;
    res.statusMessage = 'xxx';
    res.setHeader('abc','xyz');
    res.write('响应体');
    res.end('xxx');
    
    //2. express 的响应方法
    res.status(500); //设置响应状态码
    res.set('xxx','yyy');//设置响应头
    res.send('中文响应不乱码');//设置响应体
    //连贯操作
    res.status(404).set('xxx','yyy').send('你好朋友')
    
    //3. 其他响应
    res.redirect('http://atguigu.com'); //重定向
    res.download('./package.json'); //下载响应
    res.json(); //响应 JSON,一般用于接口化开发
    res.sendFile(__dirname + '/home.html') //响应文件内容
});

中间件

在这里插入图片描述

全局中间件

每一个请求到达服务端之后都会执行全局中间件函数

const express = require('express');
const fs  = require('fs');
const path = require('path');

const app = express();

function recordMiddleware(req, res, next) { // 全局中间件函数
    let {url, ip} = req;
    fs.appendFileSync(path.resolve(__dirname, './access.log'), `${url} ${ip}\r\n`);
    //执行next函数(当如果希望执行完中间件函数之后,仍然继续执行路由中的回调函数,必须调用next)
    next();
}

app.use(recordMiddleware);	// 注册全局中间件

app.get('/home', (req, res) => {
    res.end('home');
});

app.get('/admin', (req, res) => {
    res.end('admin');
});

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});

在这里插入图片描述

路由中间件

如果只需要对某一些路由进行功能封装 ,则就需要路由中间件

const express = require('express');
const fs  = require('fs');
const path = require('path');

const app = express();

let checkCodeMiddleware = (req, res, next) => { // 全局中间件函数
    if (req.query.code === '521') {
        next();
    } else {
        res.send('code error');
    }
}

app.get('/setting', checkCodeMiddleware, (req, res) => {
    res.end('home');
});

app.get('/admin', checkCodeMiddleware, (req, res) => {
    res.end('admin');
});

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});

静态资源中间件

app.use(express.static(__dirname + '/public'));

在这里插入图片描述

获取请求体数据

按照要求搭建 http 服务:

  • GET /login 显示表单网页
  • POST /login 获取表单中的用户名和密码

使用 body-parser 中间件:

const express = require('express');
const bodyParser = require('body-parser');

//处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({extended:false});

const app = express();

app.get('/login', (req, res) => {
    res.sendFile(__dirname + '/form.html');
});

app.post('/login', urlParser, (req, res) => {
    //用户名
    console.log(req.body.username);
    //密码
    console.log(req.body.password);
    res.send('获取请求体数据');
});

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
</head>
<body>
    <form action="/login" method="post">
        username: <input type="text" name="username"> <br />
        password: <input type="text" name="password"> <br />
        <button>submit</button>
    </form>
</body>
</html>

防盗链

禁止其他域名访问本网站资源

referer 请求头能够携带当前域名信息:

referer: https://www.jj20.com/
const express = require('express');

const app = express();

app.use((req, res, next) => {
    let referer = req.get('referer');	// 获取 referer 请求头
    if (referer) {
        let url = new URL(referer);
        let hostname = url.hostname;
        if (hostname !== '127.0.0.1') {
            res.status(404).send('<h1> 404 not found</h1>');
            return;
        }
    }
    next();
});

app.use(express.static(__dirname + '/public'));

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});

Router 路由模块化

什么是 Router:express 中的 Router 是一个完整的中间件路由系统,可以看作是一个小型的 app 对象

Router 的作用:对路由进行模块化,更好地管理路由

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

const app = express();

const homeRouter = require('./homeRouter');
const adminRouter = require('./adminRouter');

app.use(homeRouter);
app.use(adminRouter);

app.all('*', (req, res) => {
    res.end('404 not found');
});

app.listen(3000, () => {
    console.log('listening...');
});
// homeRouter.js
const express = require('express');

const router = express.Router();

router.get('/home', (req, res) => {
    res.send('home');
});

router.get('/search', (req, res) => {
    res.send('search');
});

module.exports = router;
// adminRouter.js
const express = require('express');

const router = express.Router();

router.get('/admin', (req, res) => {
    res.send('admin');
});

router.get('/setting', (req, res) => {
    res.send('setting');
});

module.exports = router;

ejs 模板引擎

模板引擎:分离用户界面和业务数据

npm i ejs
<% code %>		// 执行 js 代码
<%= code %>		// 输出转义的数据到模板上
<%- code %>		// 输出非转义的数据到模板上
const ejs = require('ejs');

let y = 'you';

let res = ejs.render('i love <%= y %>', {y: y});
console.log(res);

也可以对 html 文档中的内容进行替换:

// ejs.js
const ejs = require('ejs');
const fs = require('fs');

let y = 'you';

let html = fs.readFileSync('./index.html').toString();

let res = ejs.render(html, {y: y});
console.log(res);
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>i love <%= y %> </h1>
</body>
</html>

列表渲染

// ejs.js
const ejs = require('ejs');
const fs = require('fs');

const arr = ['appal', 'orange', 'banana', 'grapes'];

let html = fs.readFileSync('./index.html').toString();

let res = ejs.render(html, {arr: arr});

console.log(res);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <% arr.forEach(item => { %>
        	<li><%= item %></li>
        <% }) %>
    </ul>
</body>
</html>

条件渲染

在这里插入图片描述

// ejs.js
const ejs = require('ejs');
const fs = require('fs');

let isLogin = false;

let html = fs.readFileSync('./index.html').toString();

let res = ejs.render(html, {isLogin: isLogin});

console.log(res);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <% if(isLogin) { %>
        <span> 欢迎回来 </span>
    <% } else { %>
        <button>登录</button> <button>注册</button>
    <% } %>
</body>
</html>

express 中使用 ejs

// ejs.js
const express = require('express');
const path = require('path');

const app = express();

app.set('view engine', 'ejs');	// 设置引擎
app.set('views', path.resolve(__dirname, './views'));	// 设置模板路径

app.get('/home', (req, res) => {
    let title = 'hello, world';
    res.render('home', {title});	// 将 title render 给 home.ejs
});

app.listen(3000, () => {
    console.log('listening on 3000');
})
// views/home.ejs
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <%= title %>
</body>
</html>

express-generator

用于快速创建一个应用的骨架

npm i -g express-generator
express -e prj_name
cd prj_name
npm i	// 安装依赖
npm start

然后浏览器访问 http://localhost:3000/ 即可

// app.js
app.use('/', indexRouter);
app.use('/users', usersRouter);		// users 是路由前缀

文件上传

// routers/index.js
var express = require('express');
var router = express.Router();
const {formidable} = require('formidable');

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

router.get('/portrait', (req, res) => {
  res.render('portrait');
});

router.post('/portrait', (req, res) => {
  const form = formidable({multiples: true,
    uploadDir: __dirname + '/../public/images',
    keepExtensions: true
  });
  form.parse(req, (err, fields, files) => {
    if (err) {
      next(err);
      return;
    }
    let url = '/images/' + files.portrait[0].newFilename;
    res.send(url);
  });
});

module.exports = router;
<!--portrait.ejs-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portrait</title>
</head>
<body>
    <h1>Portrait</h1>
    <form action="/portrait" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username"> <br>
        头像:<input type="file" name="portrait">
        <button>点击提交</button>
    </form>
</body>
</html>

记账本案例实践

初始化项目:

express -e
npm i
npm start

添加 2 个路由和 2 个静态页面:

// routes/index.js
/* GET home page. */
router.get('/account', function(req, res, next) {
  // res.render('index', { title: 'Express' });
  res.render('list');
});

// 发送表单页面
router.get('/account/create', function(req, res, next) {
  // res.render('index', { title: 'Express' });
  res.render('create');
});

// 处理表单数据
router.post('/account', (req, res) => {
  console.log(req.body);
  res.send('add account');
});
<!-- views/list.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>记账本</h2>
          <hr />
          <div class="accounts">
            <div class="panel panel-danger">
              <div class="panel-heading">2023-04-05</div>
              <div class="panel-body">
                <div class="col-xs-6">抽烟只抽煊赫门,一生只爱一个人</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-warning">支出</span>
                </div>
                <div class="col-xs-2 text-right">25 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            <div class="panel panel-success">
              <div class="panel-heading">2023-04-15</div>
              <div class="panel-body">
                <div class="col-xs-6">3 月份发工资</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-success">收入</span>
                </div>
                <div class="col-xs-2 text-right">4396 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

表单页面:

<!-- views/create.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>添加记录</title>
    <link
      href="/css/bootstrap.css"
      rel="stylesheet"
    />
    <!-- 以 public 为根目录 -->
    <link href="/css/bootstrap-datepicker.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>添加记录</h2>
          <hr />
          <form method="post" action="/account">
            <div class="form-group">
              <label for="item">事项</label>
              <input
                name="title"
                type="text"
                class="form-control"
                id="item"
              />
            </div>
            <div class="form-group">
              <label for="time">发生时间</label>
              <input
                name="time"
                type="text"
                class="form-control"
                id="time"
              />
            </div>
            <div class="form-group">
              <label for="type">类型</label>
              <select class="form-control" id="type" name="type">
                <option value="-1">支出</option>
                <option value="1">收入</option>
              </select>
            </div>
            <div class="form-group">
              <label for="account">金额</label>
              <input
                name="account"
                type="text"
                class="form-control"
                id="account"
              />
            </div>
            
            <div class="form-group">
              <label for="remarks">备注</label>
              <textarea class="form-control" id="remarks" name="remark"></textarea>
            </div>
            <hr>
            <button type="submit" class="btn btn-primary btn-block">添加</button>
          </form>
        </div>
      </div>
    </div>
    <!-- 以 public 为根目录 -->
    <script src="/js/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/bootstrap-datepicker.min.js"></script>
    <script src="/js/bootstrap-datepicker.zh-CN.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

lowdb

npm i lowdb@1.0.0
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');
 
const adapter = new FileSync('db.json');
const db = low(adapter);

// Set some defaults
db.defaults({ posts: [], user: {} })
  .write()
/*
{
  "posts": [],
  "user": {}
}
*/
 

// Add a post
db.get('posts')
  .push({ id: 1, title: 'lowdb is awesome'})
  .write()
/*
{
  "posts": [
    {
      "id": 1,
      "title": "lowdb is awesome"
    }
  ],
  "user": {}
}
*/

db.get('posts')
  .unshift({id: 2, title: 'hello, world'})
  .write()
/*
  {
    "posts": [
      {
        "id": 2,
        "title": "hello, world"
      },
      {
        "id": 1,
        "title": "lowdb is awesome"
      }
    ],
    "user": {}
  }
*/

console.log(db.get('posts').value())

db.get('posts').remove({id: 2}).write()

db.get('posts').find({id: 1}).assign({title: "fuck, world"}).write()

保存账单信息

// data/db.json
{
  "accounts": []
}

安装 shortid:

npm i shortid

收到 post 请求后存入数据库:

// index.js
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync'); 
const adapter = new FileSync(__dirname + '/../data/db.json');
const db = low(adapter);
const shortid = require("shortid");

router.post('/account', (req, res) => {
  console.log(req.body);
  let id = shortid.generate();
  db.get("accounts").unshift({id: id, ...req.body}).write();
  res.render('success',  {msg: "添加成功~~~", url: '/account'});
});
<!-- success.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>提醒</title>
  <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
  <style>
    .h-50{
      height: 50px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="h-50"></div>
    <div class="alert alert-success" role="alert">
       <h1><%= msg %></h1>
      <p><a href="<%= url %>">点击跳转</a></p>
    </div>
  </div>
</body>
</html>

账单列表

<!-- list.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>记账本</h2>
          <hr />
          <div class="accounts">
            <% accounts.forEach(item => {%>
            <div class="panel <%= item.type === '-1' ? 'panel-danger' : 'panel-success'%>">
              <div class="panel-heading"><%= item.time %></div>
              <div class="panel-body">
                <div class="col-xs-6"><%= item.title %></div>
                <div class="col-xs-2 text-center">
                  <span class="label <%= item.type === '-1' ? 'label-warning' : 'label-success'%>"><%= item.type === '-1' ? '支出' : '收入'%></span>
                </div>
                <div class="col-xs-2 text-right"><%= item.account %></div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            <% }) %>            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

删除账单

前端代码修改:

<!-- list.ejs -->
<a href="/account/<%= item.id %>">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>

后端代码修改:

// index.js
router.get("/account/:id", (req, res) => {
  let id = req.params.id;
  db.get('accounts').remove({id: id}).write();
  res.render('success', {msg: '删除成功', url: '/account'});
});

MongoDB

MongoDB 是一个基于分布式文件存储的数据库,跟 json 文件非常类似

在这里插入图片描述

  • 一个 json 文件好比是一个数据库,一个 Mongodb 服务下可以有 N 个数据库
  • json 文件中的一级属性的数组值好比是集合
  • 数组中的对象好比是文档
  • 对象中的属性有时也称之为字段

一般情况下:
一个项目使用一个数据库
一个集合会存储同一类型的数据

启动数据库

Mongodb 默认使用 C:/data/db 作为文件存储路径

mongod	# 启动数据库服务端
mongo	# 启动数据库客户端
show dbs

默认有 3 个数据库:

admin
config
local

数据库与集合命令

show dbs	// 显示所有的数据库
use db_name		// 切换到指定的数据库,如果不存在则创建
db		// 显示当前所在的数据库
use db_name
db.dropDatabase()	// 删除当前数据库
db.createCollection('users')
show collections
db.collection_name.drop()	// 删除某个集合
db.collection_name.renameCollection(collection_name2)	// 重命名集合

文档命令

use bilibili
db.users.insert({name: 'hcc', age: 18})
db.users.insert({name: 'hnp', age: 19})
db.users.insert({name: 'ss', age: 20})

db.users.find()		// 查询所有文档
db.users.find({age: 20})	// 按条件查询
db.users.update({name: 'ss'}, {$set: {age: 24}})	// 更新
db.users.remove({name: 'ss'})

mongoose

Mongoose 是一个对象文档模型库,方便使用代码操作 mongodb 数据库

npm init
npm i mongoose

连接数据库

const mongoose = require('mongoose');

// 如果数据库不存在,则自动创建
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');

// 使用 once 绑定的回调函数,只执行一次
mongoose.connection.once('open', () => {
    console.log('open');
});
mongoose.connection.on('error', () => {
    console.log('error');
});
mongoose.connection.on('close', () => {
    console.log('close');
});
setTimeout(() => {
    mongoose.disconnect();
});

插入文档

mongoose.connection.once('open', () => {
    let BookSchema = new mongoose.Schema({
        title: String,
        author: String,
        price: Number
    });
    let BookModel = mongoose.model('books', BookSchema);
    BookModel.create({
        title: 'xiyouji',
        author: 'wuchengen',
        price: 19
    }).then((data) => {
        console.log(data);
        mongoose.disconnect();
    });
    console.log('open');
});

在这里插入图片描述

mongoose.connection.once('open', () => {
    let BookSchema = new mongoose.Schema({
        title: String,
        author: String,
        price: Number
        is_hot: Boolean,
        tags: Array,
        pub_time: Date
    });
    let BookModel = mongoose.model('books', BookSchema);
    BookModel.create({
        title: 'xiyouji',
        author: 'wuchengen',
        price: 19,
        is_hot: true,
        tags: ['a', 'b', 'c']
        pub_time: new Date()
    }).then((data) => {
        console.log(data);
        mongoose.disconnect();
    })
    console.log('open');
});

字段值验证(约束):Mongoose 有一些内建验证器,可以对字段值进行验证

    let BookSchema = new mongoose.Schema({
        title: {
            type: String,
            required: true
        },
        author: {
            type: String,
            default: 'anonymous'
        },
        gender: {
        	type: String,
        	enum: ['M', 'F']
        },
        username: {
        	type: String,
        	unique: true	// unique 需要重建集合才能有效果
        },
        price: Number
    });

删除文档

// 删除单条
SongModel.deleteOne({_id:'5dd65f32be6401035cb5b1ed'}, function(err){
	if(err) throw err;
	console.log('删除成功');
	mongoose.connection.close();
});

// 删除多条
SongModel.deleteMany({author:'Jay'}, function(err){
	if(err) throw err;
	console.log('删除成功');
	mongoose.connection.close();
});

更新文档

SongModel.updateOne({author: 'JJ Lin'}, {author: '林俊杰'}, function (err) {
	if(err) throw err;
	mongoose.connection.close();
});

SongModel.updateMany({author: 'Leehom Wang'}, {author: '王力宏'}, function (err) {
	if(err) throw err;
	mongoose.connection.close();
});

读取文档

查询一条数据:

SongModel.findOne({author: '王力宏'}, function(err, data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});
//根据 id 查询数据
SongModel.findById('5dd662b5381fc316b44ce167', function(err, data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});

批量查询数据:

//不加条件查询
SongModel.find(function(err, data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});
//加条件查询
SongModel.find({author: '王力宏'}, function(err, data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});

条件控制

在这里插入图片描述

db.students.find({id:{$gt:3}});

逻辑运算:

db.students.find({$or:[{age:18}, {age:24}]});
db.students.find({$and: [{age: {$lt:20}}, {age: {$gt: 15}}]});

正则:

db.students.find({name:/imissyou/});
db.students.find({name: new RegExp('imissyou')});

个性化读取

字段筛选:

//0:不要的字段
//1:要的字段
SongModel.find().select({_id:0,title:1}).exec(function(err,data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});

数据排序:

//sort 排序
//1:升序
//-1:倒序
SongModel.find().sort({hot:1}).exec(function(err,data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});
BookModel.find().select({name: 1, price: 1, _id: 0}).sort({price: 1}).exec((err, data) => {
	if (err) {
		console.log('error');
		return;
	}
	console.log(data);
});

数据截取:

//skip 跳过 limit 限定
SongModel.find().skip(10).limit(10).exec(function(err,data){
	if(err) throw err;
	console.log(data);
	mongoose.connection.close();
});
BookModel.find()
.select({name: 1, price: 1, _id: 0})
.sort({price: -1})
.skip(3)		// 跨过 3 个
.limit(3)		// 取 3 个
.exec((err, data) => {
	if (err) {
		console.log('error');
		return;
	}
	console.log(data);
});

模块化

// db/db.js
const {DBHOST, DBPORT, DBNAME}= require('../config/config');

module.exports = function (success, error) {
    if (typeof error !== 'function') {
        error = () => {
            console.log('connection error');
        }
    }
    const mongoose = require('mongoose');
    mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);

    mongoose.connection.once('open', () => {
        success();
    });
    mongoose.connection.on('error', () => {
        error();
    });
    mongoose.connection.on('close', () => {
        console.log('close');
    });
}
// config/config.js
module.exports = {
    DBHOST: '127.0.0.1', 
    DBPORT: 27017,
    DBNAME: 'bilibili'
}
// models/BookModel.js
const mongoose = require('mongoose');

let BookSchema = new mongoose.Schema({
    title: String,
    author: String,
    price: Number
});
let BookModel = mongoose.model('books', BookSchema);

module.exports = BookModel;
// demo1.js
const mongoose = require('mongoose');
const db = require('./db/db.js');
const BookModel = require('./models/BookModel.js');

db(() => {
    BookModel.create({
        title: 'xiyouji',
        author: 'wuchengen',
        price: 19
    }).then((data) => {
        console.log(data);
        mongoose.disconnect();
    });
    console.log('success');
});

数据库可视化工具:robo3t

记账本案例优化

// config/config.js
module.exports = {
    DBHOST: '127.0.0.1', 
    DBPORT: 27017,
    DBNAME: 'bilibili'
}
// db/db.js
const {DBHOST, DBPORT, DBNAME}= require('../config/config');

module.exports = function (success, error) {
    if (typeof error !== 'function') {
        error = () => {
            console.log('connection error');
        }
    }
    const mongoose = require('mongoose');
    mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);

    mongoose.connection.once('open', () => {
        success();
    });
    mongoose.connection.on('error', () => {
        error();
    });
    mongoose.connection.on('close', () => {
        console.log('close');
    });
}
// models/AccountModel.js
const mongoose = require('mongoose');

let AccountSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    time: Date,
    type: {
        type: Number,
        default: -1
    },
    account: {
        type: Number,
        required: true
    },
    remark: String
});

let AccountModel= mongoose.model('accounts', AccountSchema);
module.exports = AccountModel;
// bin/www
db(() => {
	// content of www
});

将字符串转为日期对象:

npm i moment
moment('2023-02-24').toDate();
// index.js
var express = require('express');
var router = express.Router();
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');
 
const adapter = new FileSync(__dirname + '/../data/db.json');
const db = low(adapter);
const shortid = require("shortid");
const moment = require("moment");
const AccountModel = require("../models/AccountModel")

/* GET home page. */
router.get('/account', function(req, res, next) {
  // res.render('index', { title: 'Express' });
  // let accounts = db.get('accounts').value();
  AccountModel.find().sort({time: -1}).exec((err, data) => {
    if (err) {
      res.status(500).send("读取失败");	// 修改 mongoose 版本号到 6.8.0
      return;
    }
    // data.time = moment(data.time).format("YYYY-MM-DD");
    res.render('list', {accounts: data, moment: moment});
  });
});

router.get('/account/create', function(req, res, next) {
  // res.render('index', { title: 'Express' });
  res.render('create');
});

router.post('/account', (req, res) => {
  console.log(req.body);
  // let id = shortid.generate();
  // db.get("accounts").unshift({id: id, ...req.body}).write();
  AccountModel.create({
    ...req.body,
    time: moment(req.time).toDate(),
  }).then((data) => {
    console.log(data);
    // res.status(500).send("插入失败!");
    // mongoose.disconnect();
  });

  res.render('success', {msg: "添加成功~~~", url: '/account'});
});

router.get("/account/:id", (req, res) => {
  let id = req.params.id;
  // db.get('accounts').remove({id: id}).write();
  AccountModel.deleteOne({_id: id}, (err, data) => {
    if (err) {
      res.status(500).send("删除失败");
      return;
    }
    res.render('success', {msg: '删除成功', url: '/account'});
  });
});

module.exports = router;
<!-- list.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <div class="row">
            <h2 class="col-xs-6">记账本</h2>
            <h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
          </div>
          <hr />
          <div class="accounts">
            <% accounts.forEach(item => {%>
            <div class="panel <%= item.type === -1 ? 'panel-danger' : 'panel-success'%>">
              <div class="panel-heading"><%= moment(item.time).format("YYYY-MM-DD") %></div>
              <div class="panel-body">
                <div class="col-xs-6"><%= item.title %></div>
                <div class="col-xs-2 text-center">
                  <span class="label <%= item.type === -1 ? 'label-warning' : 'label-success'%>"><%= item.type === -1 ? '支出' : '收入'%></span>
                </div>
                <div class="col-xs-2 text-right"><%= item.account %></div>
                <div class="col-xs-2 text-right">
                  <a class="delBtn" href="/account/<%= item._id %>">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                  </a>
                </div>
              </div>
            </div>
            <% }) %>            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
<script>
  let delBtns = document.querySelectorAll(".delBtn");
  delBtns.forEach(item => {
    item.addEventListener("click", function(e) {
      if (confirm("确认删除?")) {
        return true;
      } else{
        e.preventDefault();
      }
    });
  });
</script>

API接口

一个接口就是 服务中的一个路由规则 ,根据请求响应结果。一个接口一般由如下几个部分组成:

  • 请求方法
  • 接口地址(URL)
  • 请求参数
  • 响应结果

接口示例

在这里插入图片描述

RESTful API

在这里插入图片描述

接口化有利于前后端分离

json-server

json-server 本身是一个 JS 编写的工具包,可以快速搭建 RESTful API 服务

npm i -g json-server

准备一个 json 文件:

// db.json
{
	"song": [
		{ "id": 1, "name": "干杯", "singer": "五月天" },
		{ "id": 2, "name": "当", "singer": "动力火车" },
		{ "id": 3, "name": "不能说的秘密", "singer": "周杰伦" }
	]
}

启动 json-server:

json-server --watch db.json

访问:

http://localhost:3000/song
http://localhost:3000/song/1

apifox

GET

获取所有歌曲:

在这里插入图片描述

POST

新增一首歌曲:

在这里插入图片描述

DELETE

删除 id 为 4 的歌曲:

在这里插入图片描述

PATCH

修改 id 为 3 的歌曲:

在这里插入图片描述

记账本案例增加接口

// api/account.js

var express = require('express');
var router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel")

// 获取账单列表
router.get('/account', function(req, res, next) {
  AccountModel.find().sort({time: -1}).exec((err, data) => {
    if (err) {
      // res.status(500).send("读取失败");
      res.json({
        code: '1001',
        msg: '读取失败',
        data: null
      });
      return;
    }
    // res.render('list', {accounts: data, moment: moment});
    res.json({
        code: '0000',
        msg: '读取成功',
        data: data
    });
  });
});

// 新增记录
router.post('/account', (req, res) => {
  console.log(req.body);
  // 表单验证...
  AccountModel.create({
    ...req.body,
    time: moment(req.time).toDate(),
  }).then((data) => {
    console.log(data);
    res.json({
        code: '0000',
        msg: '添加成功',
        data: data
    });
  });

  // res.render('success', {msg: "添加成功~~~", url: '/account'});
});

// 删除账单
router.delete("/account/:id", (req, res) => {
  let id = req.params.id;
  AccountModel.deleteOne({_id: id}, (err, data) => {
    if (err) {
      // res.status(500).send("删除失败");
      res.json({
        code: '1000',
        msg: '删除失败',
        data: null
      });
      return;
    }
    // res.render('success', {msg: '删除成功', url: '/account'});
    res.json({
        code: '0000',
        msg: '删除成功',
        data: null
    });
  });
});

// 获取单条账单
router.get('/account/:id', (req, res) => {
    let id = req.params.id;
    AccountModel.findById(id, (err, data) => {
        if (err) {
            return res.json({
                code: '1004',
                msg: '查询失败',
                data: null
            });
        }
        res.json({
            code: '0000',
            msg: '查询成功',
            data: data
        });
    });
});

// 更新单个账单
router.patch('/account/:id', (req, res) => {
    let {id} = req.params;
    AccountModel.updateOne({_id: id}, req.body, (err, data) => {
        if (err) {
            return res.json({
                code: '1005',
                msg: '更新失败',
                data: null
            });
        }
        AccountModel.findById(id, (err, data) => {
            if (err) {
                return res.json({
                    code: '1004',
                    msg: '查询失败',
                    data: null
                });
            }
            res.json({
                code: '0000',
                msg: '更新成功',
                data: data
            });
        });
    });
});

module.exports = router;
// app.js
...
const accountRouter = require('./routes/api/account');
app.use('/api', accountRouter);
...

在这里插入图片描述

会话控制

所谓会话控制就是 对会话进行控制。HTTP 是一种 无状态 的协议,它没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户,而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题

常见的会话控制技术有三种:

  • cookie
  • session
  • token

cookie

cookie 是 HTTP 服务器发送到用户浏览器并保存在本地的一小块数据

  • cookie 是保存在浏览器端的一小块数据
  • cookie 是按照域名划分保存的

在这里插入图片描述

浏览器向服务器发送请求时,会自动将 当前域名下 可用的 cookie 设置在请求头中,然后传递给服务器

这个请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的

在这里插入图片描述
在这里插入图片描述

express 设置 cookie

const express = require('express');

const app = express();

app.get('/set-cookie', (req, res) => {
    // res.cookie('name', 'zhangsan'); // 会在浏览器关闭后自动销毁
    res.cookie('name', 'zhangsan', {maxAge: 60 * 1000});
    res.cookie('theme', 'blue');
    res.send('home');
});
app.listen(3000);

express 删除 cookie

app.get('/remove-cookie', (req, res) => {
    res.clearCookie('theme');
    res.send('删除成功');
});

express 提取 cookie

cookie-parser 中间件:

npm i cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/get-cookie', (req, res) => {
    console.log(req.cookies);
    res.send(`欢迎您 ${req.cookies.name}`);
});

app.listen(3000);

session

session 是保存在服务器端的一块儿数据 ,保存当前访问用户的相关信息

作用:实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息

在这里插入图片描述

const express = require('express');
const session = require('express-session');
const mongoStore = require('connect-mongo');

const app = express();

app.use(session({
    name: 'sid', //设置cookie的name,默认值是:connect.sid
    secret: 'atguigu', //参与加密的字符串(又称签名)
    saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
    resave: true, //是否在每次请求时重新保存session
    store: mongoStore.create({
        mongoUrl: 'mongodb://127.0.0.1:27017/bilibili' //数据库的连接配置
    }),
    cookie: {
        httpOnly: true, // 开启后前端无法通过 JS 操作 document.cookie() ×
        maxAge: 1000 * 60 * 5 // 这一条 是控制 sessionID 的过期时间的!!!
    },
}));    


app.get('/', (req, res) => {
    res.send('home');
});

app.get('/login', (req, res) => {
    // http://localhost:3000/login?username=admin&password=admin
    if(req.query.username === 'admin' && req.query.password === 'admin') {
        req.session.username = 'admin';
        res.send('登录成功');
    } else {
        res.send('登录失败');
    }
});

app.get('/cart', (req, res) => {
    if(req.session.username) {  // 中间件根据 sid 查询数据库设置 req.session.username
        res.send('购物车');
    } else {
        res.send('请登录');
    }
});

app.get('/logout', (req, res) => {
    req.session.destroy(() => {
        res.send('退出成功');
    });
});

app.listen(3000);

cookie 与 session 的区别

在这里插入图片描述

记账本案例注册功能

// views/auth/reg.ejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>注册</title>
  <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4">
        <h2>注册</h2>
        <hr />
        <form method="post" action="/reg">
          <div class="form-group">
            <label for="item">用户名</label>
            <input name="username" type="text" class="form-control" id="item" />
          </div>
          <div class="form-group">
            <label for="time">密码</label>
            <input name="password" type="password" class="form-control" id="time" />
          </div>
          <hr>
          <button type="submit" class="btn btn-primary btn-block">注册</button>
        </form>
      </div>
    </div>
  </div>
</body>

</html>
// UserModel.js
const mongoose = require('mongoose');

let UserSchema = new mongoose.Schema({
    username: String,
    password: String
});

let UserModel= mongoose.model('users', UserSchema);
module.exports = UserModel;
// routes/web/auth.js
var express = require('express');
var router = express.Router();
const UserModel= require("../../models/UserModel");
const md5 = require('md5');
//注册
router.get('/reg', (req, res) => {
  //响应 HTML 内容
  res.render('auth/reg');
});

//注册用户
router.post('/reg', (req, res) => {
  //做表单验证
  //获取请求体的数据
  UserModel.create({...req.body, password: md5(req.body.password)}, (err, data) => {
    if(err){
      res.status(500).send('注册失败, 请稍后再试~~');
      return;
    }
    res.render('success', {msg: '注册成功', url: '/login'});
  });
  
});

//登录页面
router.get('/login', (req, res) => {
  //响应 ejs 内容
  res.render('auth/login');
});

//登录操作
router.post('/login', (req, res) => {
  //获取用户名和密码
  let {username, password} = req.body;
  //查询数据库
  UserModel.findOne({username: username, password: md5(password)}, (err, data) => {
    //判断
    if(err){
      res.status(500).send('登录, 请稍后再试~~');
      return;
    }
    //判断 data
    if(!data){
      return res.send('账号或密码错误~~');
    }
    //写入session
    req.session.username = data.username;
    req.session._id = data._id;
    //登录成功响应
    res.render('success', {msg: '登录成功', url: '/account'});
  });
});

//退出登录
router.post('/logout', (req, res) => {
  //销毁 session
  req.session.destroy(() => {
    res.render('success', {msg: '退出成功', url: '/login'});
  })
});

module.exports = router;
// views/auth/login.ejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>登录</title>
  <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4">
        <h2>登录</h2>
        <hr />
        <form method="post" action="/login">
          <div class="form-group">
            <label for="item">用户名</label>
            <input name="username" type="text" class="form-control" id="item" />
          </div>
          <div class="form-group">
            <label for="time">密码</label>
            <input name="password" type="password" class="form-control" id="time" />
          </div>
          <hr>
          <button type="submit" class="btn btn-primary btn-block">登录</button>
        </form>
      </div>
    </div>
  </div>
</body>
</html>
// app.js
const session = require('express-session');
const MongoStore = require('connect-mongo');
const {DBHOST, DBNAME, DBPORT} = require('./config/config');
const authRouter = require('./routes/web/auth');

//设置 session 的中间件
app.use(session({
  name: 'sid',   //设置cookie的name,默认值是:connect.sid
  secret: 'atguigu', //参与加密的字符串(又称签名)  加盐
  saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
  resave: true,  //是否在每次请求时重新保存session  20 分钟    4:00  4:20
  store: MongoStore.create({
    mongoUrl: `mongodb://${DBHOST}:${DBPORT}/${DBNAME}` //数据库的连接配置
  }),
  cookie: {
    httpOnly: true, // 开启后前端无法通过 JS 操作
    maxAge: 1000 * 60 * 60 * 24 * 7 // 这一条 是控制 sessionID 的过期时间的!!!
  },
}));

用户登录检测

// index.js
var express = require('express');
var router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel");
const checkLoginMiddleware = require('../../middlewares/checkLoginMiddleware');


/* GET home page. */
router.get('/account', checkLoginMiddleware, function(req, res, next) {
  AccountModel.find().sort({time: -1}).exec((err, data) => {
    if (err) {
      res.status(500).send("读取失败");
      return;
    }
    res.render('list', {accounts: data, moment: moment});
  });
});

router.get('/account/create', checkLoginMiddleware, function(req, res, next) {
  res.render('create');
});

router.post('/account', checkLoginMiddleware, (req, res) => {
  console.log(req.body);
  AccountModel.create({
    ...req.body,
    time: moment(req.time).toDate(),
  }).then((data) => {
    console.log(data);
  });
  res.render('success', {msg: "添加成功~~~", url: '/account'});
});

router.get("/account/:id", checkLoginMiddleware, (req, res) => {
  let id = req.params.id;
  AccountModel.deleteOne({_id: id}, (err, data) => {
    if (err) {
      res.status(500).send("删除失败");
      return;
    }
    res.render('success', {msg: '删除成功', url: '/account'});
  });
});

module.exports = router;
// middlewares/checkLoginMiddleware.js
function checkLoginMiddleware(req, res, next) {
    if (!req.session.username) {
        return res.redirect('/login');
    }
    next();
}
module.exports = checkLoginMiddleware;

退出登录:

// list.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">

          <div class="row text-right">
            <div class="col-xs-12" style="padding-top: 20px;">
              <form method="post" action="/logout">
                <button href="/logout" class="btn btn-danger">退出</a>	<!--新增退出按钮-->
              </form>
            </div>
          </div>

          <hr>
          <div class="row">
            <h2 class="col-xs-6">记账本</h2>
            <h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
          </div>
          <hr />
          <div class="accounts">
            <% accounts.forEach(item => {%>
            <div class="panel <%= item.type === -1 ? 'panel-danger' : 'panel-success'%>">
              <div class="panel-heading"><%= moment(item.time).format("YYYY-MM-DD") %></div>
              <div class="panel-body">
                <div class="col-xs-6"><%= item.title %></div>
                <div class="col-xs-2 text-center">
                  <span class="label <%= item.type === -1 ? 'label-warning' : 'label-success'%>"><%= item.type === -1 ? '支出' : '收入'%></span>
                </div>
                <div class="col-xs-2 text-right"><%= item.account %></div>
                <div class="col-xs-2 text-right">
                  <a class="delBtn" href="/account/<%= item._id %>">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                  </a>
                </div>
              </div>
            </div>
            <% }) %>            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
<script>
  let delBtns = document.querySelectorAll(".delBtn");
  delBtns.forEach(item => {
    item.addEventListener("click", function(e) {
      if (confirm("确认删除?")) {
        return true;
      } else{
        e.preventDefault();
      }
    });
  });
</script>

CSRF = Cross-Site Request Forgery,跨站请求伪造

在同一浏览器下,访问一个攻击页面,攻击页面就可以向被攻击页面的服务器发送请求:

<link rel="stylesheet" href="http://127.0.0.1:3000/logout">

首页和 404

// views/404.ejs
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>404</title>
</head>
<body>
  <script src="//volunteer.cdn-go.cn/404/latest/404.js"></script>
</body>
</html>
// routes/web/index.js
/* GET home page. */
router.get('/', (req, res) => {
  res.redirect('/account');
});
// app.js
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  // next(createError(404));
  res.render('404');
});

token

token 是什么:token 是服务端生成并返回给 HTTP 客户端的一串加密字符串, token 中保存着用户信息

token 的作用:实现会话控制,可以识别用户的身份,主要用于移动端 APP

在这里插入图片描述
token 需要手动添加到请求报文中

在这里插入图片描述

JWT

JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证。JWT 使 token 的生成与校验更规范

npm i jsonwebtoken
const jwt = require('jsonwebtoken');

//创建 token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({
    username: 'zhangsan'
}, 'atguigu', {
    expiresIn: 60 //单位是 秒
});

console.log(token);

//解析 token
jwt.verify(token, 'atguigu', (err, data) => {
    if (err) {
        console.log('校验失败~~');
        return;
    }
    console.log(data);
});

jwt

一个 token 实例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwiaWF0IjoxNjk5MTg4NDU0LCJleHAiOjE2OTkxODg1MTR9.
Y1cQlEPAFIcfl5w4rUj9Xh_WOXYpG1c6QZmpjtkhFbM

分为 3 部分:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

记账本案例添加token

// api/auth.js
var express = require('express');
var router = express.Router();
const UserModel= require("../../models/UserModel");
const md5 = require('md5');
const jwt = require('jsonwebtoken');

//登录操作
router.post('/login', (req, res) => {
  //获取用户名和密码
  let {username, password} = req.body;
  //查询数据库
  UserModel.findOne({username: username, password: md5(password)}, (err, data) => {
    //判断
    if(err){
      res.json({
        code: '2001',
        msg: '数据库读取失败',
        data: null
      });
      return;
    }
    //判断 data
    if(!data){
      return res.json({
        code: '2002',
        msg: '账号或密码错误~~',
        data: null
      });
    }
    let token = jwt.sign({username: data.username, _id: data._id}, 'atguigu', {expiresIn: 60 * 60 *24});
    res.json({
      code: '0000',
      msg: '登录成功',
      data: token
    });
  });
});

//退出登录
router.post('/logout', (req, res) => {
  //销毁 session
  req.session.destroy(() => {
    res.render('success', {msg: '退出成功', url: '/login'});
  })
});

module.exports = router;
// app.js
const authApiRouter = require('./routes/api/auth');
app.use('/api', authApiRouter);

用户登录,下放 token
在这里插入图片描述

token校验

// middleWares/checkTokenMiddleware.js
const jwt = require('jsonwebtoken');

function checkTokenMiddleware(req, res, next) {
    let token = req.get('token');
    if(!token) {
      return res.json({
        code: '2003',
        msg: 'token 缺失',
        data: null
      });
    }
    jwt.verify(token, 'atguigu', (err, data) => {
      if (err) {
        return res.json({
          code: '2004',
          msg: '身份校验失败',
          data: null
        });
      }
      req.user = data;
      next();
    });
}
module.exports = checkTokenMiddleware;

将 token 放在头中发送请求:

// api/account.js
const express = require('express');
const router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel")
const jwt = require('jsonwebtoken');
const checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware');


// 获取账单列表
router.get('/account', checkTokenMiddleware, function(req, res, next) {
  // console.log(req.user);
  AccountModel.find({username: req.user.username}).sort({time: -1}).exec((err, data) => {
    if (err) {
      // res.status(500).send("读取失败");
      res.json({
        code: '1001',
        msg: '读取失败',
        data: null
      });
      return;
    }
    // res.render('list', {accounts: data, moment: moment});
    res.json({
        code: '0000',
        msg: '读取成功',
        data: data
    });
  });
});

// 新增记录
router.post('/account', checkTokenMiddleware, (req, res) => {
  console.log(req.body);
  // 表单验证...
  AccountModel.create({
    ...req.body,
    time: moment(req.time).toDate(),
  }).then((data) => {
    console.log(data);
    res.json({
        code: '0000',
        msg: '添加成功',
        data: data
    });
  });

  // res.render('success', {msg: "添加成功~~~", url: '/account'});
});

// 删除账单
router.delete("/account/:id", checkTokenMiddleware, (req, res) => {
  let id = req.params.id;
  AccountModel.deleteOne({_id: id}, (err, data) => {
    if (err) {
      // res.status(500).send("删除失败");
      res.json({
        code: '1000',
        msg: '删除失败',
        data: null
      });
      return;
    }
    // res.render('success', {msg: '删除成功', url: '/account'});
    res.json({
        code: '0000',
        msg: '删除成功',
        data: null
    });
  });
});

// 获取单条账单
router.get('/account/:id', checkTokenMiddleware, (req, res) => {
    let id = req.params.id;
    AccountModel.findById(id, (err, data) => {
        if (err) {
            return res.json({
                code: '1004',
                msg: '查询失败',
                data: null
            });
        }
        res.json({
            code: '0000',
            msg: '查询成功',
            data: data
        });
    });
});

// 更新单个账单
router.patch('/account/:id', checkTokenMiddleware, (req, res) => {
    let {id} = req.params;
    AccountModel.updateOne({_id: id}, req.body, (err, data) => {
        if (err) {
            return res.json({
                code: '1005',
                msg: '更新失败',
                data: null
            });
        }
        AccountModel.findById(id, (err, data) => {
            if (err) {
                return res.json({
                    code: '1004',
                    msg: '查询失败',
                    data: null
                });
            }
            res.json({
                code: '0000',
                msg: '更新成功',
                data: data
            });
        });
    });
});

module.exports = router;

前后端介绍

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194305.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AndroidStudio gitee令牌过期 解决方式 remote:Oauth: Access token is expired

记一次&#xff0c;gitee令牌过期 解决方式 Oauth: Access token is expired fatal: unable to access ‘https://gitee.com/xxxx.git/’: The requested URL returned error: 403 remote: [session-e14669a3] Oauth: Access token is expired fatal: unable to access https…

Swift编写爬取商品详情页面的爬虫程序

以下是一个使用Swift编写的基本爬虫程序&#xff0c;该程序使用Selenium库模拟浏览器行为来爬取商品详情页面的内容。 import Foundation import Selenium// 设置爬虫ip信息 let proxyHost "duoip" let proxyPort 8000 let proxy SeleniumProxy(httpProxy: "…

【MSF服务】3389远程连接命令扩展

攻击机IP地址&#xff08;kali&#xff09;&#xff1a;192.168.200.14 靶子机IP地址&#xff08;windows 10&#xff09;&#xff1a;192.168.200.81 前提&#xff1a;获取目标主机系统权限之后的操作 远程连接桌面 rdesktop -u username -p password iprdesktop报错 解决…

lesson4-C++内存管理

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 C/C内存分布 C语言中动态内存管理方式 C中动态内存管理 operator new与operator delete函数 new和delete的实现原理 定位new表达式(placement-new) 常见面试题 C/C内存分布 我们先来看一段代码&#xff1a; int…

【搭建IIS网站教程】

文章目录 一、搭建IIS网站二、http重定向三、虚拟目录四、用域名访问网站五、网站安全六、小知识总结 一、搭建IIS网站 1、Windows Server 2008中配置IIS 打开服务器管理器&#xff0c;角色&#xff0c;添加角色&#xff0c;然后点击下一步 选择安装web服务器iis 选择角色…

纯python代码快速实现简易带界面的数字华容道小游戏

数字华容道是一种经典的益智游戏&#xff0c;旨在通过移动数字方块的位置&#xff0c;将它们按照正确的顺序排列。游戏板由一个4x4的方格组成&#xff0c;其中包含了编号为1到15的数字方块&#xff0c;以及一个空白方块。 游戏开始时&#xff0c;数字方块被随机打乱并填充到方格…

Linux-用户与用户组,权限

1.用户组管理&#xff08;以下命令需root用户执行&#xff09; ①创建用户组 groupadd 用户组名 ②删除用户组 groupdel 用户组名 2.用户管理&#xff08;以下命令需root用户执行&#xff09; ①创建用户 useradd [-g -d] 用户名 >-g&#xff1a;指定用户的组&#xff0c;不…

香橙派5部署chatglm2-6b模型

香橙派5部署chatglm2-6b模型 环境信息&#xff1a; 部署服务器&#xff1a;orangepi 5 16G版本 系统版本&#xff1a;Ubuntu 22.04.3 LTS 参考文档&#xff1a; 利用GPU加速&#xff0c;在Orange Pi上跑LLMs &#xff1a; https://zhuanlan.zhihu.com/p/650110025 遥遥领…

Python语法基础(变量 注释 数据类型 输入与输出 运算符 缩进)

目录 变量变量命名规则变量的类型变量的创建变量的作用域 注释的方法数据类型对象和引用的概念Number(数字)数据转换 输入与输出输入函数输出函数输出函数的end参数输出格式多行语句 运算符算术运算符赋值运算符三目运算符运算符的优先级 缩进缩进格式注意事项层级嵌套 变量 标…

Web实验总

目录 网站需求&#xff1a; 思路&#xff1a; 实验步骤&#xff1a; 第一步&#xff1a;准备工作 第二步&#xff1a;新建一个存储网页的目录 第三步&#xff1a;修改本地hosts映射 第四步&#xff1a;修改配置文件&#xff0c;建立基于http服务的网站 1)创建用户song和…

计算机考研408到底有多难?25届开个好头很有必要

前言 大家好&#xff0c;我是陈橘又青&#xff0c;相信关注我的各位小伙伴们中&#xff0c;大多都是在计算机专业的大学生吧&#xff01; 每天都有许多人在后台私信我&#xff0c;问我要不要考研&#xff0c;我想说这个东西是因人而异的&#xff0c;像我本人就选择了就业&…

STL常用库函数复习

文章目录 pairvectorliststackqueuequeuepriority_queuequeue双端队列 set✨set集合✨multiset 多重集合了解&#xff1a;unordered_set 无序集合 map&#x1f31f;map几乎不用&#xff1a;multimap一般不用&#xff1a;undered_map pair utility示例 #include <iostream&…

使用Python自动检测SSL证书是否过期

目录 一、概述 二、SSL证书过期检测原理 三、Python实现SSL证书过期检测 四、注意事项 总结 一、概述 随着互联网的普及和安全意识的提高&#xff0c;SSL证书的使用变得越来越重要。SSL证书可以提供加密通信&#xff0c;保护用户的数据安全&#xff0c;防止中间人攻击等。…

切换闭锁元件 双位置继电器 TST-AB440GT DC24V 导轨安装

TST440AB-GT双位置继电器用于交直流操作的各种保护和自动控制的装置中&#xff0c;作为切换闭锁元件。 系列型号&#xff1a; DSP2-4A重动继电器&#xff1b;DSP2-2A2B重动继电器&#xff1b; DSP2-3A1B重动继电器&#xff1b;TST440GT双母线切换继电器&#xff1b; TST220GT双…

MIPSsim模拟器 使用说明

&#xff08;一&#xff09; 启动模拟器 双击MIPSsim.exe&#xff0c;即可启动该模拟器。模拟器启动时&#xff0c;自动将自己初始化为默认状态。所设置的默认值为&#xff1a; u所有通用寄存器和浮点寄存器为全0&#xff1b; u内存清零&#xff1b; u流水寄存器为全0&#xff…

Dapp开发流程以及应用

随着区块链技术的发展和普及&#xff0c;Dapp&#xff08;去中心化应用&#xff09;逐渐成为了区块链领域中备受关注的话题。Dapp是一种运行在区块链网络上的应用程序&#xff0c;具有去中心化、透明、安全、自治等特点&#xff0c;能够为人们提供更加便捷、高效、安全的应用体…

Essential Math for AI:高效的人工智能数学原理晋级读物

今天给大家介绍一本人工智能数学原理书籍&#xff1a;Essential Math for AI。作者是Hala Nelson&#xff0c;一位应用数学领域的美女博士&#xff0c;James Madison University (JMU) 大学的助理教授。 关注微信公众号&#xff1a;人工智能大讲堂&#xff0c;后台回复【ema】获…

使用反射调用私有内部类方法

使用反射调用私有内部类方法 通过反射机制调用私有内部类方法,反射机制允许在运行时检查和操作类和方法。可以使用反射机制创建内部类的实例,并调用其私有方法 🍓情况一: 注意这里的内部类是私有静态内部类 待测类如下: package jj;import java.lang.reflect.Constru…

MySQL时间类型注意事项

MySQL常见的时间类型有YEAR、DATE、TIME、DATETIME、TIMESTAMP&#xff0c;绝大多数业务都是精确到秒的&#xff0c;所以通常用后两种。并且MySQL5.6以后后两种支持精度到毫秒&#xff08;最多小数点后6位&#xff09; DATETIME占8字节&#xff0c;不论要不要毫秒 TIMESTAMP占4…

三大赛题指南发布!2023 冬季波卡黑客松本周末开启 Workshop

2023 年一众黑客松赛事中&#xff0c;为什么我们建议您选择波卡黑客松大赛&#xff1f;或许答案在于——作为开发者极度友好的技术生态&#xff0c;波卡能够从参赛者的立场出发&#xff0c;为大家提供从 0 到 1 实现项目孵化成长的机会。这里聚集了一线技术专家的资源力量&…