文件上传
文件上传使用的 http 报文格式,服务器解析处理请求体
利用 multer 库实现;
-
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> <p> <input type="text" name="a" /> </p> <p> <input type="file" name="img" accept="image/*" multiple /> </p> <p> <button>提交</button> </p> <img src="" alt="" /> <script> function upload() { const inpA = document.querySelector("[name=a]"); const inpFile = document.querySelector("[name=img]"); const img = document.querySelector("img"); const formData = new FormData(); //帮助你构建form-data格式的消息体 formData.append("a", inpA.value); for (const file of inpFile.files) { formData.append("img", file, file.name); } fetch("/api/upload", { body: formData, method: "POST", }) .then((resp) => resp.json()) .then((resp) => { console.log(resp); if (resp.code) { //有错误 alert(resp.msg); } else { img.src = resp.data; } }); } document.querySelector("button").onclick = upload; </script> </body> </html>
-
index.js
const express = require("express"); const app = express(); // 映射public目录中的静态资源 const path = require("path"); const staticRoot = path.resolve(__dirname, "../public"); app.use(express.static(staticRoot)); // 解析 application/x-www-form-urlencoded 格式的请求体 app.use(express.urlencoded({ extended: true })); // 解析 application/json 格式的请求体 app.use(express.json()); app.use("/api/upload", require("./upload")); // 处理错误的中间件 app.use(require("./errorMiddleware")); const port = 5008; app.listen(port, () => { console.log(`server listen on ${port}`); });
-
upload.js
const express = require("express"); const router = express.Router(); const multer = require("multer"); const path = require("path"); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, path.resolve(__dirname, "../public/upload")); }, filename: function (req, file, cb) { // 时间戳-6位随机字符.文件后缀 const timeStamp = Date.now(); const ramdomStr = Math.random().toString(36).slice(-6); const ext = path.extname(file.originalname); const filename = `${timeStamp}-${ramdomStr}${ext}`; cb(null, filename); }, }); const upload = multer({ storage, limits: { // fileSize: 150 * 1024, }, fileFilter(req, file, cb) { //验证文件后缀名 const extname = path.extname(file.originalname); const whitelist = [".jpg", ".gif", "png"]; if (whitelist.includes(extname)) { cb(null, true); } else { cb(new Error(`your ext name of ${extname} is not support`)); } }, }); router.post("/", upload.single("img"), (req, res) => { const url = `/upload/${req.file.filename}`; console.log(url); res.send({ code: 0, msg: "", data: url, }); }); module.exports = router;
文件下载
-
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> <a resrole="thunder" href="/res/hill.zip">下载</a> <!-- 迅雷下载协议 thunder://base64(AA地址ZZ) --> <button>迅雷下载</button> <script> document.querySelector("button").onclick = () => { const a = document.querySelector("a[resrole=thunder]"); a.href = "thunder://" + btoa(`AA${a.href}ZZ`); }; </script> </body> </html>
-
index.js
const express = require("express"); const app = express(); // 映射public目录中的静态资源 const path = require("path"); const staticRoot = path.resolve(__dirname, "../public"); app.use(express.static(staticRoot)); // 解析 application/x-www-form-urlencoded 格式的请求体 app.use(express.urlencoded({ extended: true })); // 解析 application/json 格式的请求体 app.use(express.json()); // 处理对下载资源的请求 app.use("/res", require("./download")); // 处理错误的中间件 app.use(require("./errorMiddleware")); const port = 5008; app.listen(port, () => { console.log(`server listen on ${port}`); });
-
download.js
const express = require("express"); const path = require("path"); const router = express.Router(); router.get("/:filename", (req, res) => { const absPath = path.resolve( __dirname, "../public", req.params.filename ); res.download(absPath, req.params.filename); }); module.exports = router;
图片水印
const path = require("path");
const jimp = require("jimp");
// 给一张图片加水印
async function mark(waterFile, originFile, targetFile, proportion = 5, marginProportion = 0.01) {
const [water, origin] = await Promise.all([
jimp.read(waterFile),
jimp.read(originFile),
]);
// 对水印图片进行缩放
const curProportion = origin.bitmap.width / water.bitmap.width;
water.scale(curProportion / proportion);
// 计算位置
const right = origin.bitmap.width * marginProportion;
const bottom = origin.bitmap.height * marginProportion;
const x = origin.bitmap.width - right - water.bitmap.width;
const y = origin.bitmap.height - bottom - water.bitmap.height;
// 写入水印
origin.composite(water, x, y, {
mode: jimp.BLEND_SOURCE_OVER,
opacitySource: 0.3,
});
await origin.write(targetFile);
}
async function test() {
const waterPath = path.resolve(__dirname, "./water.jpg");
const originPath = path.resolve(__dirname, "./origin.jpg");
const targetPath = path.resolve(__dirname, "./new.jpg");
mark(waterPath, originPath, targetPath);
}
test();
图片防盗链
-
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> <img src="./img/water.jpg" alt=""> <!-- 防盗链 --> <img src="http://127.0.0.1:5008/img/water.jpg" alt=""> </body> </html>
-
index.js
const express = require("express"); const app = express(); const cors = require("cors"); app.use(require("./imgProtectMid")); // 映射public目录中的静态资源 const path = require("path"); const staticRoot = path.resolve(__dirname, "../public"); app.use(express.static(staticRoot)); // 解析 application/x-www-form-urlencoded 格式的请求体 app.use(express.urlencoded({ extended: true })); // 解析 application/json 格式的请求体 app.use(express.json()); // 处理错误的中间件 app.use(require("./errorMiddleware")); const port = 5008; app.listen(port, () => { console.log(`server listen on ${port}`); });
-
imgProtectMid.js
const url = require("url"); const path = require("path"); module.exports = (req, res, next) => { const host = req.headers.host; //获取本网站的主机名(包括端口号) let referer = req.headers.referer; // 只处理图片 const extname = path.extname(req.path); if (![".jpg", ".jpeg", ".png", ".gif"].includes(extname)) { next(); return; } if (referer) { referer = url.parse(referer).host; } if (referer && host !== referer) { req.url = "/img/logo.jpg"; // url rewrite } next(); };
客户端代理
利用 http-proxy-middleware 中间件、利用 http 模块 实现客户端代理;
访问地址:http://localhost:5008/data/api/movie 代理到 http://yuanjin.tech:5100/api/movie
-
index.js
const express = require("express"); const app = express(); const cors = require("cors"); // 解析 application/x-www-form-urlencoded 格式的请求体 app.use(express.urlencoded({ extended: true })); // 解析 application/json 格式的请求体 app.use(express.json()); // 使用代理 app.use(require("./proxyMid")); // 处理错误的中间件 app.use(require("./errorMiddleware")); const port = 5008; app.listen(port, () => { console.log(`server listen on ${port}`); });
-
proxyMid.js(利用中间件)
const { createProxyMiddleware } = require("http-proxy-middleware"); const context = "/data"; module.exports = createProxyMiddleware(context, { target: "http://yuanjin.tech:5100", pathRewrite: function (path, req) { console.log(path.substr(context.length)); return path.substr(context.length); }, });
-
proxyMidWrite.js(利用 http模块 手动实现)
const http = require("http"); // /data/api/xxxxx ---> http://yuanjin.tech:5100/api/xxxx module.exports = (req, res, next) => { const context = "/data"; if (!req.path.startsWith(context)) { //不需要代理 next(); return; } // 需要代理 const path = req.path.substr(context.length); // 创建代理请求对象 request const request = http.request( { host: "yuanjin.tech", port: 5100, path: path, method: req.method, headers: req.headers, }, (response) => { // 代理响应对象 response res.status(response.statusCode); for (const key in response.headers) { res.setHeader(key, response.headers[key]); } response.pipe(res); } ); req.pipe(request); //把请求体写入到代理请求对象的请求体中 };
模板引擎
在静态内容中插入动态内容
-
使用方式一:使用字符串
HTMLjs<!-- index.html --> <body> <h2>{{ foo }}</h2> <ul> <!-- 遍历 todos--> {{ each todos }} <!-- $value 就是遍历项 --> <li>{{ $value.title }}</li> {{ /each }} </ul> </body>
// app.js const express = require('express') const fs = require('fs') const template = require('art-template') const app = express() const todos = [{ id: 1, title: '吃饭1' }, { id: 2, title: '吃饭2' }, { id: 3, title: '吃饭3' }, { id: 4, title: '吃饭4' } ] app.get('/', (req, res) => { // 1. 读取模板内容 fs.readFile('./views/index.html', 'utf8', (err, templateStr) => { if (err) { return res.status(404).send('404 Not Found.') } // 2. 获取数据 // 3. 渲染这件事儿是在服务端完成的 // 所谓的模板引擎就是在根据特定的规则进行字符串解析替换 const ret = template.render(templateStr, { // 模板中使用的数据 foo: 'bar', todos }) // 把渲染结果发送给客户端 res.end(ret) }) }) app.listen(3000, () => { console.log(`Server running at http://localhost:3000/`) })
-
使用方式二:使用 art 模板
htmlJS<!-- index.art --> <body> <h2>{{ foo }}</h2> <ul> <!-- 遍历 todos--> {{ each todos }} <!-- $value 就是遍历项 --> <li>{{ $value.title }}</li> {{ /each }} </ul> </body>
// app.js const express = require('express') const fs = require('fs') const template = require('art-template') const path = require('path') const app = express() // view engine setup app.engine('html', require('express-art-template')) // 当渲染以 .art 结尾的资源文件的时候使用 express-art-template app.set('view options', { // art-template 模板引擎配置 debug: process.env.NODE_ENV !== 'production' }) app.set('views', path.join(__dirname, 'views')) // 模板文件的存储目录 app.set('view engine', 'html') // 可以省略的模板文件后缀名 const todos = [{ id: 1, title: '吃饭1' }, { id: 2, title: '吃饭2' }, { id: 3, title: '吃饭3' }, { id: 4, title: '吃饭4' } ] app.get('/', (req, res) => { // 只要配置模板引擎,就可以使用 res.render 方法渲染页面了 // 1、读模板文件 2、渲染 3、发送响应 res.render('index', { foo: 'bar', todos }) }) app.listen(3000, () => { console.log(`Server running at http://localhost:3000/`) })
生成二维码
二维码的概念
矩阵点
:通常是白色或黑色的小点,深色表示 1 ,白色表示 0位置探测组
:三个位于角落的嵌套矩形,用于定位二维码图片的方向Version
:1~40 的数字,数字越大,表示整个二维码的矩阵越大mode
:字符编码方式(Numeric、Alphanumeric、Kanji、Byte)纠错等级
:纠错等级越高,能够表达的字符量越少(L、M、Q、H)利用 qrcode 生成二维码
<!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>
<div id="divcode"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.js"></script>
<script>
new QRCode(divcode, {
text: "http://yuanjin.tech",
width: 128,
height: 128,
colorDark: "blue",
colorLight: "#fff",
correctLevel: QRCode.CorrectLevel.H,
src: "./img/logo.jpg"
});
</script>
</body>
</html>
生成验证码
验证码作用:防止机器提交
验证码类型:普通验证码、行为验证码
获取验证码图片
- 客户端通过 img 元素的 src 地址获取验证码图片
- 服务器生成随机图片
- 服务器保存随机图片中的文字
验证
- 服务器判断是否对验证码进行验证
- 验证客户端传递的验证码是否和服务器保存的一致
-
index.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> <p>账号:<input type="text" id="loginId" /></p> <p>密码:<input type="password" id="loginPwd" /></p> <p id="captchaArea"> 验证码:<input type="text" id="captcha" /> <img id="imgCaptcha" src="/captcha" alt="" /> </p> <p> <button>登录</button> </p> <script> function refreshCaptcha() { imgCaptcha.src = `/captcha?rad=${Math.random()}`; } imgCaptcha.onclick = refreshCaptcha document.querySelector("button").onclick = async function () { const body = { loginId: loginId.value, loginPwd: loginPwd.value, captcha: captcha.value }; const resp = await fetch("/api/admin/login", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(body), }).then((resp) => resp.json()); if (resp.code === 401) { console.log("验证码错误"); captchaArea.style.display = "block"; refreshCaptcha(); } else if (resp.data) { console.log("登录成功"); } else { console.log("登录失败"); } }; </script> </body> </html>
-
index.js
const express = require("express"); const app = express(); const cors = require("cors"); app.use( require("express-session")({ secret: "ricepudding", }) ); // 映射public目录中的静态资源 const path = require("path"); const staticRoot = path.resolve(__dirname, "../public"); app.use(express.static(staticRoot)); // 解析 application/x-www-form-urlencoded 格式的请求体 app.use(express.urlencoded({ extended: true })); // 解析 application/json 格式的请求体 app.use(express.json()); app.use(require("./captchaMid")); // 处理错误的中间件 app.use(require("./errorMiddleware")); const port = 5008; app.listen(port, () => { console.log(`server listen on ${port}`); });
-
captchaMid.js
const express = require("express"); const router = express.Router(); var svgCaptcha = require("svg-captcha"); router.get("/captcha", (req, res) => { var captcha = svgCaptcha.create({ size: 6, ignoreChars: "iIl10oO", noise: 6, color: true, }); req.session.captcha = captcha.text.toLowerCase(); // 把验证码中的文本存放到 session 中 res.type("svg"); res.status(200).send(captcha.data); }); function validateCaptcha(req, res, next) { const reqCaptcha = req.body.captcha ? req.body.captcha.toLowerCase() : ""; // 用户传递的验证码 if (reqCaptcha !== req.session.captcha) { // 验证码有问题 res.send({ code: 401, msg: "验证码有问题", }); } else { next(); } req.session.captcha = ""; } // 对所有的 post、put 请求做验证 router.post("*", validateCaptcha); router.put("*", validateCaptcha); module.exports = router;
客户端缓存
富文本框
富文本框的本质:一个可以被编辑的 div ,编辑后得到的结果是一个 html 字符串;
利用 wangEditor 中间件实现;
Express👉 路由器中间件
上一篇