工作随笔🚀🚀🚀

108 阅读4分钟

image.png

用于记录在工作与学习的过程中,发现的各种开发技巧与解决方案,便于日后再遇到同类问题时能快速查阅到解决方案。

  1. 解决create-react-app 脚手架打包时,运行内存溢出问题的命令 set NODE_OPTIONS=--max_old_space_size=4096&&react-scripts build

  2. 缺少python2.7支持,可快速使用以下语句完成安装 npm install --global --production windows-build-tools

  3. 针对页面指定区域生成pdf文件的方法

import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
import Canvg from 'canvg'

/*
  利用Canvg导出页面指定区域为PDF格式
*/
export const getPdfByCanvg = (title, container) => {
  return new Promise(async(resolve, reject)=>{
    try {
      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')
      canvas.setAttribute('width', '8000px')
      canvas.setAttribute('height', '1500px')
      canvas.setAttribute('position', 'fixed')
      canvas.setAttribute('top', '99999999px')
      document.body.appendChild(canvas)

      /*
        由于canvg库不支持filter属性,如果加上,矩形和圆形的描边或者填充都失效,所以在转换之前先将filter去掉,使用stroke代替
      */
      let rectArr = Array.from(container.getElementsByTagName('rect'))
      rectArr.forEach(item=>{
        item.setAttribute('stroke', '#ccc')
        item.removeAttribute('filter')
      })
      let rootCircle = container.getElementsByTagName('circle')[0]
      rootCircle.setAttribute('stroke', '#ccc')
      rootCircle.removeAttribute('filter')

      let v = await Canvg.from(ctx, `<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><rect fill="white" stroke="none" width="${container.clientWidth}" height="${container.clientHeight}" pointer-events="all"></rect>${container.innerHTML.trim()}</svg>`, {
        offsetX: 3000
      })
      await v.render()

      let a = document.createElement('a')
      a.style.display = 'none'

      let contentWidth = 8000
      let contentHeight = 1500
      let pageHeight = contentWidth / 592.28 * 841.89
      let leftHeight = contentHeight
      let position = 0
      let imgWidth = 595.28
      let imgHeight = 592.28 / contentWidth * contentHeight
      let pageData = canvas.toDataURL('image/png', 1.0)
      let PDF = new JsPDF('', 'pt', 'a4')
      if (leftHeight < pageHeight) {
        PDF.addImage(canvas, 'PNG', 0, 0, imgWidth, imgHeight)
      } else {
        while (leftHeight > 0) {
          PDF.addImage(canvas, 'PNG', 0, position, imgWidth, imgHeight)
          leftHeight -= pageHeight
          position -= 841.89
          if (leftHeight > 0) {
            PDF.addPage()
          }
        }
      }
      PDF.save(title + '.pdf')
      document.body.removeChild(canvas)
      resolve()
    } catch (err){
      reject(err)
    } finally {
      /*
        还原矩形与圆形的样式
      */
      let rectArr = Array.from(container.getElementsByTagName('rect'))
      let rootCircle = container.getElementsByTagName('circle')[0]
      rectArr.forEach(item=>{
        item.setAttribute('stroke', 'none')
        item.setAttribute('filter', 'drop-shadow(0px 5px 5px rgba(0,0,0,0.1))')
      })
      rootCircle.setAttribute('stroke', 'none')
      rootCircle.setAttribute('filter', 'drop-shadow(0px 5px 5px rgba(0,0,0,0.1))')
    }
  })
}
/*
  利用html2canvas导出页面指定区域为PDF格式
*/
export const getPdfByHtml2canvas = (title, container) => {
  return html2Canvas(container, {
    allowTaint: true,
    width: 8000,
    height: 1500,
    x: 3000
  }).then(canvas=>{
    let contentWidth = canvas.width
    let contentHeight = canvas.height
    let pageHeight = contentWidth / 592.28 * 841.89
    let leftHeight = contentHeight
    let position = 0
    let imgWidth = 595.28
    let imgHeight = 592.28 / contentWidth * contentHeight
    let pageData = canvas.toDataURL('image/jpeg', 1.0)
    let PDF = new JsPDF('', 'pt', 'a4')
    if (leftHeight < pageHeight) {
      PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
    } else {
      while (leftHeight > 0) {
        PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
        leftHeight -= pageHeight
        position -= 841.89
        if (leftHeight > 0) {
          PDF.addPage()
        }
      }
    }
    PDF.save(title + '.pdf')
  })
}
  1. Mac下最简单翻墙的方法,自动代理配置url:raw.githubusercontent.com/bannedbook/…,

  2. linux 开启端口 /sbin/iptables -I INPUT -p http --dport 3000 -j ACCEPT,查看linux端口状态 netstat -anp

  3. 生成ssh秘钥:ssh-keygen -t rsa -C "any comment can be written here"

  4. jenkins构建失败后自动重新进行构建的插件:Naginator

  5. 解决git拉取代码时报错 gnutls_handshake() failed: The TLS connection was non-properly terminated.: 将代码仓库地址的协议更改为http

  6. node.js连接mysql出现错误:ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    解决方案:

  • ALTER USER '用户名' IDENTIFIED WITH mysql_native_password BY '密码';
  • FLUSH PRIVILEGES;
  1. 生成rsa公钥与私钥
  • 打开命令行工具,输入openssl,打开openssl
  • 生成私钥:genrsa -out rsa_private_key.pem 2048
  • 生成公钥: rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
  1. JSON.parse解析换行符\n会报错,需要对目标字符串中\n进行替换: '目标字符串\n'.replace(/\n/g, '\\n')

  2. cursor: url 注意事项

  • 图片大小不能大于32*32
  • 图片格式最好是.cur和.ico
  1. 用于RSA加密的npm包: jsencrypt

  2. webpack构建输出bundle详细信息文件stats.json: 增加--profile --json参数

  3. 可以调整 creat-react-app 脚手架webpack配置项的npm包:@rescripts/cli

  4. 在设置webpack运行时publicPath时,报错'__webpack_public_path__' is not defined no-undef,原因是 ESLint规则警告有关使用未声明的变量,需要告诉ESLint __webpack_public_path__是全局变量。,解决方案有三种:

  • 在使用 webpack_public_path 的地方加上注释:/* global __webpack_public_path__:writable */
  • 直接在使用 webpack_public_path 的地方加上注释: //eslint-disable-next-line//eslint-disable-line
  • 配置eslint
{
  "globals": {
    "__webpack_public_path__": "writable"
  }
}

