文件上传

  • 文件上传使用的 http 报文格式,服务器解析处理请求体

  • 利用 multer 库实现;

  1. 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>
    
  2. 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}`);
    });
    
  3. 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;
    

文件下载

  1. 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>
    
  2. 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}`);
    });
    
  3. 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();

图片防盗链

  1. 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>
    
  2. 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}`);
    });
    
  3. 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

  1. 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}`);
    });
    
  2. 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);
      },
    });
    
  3. 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); //把请求体写入到代理请求对象的请求体中
    };
    

模板引擎

在静态内容中插入动态内容

  1. 使用方式一:使用字符串

    HTML
    js
    <!-- 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/`) })
    
  2. 使用方式二:使用 art 模板

    html
    JS
    <!-- 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. 二维码的概念

    • 矩阵点:通常是白色或黑色的小点,深色表示 1 ,白色表示 0
    • 位置探测组:三个位于角落的嵌套矩形,用于定位二维码图片的方向
    • Version1~40 的数字,数字越大,表示整个二维码的矩阵越大
    • mode:字符编码方式(NumericAlphanumericKanjiByte
    • 纠错等级:纠错等级越高,能够表达的字符量越少(LMQH
  2. 利用 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>

生成验证码

  1. 验证码作用:防止机器提交

  2. 验证码类型:普通验证码、行为验证码

  3. 获取验证码图片

    • 客户端通过 img 元素的 src 地址获取验证码图片
    • 服务器生成随机图片
    • 服务器保存随机图片中的文字
  4. 验证

    • 服务器判断是否对验证码进行验证
    • 验证客户端传递的验证码是否和服务器保存的一致
  1. 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>
    
  2. 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}`);
    });
    
  3. 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 中间件实现;

  • demo 地址

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 文件上传
  2. 2. 文件下载
  3. 3. 图片水印
  4. 4. 图片防盗链
  5. 5. 客户端代理
  6. 6. 模板引擎
  7. 7. 生成二维码
  8. 8. 生成验证码
  9. 9. 客户端缓存
  10. 10. 富文本框