Access to image at from origin has been blocked by CORS policy: No ‘Access-Contr

181 阅读2分钟

问题背景

最近,在平台上发现很多图片无法展示,显示此图片来自微信公众平台,未经允许不可引用,分析原因后,发现是在发布文章时,富文本编辑器get图片,有跨域问题。

image.png

解决方案

在网上搜了很多方案,例如img.setAttribute('crossOrigin', 'anonymous'),发现无效,于是想到代理服务器。

思路:传递图片地址过去 让后端给转成buffer返回给前端,前端再上传二进制图片,到oss,获得链接

代理服务器非常简单,实现代码如下:

新建一个node项目

image.png

main.js
const express = require('express')
const fetch = require('node-fetch')

const app = express();

app.use('/proxy',  async (req, res) => {
    const imageUrl = req.query.url;
    try {
        const response = await fetch(imageUrl)
        const body = await response.buffer();
        res.set('Content-Type', 'image/jpeg')
        res.send(body)
    } catch(e) {
        res.status(500).send('error')
    }
})

app.listen(3333, () => {
    console.log('server is running on port 3333')
})
package.json
{
  "name": "img-proxy",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "commonjs",
  "scripts": {
    "dev": "node main.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2",
    "node-fetch": "^2.0.0"
  }
}

然后在nuxt.config.js中配置代码

'/proxy': {
   target: 'http://localhost:3333/',
   changeOrigin: true
},

在前端富文本中进行配置

<template>
  <div class="w-editor-box">
    <div class="wEditor" id="wEditor"></div>
    <input class="hide" type="file" id="wordFile" @change="getWordFile" accept=".docx" />
  </div>