17. 客户端连接mysql服务的两种方式:

  • mysql --host=localhost --user=myname --password=password mydb
  • mysql -h localhost -u myname -ppassword mydb

需要注意如果明确指定了-p或者--password的值,那么-p或者--password和密码之间是不能有空格的,如果你使用了-p或者--password选项但是没有给出password值,客户端会提示输入密码

  1. webpack报错 Uncaught TypeError: Cannot read property 'bind' of undefined, 直接原因是webpack运行时代码里的webpackJsonppush方法是undefined导致的,根本原因是多个bundle使用了相同的output.jsonpFunction的值,所以只需将其赋值为不一样的值就可以解决了,详见issue

  2. 开发node的CLI工具的项目中,所有依赖项必须是dependencies。如果是devDependencies,那么在全局安装CLI工具时不会自动安装对应依赖

  3. fatal: refusing to merge unrelated histories解决方案: git merge master --allow-unrelated-histories

  4. 软件许可证图例 软件许可证图例

  5. 移动端视口配置 <meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">,user-scalable设置为no可以解决移动端点击事件延迟问题

  6. md文档一键转换为微信公众号文章:md.openwrite.cn/?from=didi

  7. eslint删除无用导入的插件:eslint-plugin-unused-imports

  8. 关键字高亮处理方法:

    const highlightKeyWord = (text, keyword) => {
      return keyword
        ? text.split(new RegExp(`(${keyword})`, 'gi')).map((c, i) =>
            c === keyword ? (
              <span key={i} style={{ color: '#009ba5' }}>
                {c}
              </span>
            ) : (
              c
            )
          )
        : text;
    };
  1. js复制文本方法
  //方法1
  await navigator.clipboard.writeText('some text');
  
  //方法2
   const input = document.createElement('input');
   input.setAttribute('value','some text');
   document.body.appendChild(input);
   input.select();
   document.execCommand('copy');
   document.body.removeChild(input);
  1. 中英文加数字混合排序
