当前位置: 首页 > news >正文

MARKDOWN 文档图片编码嵌入方案

#1 写在前面

  • 开始写这篇文章时,标题怎么定困扰我良久,缘于不晓得如何给接下来要做的事定个简单明了的标题:在📱终端只能纯文本交互的前提下,优雅展示 markdown 文档中的图片。
  • 这也许比问题本身还要棘手😄。

#2 背景说明

公司内网有一套基于 markdown 的文档系统,方便同事查阅资料,现希望能够在移动端进行浏览。

目前我们已在集团移动办公 APP 发布有 H5 小程序,实现了互联网与内网的数据通信,但存在以下限制:

  • 请求方式为 POST
  • 后端返回内容限定为纯文本
  • 每次发起请求终端都有 loading 弹窗
  • 无法加载互联网资源

#3 思路阐述

**方案一:将图片编码进 markdown 文本 **

识别出 markdown 内的图片,转换为 BASE64 编码并替换原文本,终端解析后渲染。本文采用此方案✅。

方案二:延迟加载图片

终端渲染后,监听页面滚动,按需加载图片(传递 url 或图片编号,后端返回 BASE64 编码)。此方案可通过自定义指令实现,前后端均需要代码改造。

#3.1 处理流程

  1. 用户请求指定 ID 的 MARKDOWN 资源
  2. 从数据库读取原始文本,调用 MarkdownFunc.embedImages 方法
  3. 若该 ID 的缓存文件存在,则直接使用,跳转到⑥
  4. 用正则表达式匹配全部图片标签,对符合后缀规范的本地文件,进行以下操作
    a. 原始图片宽度超出阈值,则先缩放
    b. 转换为 WEBP 格式(节流😄)
    c. 进一步转换为 BASE64 编码
    d. 替换到原标签文本
  5. 将处理完成的文本写入缓存文件
  6. 返回内容到客户端

同时,当文档被修改后,监听事件,删除对应的缓存文件。

#3.2 代码实现

