MARKDOWN 文档图片编码嵌入方案
#1 写在前面
- 开始写这篇文章时,标题怎么定困扰我良久,缘于不晓得如何给接下来要做的事定个简单明了的标题:在📱终端只能
纯文本交互的前提下,优雅展示 markdown 文档中的图片。 - 这也许比问题本身还要棘手😄。
#2 背景说明
公司内网有一套基于 markdown 的文档系统,方便同事查阅资料,现希望能够在移动端进行浏览。
目前我们已在集团移动办公 APP 发布有 H5 小程序,实现了互联网与内网的数据通信,但存在以下限制:
- 请求方式为
POST - 后端返回内容限定为纯文本
- 每次发起请求终端都有
loading弹窗 - 无法加载互联网资源
#3 思路阐述
**方案一:将图片编码进 markdown 文本 **
识别出 markdown 内的图片,转换为 BASE64 编码并替换原文本,终端解析后渲染。本文采用此方案✅。
方案二:延迟加载图片
终端渲染后,监听页面滚动,按需加载图片(传递 url 或图片编号,后端返回 BASE64 编码)。此方案可通过自定义指令实现,前后端均需要代码改造。
#3.1 处理流程
- 用户请求指定 ID 的 MARKDOWN 资源
- 从数据库读取原始文本,调用 MarkdownFunc.embedImages 方法
- 若该 ID 的缓存文件存在,则直接使用,跳转到⑥
- 用正则表达式匹配全部图片标签,对符合后缀规范的本地文件,进行以下操作
a. 原始图片宽度超出阈值,则先缩放
b. 转换为 WEBP 格式(节流😄)
c. 进一步转换为 BASE64 编码
d. 替换到原标签文本 - 将处理完成的文本写入缓存文件
- 返回内容到客户端
同时,当文档被修改后,监听事件,删除对应的缓存文件。
#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) = ".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…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表
设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Linux实现线程同步的方式有哪些?
什么是线程同步? 想象一下超市收银台:如果所有顾客(线程)同时挤向同一个收银台(共享资源),场面会一片混乱。线程同步就是给顾客们发"排队号码牌",确保: 有序访…...
timestamp时间戳转换工具
作为一名程序员,一款高效的 在线转换工具 (在线时间戳转换 计算器 字节单位转换 json格式化)必不可少!https://jsons.top 排查问题时非常痛的点: 经常在秒级、毫秒级、字符串格式的时间单位来回转换,于是决定手撸一个…...