export function mixSort(_a, _b) {
  const reg = /[a-zA-Z0-9]/
  // 比对仅针对字符串,数字参与对比会导致对比的字符串转为number类型,变成NaN
  const a = _a.toString()
  const b = _b.toString()
  // 比对0号位的原因是字符串中有可能出现中英文混合的情况,这种仅按首位排序即可
  if (reg.test(a[0]) || reg.test(b[0])) {
    if (a > b) {
      return 1
    } else if (a < b) {
      return -1
    } else {
      return 0
    }
  } else {
    return a.localeCompare(b)
  }
}
  1. 给图片添加水印
    (async ()=>{
         const genWaterMark = async (imgSrc: string) => {
         const canvas = document.createElement('canvas');
         // ① 图片路径转成canvas
         await imgSrc2Canvas(canvas, imgSrc);
         // ② canvas添加水印
        addWatermark(canvas);
        // ③ canvas转成img
         return canvas.toDataURL('image/png');
      };
     const addWatermark = async canvas => {
         const ctx = canvas.getContext('2d');
         ctx.fillStyle = 'rgba(100,100,100,0.2)'; // 字体颜色
         ctx.font = `24px serif`;
         ctx.translate(0, 0);
         ctx.rotate((5 * Math.PI) / 180); // 旋转角度
         const repeatX = Math.floor((canvas.width / 24) * 4); // 100 为每个水印的基本宽度
         const repeatY = Math.floor((canvas.height / 24) * 4);
         for (let i = 0; i < repeatX; i++) {
             for (let j = 1; j < repeatY; j++) {
                  ctx.fillText(`哈哈哈哈`, 24 * 4 * i + 20, 24 * j + 20); // 控制水印的疏密
             }
           }
      };
     const imgSrc2Canvas = (cav: HTMLCanvasElement, imgSrc: string) => {
            return new Promise(async resolve => {
                   const image = new Image();
                    image.src = imgSrc;
                    // ① 为图片设置crossOrigin属性,防止Failed to execute 'toDataURL' on 'HTMLCanvasElement'
                    image.setAttribute('crossOrigin', 'anonymous');
                    // ② 解决渲染图片为透明图层
                    await new Promise(resolve => (image.onload = resolve));
                    cav.width = image.width;
                    cav.height = image.height;
                    const ctx = cav.getContext('2d');
                    if (ctx) {
                      ctx.drawImage(image, 0, 0);
                    }
                    resolve(cav);
                  });
               };

         const link = document.createElement('a');
         link.download = `pic.png`;
         const url = await genWaterMark(dataUrl);
         link.href = url;
         link.click();
    })()
  1. 当给“图片元素”添加mousemove事件时,需要设置draggable=false,否则会出现mousemove事件不触发的bug

  2. 禁用移动端手势返回

  window.addEventListener('popstate', () => {
    history.pushState(null, '', document.URL)
  })
  1. ai隐式标识打标
/** 工具函数 */
function blobToDataURL(blob: Blob): Promise<string> {
  return new Promise(resolve => {
    const reader = new FileReader()
    reader.onload = () => resolve(reader.result as string)
    reader.readAsDataURL(blob)
  })
}

function dataURLToBlob(dataURL: string, type: string): Blob {
  const byteString = atob(dataURL.split(',')[1])
  const ab = new ArrayBuffer(byteString.length)
  const ia = new Uint8Array(ab)
  for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i)
  return new Blob([ab], { type })
}

function stringToExifUnicode(str: string): Uint8Array {
  const utf16 = unescape(encodeURIComponent(str))
  const arr = new Uint8Array(utf16.length + 8)
  const prefix = [85, 78, 73, 67, 79, 68, 69, 0] // "UNICODE\0"
  arr.set(prefix, 0)
  for (let i = 0; i < utf16.length; i++) arr[i + 8] = utf16.charCodeAt(i)
  return arr
}
/** Blob / File / ArrayBuffer / Uint8Array 转 ArrayBuffer */
async function toArrayBuffer(input: Blob | ArrayBuffer | Uint8Array): Promise<ArrayBuffer> {
  if (input instanceof Blob) return await input.arrayBuffer()
  if (input instanceof ArrayBuffer) return input
  if (input instanceof Uint8Array) {
    // 确保返回的是 ArrayBuffer 而不是 SharedArrayBuffer
    const buffer = input.buffer
    if (buffer instanceof ArrayBuffer) {
      return buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)
    }
    // 如果是 SharedArrayBuffer,创建一个新的 ArrayBuffer
    const newBuffer = new ArrayBuffer(input.byteLength)
    new Uint8Array(newBuffer).set(input)
    return newBuffer
  }
  throw new Error('Unsupported input type')
}