@Configuration
@ConfigurationProperties(prefix = "page.markdown")
class MarkdownConfig {var maxWidth        = 900       //图片宽度超出此值则进行压缩var quality         = 0.1F      //转换为 webp 时质量阈值var resizeQuality   = 0.8f      //裁剪图片的质量阈值var exts            = listOf("jpg","jpeg","bmp","png")var dir             = "markdown"
}@Component
class MarkdownFunc(private val fileStore: FileStore,private val config: MarkdownConfig) {@Value("\${server.servlet.context-path}")private val contextPath = ""private val logger = LoggerFactory.getLogger(javaClass)/*** 转换为 Base64 编码*/private fun base64(bytes:ByteArray) = "![](data:image/webp;base64,${Base64.getEncoder().encodeToString(bytes)})"private fun txtFile(id: Long) = fileStore.buildPathWithoutDate("${id}.txt", config.dir)/**** @param id    文档唯一编号* @param text  markdown 源文本*/fun embedImages(id:Long, text:String):String = txtFile(id).let { file->if(file.exists()) return@let Files.readString(file)Regex("!\\[.*?\\]\\((.*?)\\)").replace(text) { match->val fileUrl = match.groupValues.last().let {if(it.startsWith(contextPath))it.replaceFirst(contextPath, "")elseit}//暂不支持互联网资源if(fileUrl.startsWith("http"))  return@replace match.valueval imgPath = Paths.get(".", fileUrl)val ext = imgPath.extension.lowercase()logger.info("${imgPath.toAbsolutePath() }  ${imgPath.isRegularFile()}")if(imgPath.exists() && imgPath.isRegularFile()){if(config.exts.contains(ext)){var img = ImageIO.read(imgPath.toFile()).let {if(it.width > config.maxWidth){if(logger.isDebugEnabled)   logger.debug("图片 $imgPath 宽度超出阈值 ${config.maxWidth} 即将裁剪...")//对图片进行缩放,如需水印可以调用 watermark 方法Thumbnails.of(it).width(config.maxWidth).outputQuality(config.resizeQuality).asBufferedImage()}elseit}val out = ByteArrayOutputStream()val mout = MemoryCacheImageOutputStream(out)ImageIO.getImageWritersByMIMEType("image/webp").next().let { writer->writer.output = moutwriter.write(null,IIOImage(img, null, null),WebPWriteParam(writer.locale).also {it.compressionMode = ImageWriteParam.MODE_EXPLICITit.compressionType = it.compressionTypes[WebPWriteParam.LOSSY_COMPRESSION]it.compressionQuality = config.quality})if(logger.isDebugEnabled)   logger.debug("图片 $imgPath 转 webp 完成...")}mout.flush()base64(out.toByteArray())}//对于 webp 格式不作缩放处理直接编码else if(ext == "webp"){base64(Files.readAllBytes(imgPath))}else{if(logger.isDebugEnabled)   logger.debug("图片 $imgPath 不是支持的格式...")match.value}}else {logger.error("图片 $imgPath 不存在或不是一个有效文件...")match.value}}.also {file.parent.also { p->if(!p.exists())Files.createDirectories(p)}Files.writeString(file, it, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)logger.info("缓存 $file 写入成功(SIZE = ${file.fileSize()} B)")}}@Async@EventListener(PageContentUpdateEvent::class)fun onPageUpdate(event: PageContentUpdateEvent) {event.page.also {if(it.template == Page.MARKDOWN){logger.info("检测到 #${it.id} 的内容变更,即将删除其缓存文件(若存在)...")txtFile(it.id).deleteIfExists()}}}
}

相关文章:

MARKDOWN 文档图片编码嵌入方案

#1 写在前面 开始写这篇文章时,标题怎么定困扰我良久,缘于不晓得如何给接下来要做的事定个简单明了的标题:在📱终端只能纯文本交互的前提下,优雅展示 markdown 文档中的图片。这也许比问题本身还要棘手😄。…...

KubeVela可持续测试应用部署之Mock基础设施

Mock接口是我们常用的功能测试方案,有时候依赖的接口未开发完成或者依赖的第三方接口不提供测试环境等,只有Mock才能跑通流程。 我们基于KubeVela开发的云原生应用交付平台,提供如初始化基础设施导入、中间件部署共用基础设施等相关能力的测试,需要依赖基础设施。虽然terr…...

代理IP、Socks5代理与网络工程:解析技术世界的无限可能

在当今数字化的世界中,网络工程师不仅需要保证网络的稳定性,还要应对多样的技术挑战。代理IP和Socks5代理技术已经成为网络工程师工具箱中不可或缺的利器,在跨界电商、爬虫、出海、网络安全、游戏等领域发挥关键作用。本文将深入探讨这两项技…...

OpenCV级联分类器识别车辆实践笔记

1. OpenCV 级联分类器的基本原理 基于Haar特征的级联分类器的目标检测是Paul Viola和Michael Jones在2001年的论文中提出的一种有效的目标检测方法。这是一种基于机器学习的方法,从大量的正面和负面图像中训练级联函数。然后用它来检测其他图像中的物体。 Haar特征…...

VS编译的时候不生成Release文件夹

方法描述: Build>Configuration Manager>Release 编译》配置管理》选择发布版本 再编译就有了 具体操作过程 第一步: 第二步: 第三步: 特此记录 anlog 2023年10月12日...

14.2 Socket 反向远程命令行

在本节,我们将继续深入探讨套接字通信技术,并介绍一种常见的用法,实现反向远程命令执行功能。对于安全从业者而言,经常需要在远程主机上执行命令并获取执行结果。本节将介绍如何利用 _popen() 函数来启动命令行进程,并…...

PCL点云处理之点云重建为Mesh模型并保存到PLY文件 ---方法二 (二百一十一)

PCL点云处理之点云重建为Mesh模型并保存到PLY文件 ---方法二 (二百一十一) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 离散点云重建为mesh网格模型,并保存到PLY文件中,用于其他软件打开查看,代码非常简短,复制粘贴即可迅速上手使用,具体参数根据自己的点云数据…...

CSS 中::after的妙用(实现在margin中显示内容)

效果图如下: 背景: 如上图,之前只是当纯的写一个参考货架平面图,用作物料系统的在库状态可视化,当完成页面body分成10等份时,货架之间需要有通道,为了实现实际的样式,我给每个等份都…...

SentenceTransformer使用多GPU加速向量化

文章目录 前言代码 前言 当我们需要对大规模的数据向量化以存到向量数据库中时,且服务器上有多个GPU可以支配,我们希望同时利用所有的GPU来并行这一过程,加速向量化。 代码 就几行代码,不废话了 from sentence_transformers i…...

架构师-软件工程习题选择题

架构师-软件工程习题选择题 真题案例题 真题 c 瀑布模型:针对软件需求明确的情况,将前一个阶段做完,才能开始下一个阶段 原型模型:针对需求不明确的情况,快速搭建出系统原型,然后根据系统原型和客户确认需求…...

springboot单独在指定地方输出sql

一般线上项目都是将日志进行关闭,因为mybatis日志打印,时间长了,会占用大量的内存,如果我想在我指定的地方进行打印sql情况,怎么玩呢! 下面这个场景: 某天线上的项目出bug了,日志打印…...

gpio内部结构(一)

一,GPIO内部结构 1,保护二极管 * 引脚内部加上这两个保护二级管可以防止引脚外部过高或过低的电压输入。 * 当引脚电压高于 VDD_FT 或 VDD 时,上方的二极管导通吸收这个高电压。 * 当引脚电压低于 VSS 时,下方的二极管导通&…...

【C++14保姆级教程】变量模板,Labmda泛型

文章目录 前言一、变量模板(Variable Templates)1.1 变量模板是什么1.2 泛型大概使用1.3 示例代码11.4 示例代码21.5 示例代码3 二、Lambda泛型(Lambda Generics)2.1 Lambda表达式泛型是什么?2.2 函数原型怎么写&#…...

LLM - 旋转位置编码 RoPE 代码详解

目录 一.引言 二.RoPE 理论 1.RoPE 矩阵形式 2.RoPE 图例形式 3.RoPE 实践分析 三.RoPE 代码分析 1.源码获取 2.源码分析 3.rotary_emb 3.1 __init__ 3.2 forward 4.apply_rotary_pos_emb 4.1 rotate_half 4.2 apply_rotary_pos_emb 四.RoPE 代码实现 1.Q/K/V …...

Vue之VueX知识探索(一起了解关于VueX的新世界)

目录 前言 一、VueX简介 1. 什么是VueX 2. VueX的作用及重要性 3. VueX的应用场景 二、VueX的使用准备工作 1. 下载安装VueX 2. vuex获取值以及改变值 2.1 创建所需示例 2.2 将创建好的.vue文件页面显示 2.3 创建VueX的相关文件 2.4 配置VueX四个js文件 2.5 加载到vue示…...

提升吃鸡战斗力,分享顶级作战干货!

大家好!作为一名吃鸡玩家,你是否也希望提高自己的游戏战斗力?在这里,我将为大家分享一些顶级游戏作战干货,以及方便吃鸡作图和查询装备皮肤库存的实用工具。 首先,让我们提到绝地求生作图工具推荐。通过使用…...

【rust】cargo的概念和使用方法

啥是cargo 包管理器 cargo 提供了一系列的工具,从项目的建立、构建到测试、运行直至部署,为 Rust 项目的管理提供尽可能完整的手段,与 Rust 语言及其编译器 rustc 紧密结合。 创建项目 使用cargo创建一个项目: $ cargo new wo…...

MySQL数据库——SQL优化(2)-order by 优化、group by 优化

目录 order by 优化 概述 测试 优化原则 group by 优化 测试 优化原则 order by 优化 概述 MySQL的排序,有两种方式: Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sortbuffer中完成排…...

C++DAY43

#include <iostream>using namespace std;//封装 沙发 类 class Sofa { private:string living; public:Sofa(){cout << "沙发的无参构造函数" << endl;}Sofa(string l):living(l){cout << "沙发的有参构造函数" << endl;}v…...

大模型的超级“外脑”——向量数据库解决大模型的三大挑战

随着AI大模型产品及应用呈现爆发式增长,新的AI时代已经到来。向量数据库可与大语言模型配合使用,解决大模型落地过程中的痛点,已成为企业数据处理和应用大模型的必选项。在近日举行的华为全联接大会2023期间,华为云正式发布GaussDB向量数据库。GaussDB向量数据库基于GaussD…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...