当前位置: 首页 > 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…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...