</template>
<script>
import { attachNoticesApi } from "@/apis";
export default {
  data() {
    return {
      editor: null,
      isHTML: false,
      loading: false,
    };
  },
  methods: {
    initEditor() {
      let _this = this;
      const E = window.wangEditor;
      const { $, BtnMenu } = E;
      class AlertMenu extends BtnMenu {
        constructor(editor) {
          const $elem = E.$(
            `<div class="w-e-menu">
              <i class="cs-html"></i>
            </div>`
          );
          super($elem, editor);
        }
        clickHandler() {
          this.tryChangeActive();
        }
        tryChangeActive() {
          if (_this.isHTML) this.active();
          else this.unActive();
        }
      }

      this.editor = new E("#wEditor");
      const editor = this.editor;
      editor.config.focus = false;
      editor.config.menus = [
        "bold",
        "head",
        "italic",
        "underline",
        "strikeThrough",
        "list",
        "justify",
        "link",
        "quote",
        "image",
        "table",
        "code",
      ];
      editor.highlight = hljs;
      editor.config.languageType = [
        "SQL",
        "Shell Session",
        "Java",
        "JavaScript",
        "JSON",
        "Markdown",
        "TypeScript",
        "Plain text",
        "Html",
        "CSS",
        "Python",
        "XML",
        "Go",
        "Bash",
        "C",
        "C#",
        "C++",
        "Kotlin",
        "Lua",
        "PHP",
        "Ruby",
      ];
      // 注册菜单
      const menuKey = "alertMenuKey"; // 菜单 key ,各个菜单不能重复
      editor.menus.extend("alertMenuKey", AlertMenu);
      editor.config.menus = editor.config.menus.concat(menuKey);

      editor.config.zIndex = 8;
      editor.config.customUploadImg = function (resultFiles, insertImgFn) {
        resultFiles.forEach((file) => {
          let formdata = new FormData();
          formdata.append("file", file);
          attachNoticesApi(formdata).then((res) => {
            let imgUrl = res.data.operateCallBackObj;
            insertImgFn(imgUrl);
          });
        });
      };
      editor.config.pasteTextHandle = function (pasteStr) {
        // 需要判断是否是纯文本,不是纯文本执行函数,是纯文本返回
        // 验证图片中是否包含img标签,具有得到true否则得到false
        let containsImage = pasteStr.search(/<img /i) >= 0;
        // 存在图片就执行
        if (containsImage) {
          // 打开loading
          _this.pasteTextFulfill = true;
          _this.disposePasteImg(pasteStr).then((res) => {
            // 将内容追加上
            pasteStr = res
            editor.cmd.do('insertHTML', res)
            // 关闭loading
            _this.pasteTextFulfill = false;
          })
          return '';
        }else {
          return pasteStr;
        } 
      };
      editor.config.onchange = function (newHtml) {
        let _text = editor.txt.text();
        if (_this.isHTML) {
          newHtml = _text
        _this.$emit("setValue", newHtml, _text, "richtxt");
      };
      editor.create();
    },
    displayResult(result) {
      let _editor = this.editor;
      let _source = _editor.txt.html() + result.value;
      _editor.txt.html(_source);
      wordFile.value = "";
      this.loading = false;
    },
    async uploadBase64Image(base64Image, mime) {
      const formdata = new FormData();
      const _file = this.base64ToBlob(base64Image, mime);
      formdata.append("file", _file);
      let { data } = await attachNoticesApi(formdata);
      return data.operateCallBackObj;
    },
    base64ToBlob(base64, mime) {
      mime = mime || "";
      const sliceSize = 1024;
      const byteChars = window.atob(base64);
      const byteArrays = [];
      for (
        let offset = 0, len = byteChars.length;
        offset < len;
        offset += sliceSize
      ) {
        const slice = byteChars.slice(offset, offset + sliceSize);
        const byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
          byteNumbers[i] = slice.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
      }
      return new Blob(byteArrays, { type: mime });
    },
    // 处理粘贴的图片,传递的参数是粘贴来的数据
    disposePasteImg(pasteStr) {
      let _this = this;
      return new Promise(function (resolve) {
        // 用于计数图片数量
        let imgNum = 0;
        //匹配图片
        let imgReg = /<img.*?(?:>|\/>)/gi;
        //匹配src属性
        // eslint-disable-next-line no-useless-escape
        let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
        // 提取图片连接
        if (pasteStr.match(imgReg)) {
          pasteStr.replace(imgReg, function (txt) {
            return txt.replace(srcReg, function (src) {
              let img_src = src.match(srcReg)[1];
              //正则把?x-oss-process后面的都去掉
              img_src = img_src.replace(/\?.*/i, "");
              // 查找到一个图片地址就讲图片数量加1
              imgNum++;
              // 将图片转成图片文件对象并上传得到图片地址,传递的参数是图片地址
              _this.imgUrlSwitchBlob(img_src).then((res) => {
                /**
                 * 得到图片地址进行替换并将替换之后的文本返回渲染
                 */
                pasteStr = pasteStr.replace(img_src, res);
                // 替换之后将图片数量减1
                imgNum--;
                // 只有图片数量变成0的时候才会进行返回
                if (imgNum == 0) {
                  resolve(pasteStr);
                }
              });
            });
          });
        } else {
          resolve(pasteStr);
        }
      });
    },
    /**
     * @函数名称: 将图片地址转成文件对象
     * @返回值:图片地址
     * @描述: 接受的参数是图片的全地址路径,在函数中调用upPasteImg函数上传图片得到图片路径并返回
     * @其它: 使用Promise处理异步问题
     * @param {String} param 图片地址
     */
    imgUrlSwitchBlob(param) {
      let _this = this;
      return new Promise(function (resolve) {
        fetch(`/proxy?url=${param}`)
          .then((res) => {
            if (res.ok) return res.blob();
          })
          .then((blob) => {
            let suffix = param.substring(param.lastIndexOf(".") + 1); //获取后缀
            // 设置图片名称及后缀
            const crypto =
              window.crypto ||
              window.webkitCrypto ||
              window.mozCrypto ||
              window.oCrypto ||
              window.msCrypto;
            let _Math = crypto.getRandomValues(new Uint32Array(1));
            const key =
              new Date().getTime() +
              _Math.toString().substr(0, 5) +
              "." +
              suffix;
              // 创建图片对象
            let image = new Image();
            image.src = param;
            image.onload = () => {
              _this.upPasteImg(key, blob).then((res) => {
                resolve(res);
              });
            }
          });
      });
    },
    /**
     * @函数名称: 上传粘贴的图片
     * @返回值: 上传得到的图片地址
     * @其它: 使用Promise处理异步问题
     * @param {String} key  文件名称
     * @param {Object} file  文件对象
     */
    upPasteImg(key, file) {
      let _this = this;
      let formdata = new FormData();
      formdata.append("file", file);
      return new Promise(function (resolve) {
        attachNoticesApi(formdata).then((res) => {
          let imgUrl = res.data.operateCallBackObj;
          resolve(imgUrl);
        });
      });
    },
  },
  mounted() {
    this.initEditor();
  },
};
</script>

效果展示

传过去图片地址,前端根据后端返回的blob,上传图片,最终完美解决

image.png

上传二进制

image.png

返回图片地址