//PDF
//依赖pdf-lib库
async function setAIGCPropertyForPDF(fileOrBuffer: Blob | ArrayBuffer | Uint8Array, aigcData: any): Promise<Blob> {
  if (fileOrBuffer instanceof Blob) {
    fileOrBuffer = await fileOrBuffer.arrayBuffer();
  }

  const aigcJson = JSON.stringify(aigcData);
  const pdfEscapedJson = aigcJson.replace(/([\\()])/g, '\\$1');
  const pdfDoc = await PDFDocument.load(fileOrBuffer);
  const allDicts: PDFDict[] = [];
  const indirectObjects = pdfDoc.context.enumerateIndirectObjects();
  let infoDict = null;

  // 遍历查找所有字典对象
  for (const [_, obj] of indirectObjects) {
    if (obj instanceof PDFDict) {
      allDicts.push(obj);
    }
  }
  infoDict = allDicts.find((item) => {
    for (const [key] of item.dict) {
      if (key.encodedName === '/CreationDate') {
        return true;
      }
    }
  });
  if (infoDict) {
    infoDict.set(PDFName.of('AIGC'), PDFString.of(pdfEscapedJson));
  }

  const blob = new Blob([await pdfDoc.save()], { type: 'application/pdf' });
  return blob;
}


//MD
/**
 * 设置 Markdown 文件头部的 AIGC 元数据
 * @param mdContent - 原始 Markdown 文本
 * @param aigcData - AIGC 元数据对象
 * @returns 新的 Markdown 文本
 */
export function setAIGCPropertyForMD(mdContent: string, aigcData: AIGCData): string {
  // 构造AIGC的YAML块
  const yamlEscape = (str: string) => `'${str.replace(/'/g, "''")}'`
  const aigcYamlLines = [
    'AIGC:',
    ...Object.entries(aigcData)
      .filter(([, value]) => value !== undefined && value !== null)
      .map(([key, value]) => `  ${key}: ${yamlEscape(String(value))}`)
  ]
  const yamlBlock = `---\n${aigcYamlLines.join('\n')}\n---`

  if (mdContent.startsWith('---')) {
    // 替换或插入AIGC块
    const parts = mdContent.split(/\n---\s*/)
    let yamlLines = parts[0].split('\n')
    const aigcStart = yamlLines.findIndex(line => line.trim() === 'AIGC:')
    if (aigcStart !== -1) {
      // 替换AIGC块
      let endIdx = aigcStart + 1
      while (endIdx < yamlLines.length && yamlLines[endIdx].startsWith('  ')) endIdx++
      yamlLines.splice(aigcStart, endIdx - aigcStart, ...aigcYamlLines)
    } else {
      // 插入到第二行(保留YAML格式)
      yamlLines.splice(1, 0, ...aigcYamlLines)
    }
    return `${yamlLines.join('\n')}\n---${parts.length > 1 ? '\n' + parts.slice(1).join('\n---') : ''}`
  } else {
    // 没有 front matter,直接插入
    return `${yamlBlock}\n\n${mdContent}`
  }
}


//XML
/**
 * 基于 OOXML 自定义属性写入 AIGC 元数据
 * 包含docx、pptx、xlsx、dotx、xltx、potx;
 * 依赖:npm install jszip
 * @returns 新的 XML blob
 *
 */
