企业站群cms,专业门户网站建设,seo关键词优化推广哪家好,南昌企业网站开发#1 写在前面
开始写这篇文章时#xff0c;标题怎么定困扰我良久#xff0c;缘于不晓得如何给接下来要做的事定个简单明了的标题#xff1a;在#x1f4f1;终端只能纯文本交互的前提下#xff0c;优雅展示 markdown 文档中的图片。这也许比问题本身还要棘手#x1f604;。…#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()) returnlet 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)) returnreplace 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)}}AsyncEventListener(PageContentUpdateEvent::class)fun onPageUpdate(event: PageContentUpdateEvent) {event.page.also {if(it.template Page.MARKDOWN){logger.info(检测到 #${it.id} 的内容变更即将删除其缓存文件若存在...)txtFile(it.id).deleteIfExists()}}}
}