export async function setAIGCPropertyForXML(
  fileOrBlob: Blob | ArrayBuffer | Uint8Array,
  aigcData: AIGCData
): Promise<Blob> {
  const buffer = await toArrayBuffer(fileOrBlob)

  const zip = new JSZip()
  await zip.loadAsync(buffer)

  const aigcJson = JSON.stringify(aigcData)
  const customPath = 'docProps/custom.xml'

  let customXml = await zip.file(customPath)?.async('string')
  if (!customXml) {
    customXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
    xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
</Properties>`
  }

  const parser = new DOMParser()
  const xmlDoc = parser.parseFromString(customXml, 'application/xml')

  // 删除已有 AIGC 节点
  const oldProp = Array.from(xmlDoc.getElementsByTagName('property')).find(p => p.getAttribute('name') === 'AIGC')
  if (oldProp && oldProp.parentNode) {
    oldProp.parentNode.removeChild(oldProp)
  }

  // 创建新 property
  const prop = xmlDoc.createElementNS(
    'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties',
    'property'
  )
  prop.setAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}')
  prop.setAttribute('pid', Date.now().toString().slice(-6)) // 生成唯一 pid
  prop.setAttribute('name', 'AIGC')

  const vtNS = 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'
  const lpwstr = xmlDoc.createElementNS(vtNS, 'vt:lpwstr')
  lpwstr.textContent = aigcJson
  prop.appendChild(lpwstr)

  xmlDoc.documentElement.appendChild(prop)

  // 写回 zip
  const serializer = new XMLSerializer()
  zip.file(customPath, serializer.serializeToString(xmlDoc))

  return zip.generateAsync({ type: 'blob' })
}


//png,jpg
/** 统一方法:PNG / JPEG 兼容 */
export async function setAIGCPropertyForImage(blob: Blob, aigcData: AIGCData, useCustomChunk = false): Promise<Blob> {
  if (blob.type === 'image/png') {
    return setAIGCPropertyForPng(blob, aigcData, useCustomChunk)
  } else if (blob.type === 'image/jpeg' || blob.type === 'image/jpg') {
    return setAIGCPropertyForJpg(blob, aigcData)
  } else {
    throw new Error(`Unsupported image type: ${blob.type}`)
  }
}


//png
/**
 * 向 PNG Blob 写入 AIGC 元数据
 * @param blob PNG 文件 Blob
 * @param aigcData AIGC 元数据对象
 * @param useCustomChunk 是否使用自定义块(否则用 tEXt)
 * @returns 新的 PNG Blob
 */
async function setAIGCPropertyForPng(blob: Blob, aigcData: AIGCData, useCustomChunk = false): Promise<Blob> {
  const buffer = new Uint8Array(await blob.arrayBuffer())
  const chunks = extractChunks(buffer)
  const jsonStr = JSON.stringify(aigcData)

  // 删除已有 AIGC 块
  const cleanedChunks = chunks.filter(chunk => {
    if (chunk.name === 'tEXt') {
      const text = new TextDecoder().decode(chunk.data)
      return !text.startsWith('AIGC=')
    }
    return chunk.name !== 'AIGC'
  })

  // 构造新块
  const newChunk = useCustomChunk
    ? {
        name: 'AIGC',
        data: new TextEncoder().encode(jsonStr)
      }
    : PNGTextChunk.encode('AIGC', jsonStr)

  // 插入到第一个 IDAT 之前
  const insertIndex = cleanedChunks.findIndex(chunk => chunk.name === 'IDAT')
  if (insertIndex === -1) throw new Error('Invalid PNG: no IDAT chunk found.')
  cleanedChunks.splice(insertIndex, 0, newChunk)

  // 重编码并生成 Blob
  const newBuffer = encodeChunks(cleanedChunks)
  // 确保类型安全,处理 SharedArrayBuffer 兼容性
  const uint8Array = newBuffer instanceof Uint8Array ? newBuffer : new Uint8Array(newBuffer)
  return new Blob([uint8Array as Uint8Array], { type: 'image/png' })
}


//jpg
/**
 * 基于 EXIF UserComment 写入 AIGC 元数据
 * 支持 JPEG/JPG, PNG ≥1.5
 * 依赖:npm install piexifjs
 */

async function setAIGCPropertyForJpg(blob: Blob, aigcData: AIGCData): Promise<Blob> {
  const dataURL = await blobToDataURL(blob)
  const jsonStr = JSON.stringify({ AIGC: aigcData })

  let exifObj: any = {}
  try {
    exifObj = piexif.load(dataURL)
  } catch {
    exifObj = { '0th': {}, Exif: {}, GPS: {}, Interop: {}, '1st': {}, thumbnail: null }
  }

  exifObj['Exif'][piexif.ExifIFD.UserComment] = stringToExifUnicode(jsonStr)
  const exifBytes = piexif.dump(exifObj)
  const newDataURL = piexif.insert(exifBytes, dataURL)
  return dataURLToBlob(newDataURL, blob.type)
}