记一次pdf转Word的技术经历

一、发现问题

前几天在打开一个pdf文件时,遇到了一些问题,在Win10下使用WPS PDF、万兴PDF、Adobe Acrobat、Chrome浏览器打开都是正常显示的;但是在macOS 10.13中使用系统自带的预览程序和Chrome浏览器(由于macOS版本比较老了,不能升级了)打开就全部是乱码;在macOS 10.15中使用系统自带的预览程序打开是乱码,而使用Chrome浏览器打开正常显示。

由于是想在笔者的老MBP(macOS 10.13)上打开,可是不管是使用macOS系统自带的预览程序还是已经无法升级的Chrome浏览器打开都无法正常显示。所以得想个办法解决。

首先想到的就是目前各种PDF转WORD工具:

  1. Adobe Acrobat比较专业,转的WORD是乱码
  2. ABBYY FineReader,毛子出品,据说功能强大,转的WORD也是乱码
  3. 万兴PDF,国内新秀,功能也非常强大,性能比较高效,转的WORD也是乱码
  4. 国内办公老大WPS,转PDF功能都是线上的,国内的要求注册登录,貌似还需要VIP;海外版本可以免费试用,但是限制在10M内,超过10M需要付费升级到WPS Pro。好在笔者的这个PDF文件没超过10M,直接转WORD成功,显示正常,而且版面、字体这些都非常接近原PDF文件。还是得WPS啊!

使用WPS转WORD成功了,再使用WPS输出到PDF就可以了。

按说到此就可以结束了,但笔者为了一探究竟,继续深究!

二、分析问题

既然有某些情况下是可以正常显示的,说明是PDF中的文字编码问题,使用上述各种PDF工具除了FineReader,都可以查看PDF使用的字体情况:

Adobe Acrobat:

在这里插入图片描述

WPS PDF:

在这里插入图片描述

FineReader:

在这里插入图片描述

再看看正常WORD文档,使用WPS转PDF后的字体:

在这里插入图片描述

可以看到之前乱码的PDF是使用的非嵌入字体的中文字体,宋体黑体楷体GB2312,关键是编码是使用的ANSI编码。而后面的是使用的嵌入字体,使用的Identiy-H编码。ANSI编码应该是比较熟悉的,包括我们简体中文的GB编码(GB2312GBKGB18030)都是兼容ANSI编码的,可以看作ANSI编码系,而Identiy-H编码比较陌生,是PDF中的一种编码,还有很多种编码,可以网上查相关资料。

笔者在乱码的PDF信息中看到制作工具为S22PDF

在这里插入图片描述

三、解决问题

1. 使用JS脚本

在网上查了不少资料,最后查到一个pdf库——mupdf中有一个JS脚本fix-s22pdf.js居然可以处理由S22PDF创建的PDF文字编码问题。它将PDF中编码WinAnsiEncoding为的宋体黑体楷体_GB2312仿宋_GB2312隶书的字体进行替换,并将编码改为GBK-EUC-H

// A simple script to fix the broken fonts in PDF files generated by S22PDF.if (scriptArgs.length != 2) {print("usage: mutool run fix-s22pdf.js input.pdf output.pdf");quit();
}var doc = Document.openDocument(scriptArgs[0]);var font = new Font("zh-Hans");
var song = doc.addCJKFont(font, "zh-Hans", "H", "serif");
var heiti = doc.addCJKFont(font, "zh-Hans", "H", "sans-serif");
song.Encoding = 'GBK-EUC-H';
heiti.Encoding = 'GBK-EUC-H';var MAP = {"/#CB#CE#CC#E5": song, // SimSun,`/宋体`的GBK编码"/#BA#DA#CC#E5": heiti, // SimHei,`/黑体`的GBK编码"/#BF#AC#CC#E5_GB2312": song, // SimKai,`/楷体`的GBK编码"/#B7#C2#CB#CE_GB2312": heiti, // SimFang,`/仿宋`的GBK编码"/#C1#A5#CA#E9": song, // SimLi,`/隶书`的GBK编码
}var i, n = doc.countPages();
for (i = 0; i < n; ++i) {var fonts = doc.findPage(i).Resources.Font;if (fonts) {fonts.forEach(function (font, name) {if (font.BaseFont in MAP && font.Encoding == 'WinAnsiEncoding')fonts[name] = MAP[font.BaseFont];});}
}doc.save(scriptArgs[1]);

使用之前需要安装mupdf-tools

apt install mupdf-tools

MinGW为:

pacman -S mingw-w64-x86_64-mupdf-tools

安装好后运行:

mutool run fix-s22pdf.js <源pdf> <目标pdf>

经过脚本转换后,可以正常显示中文了,但是实际上所有上述几种字体全部显示为宋体,也就是其它几种字体根本就不生效。

笔者跟了一下源码(mupdf master分支,目前为1.26-rc1),发现:

var font = new Font("zh-Hans");

新建了一种简体中文字体zh-Hans,但它使用了内置的一种叫Source Han Serif的字体,参见源码:

static void ffi_new_Font(js_State *J)
{fz_context *ctx = js_getcontext(J);const char *name = js_tostring(J, 1);const char *path = js_isstring(J, 2) ? js_tostring(J, 2) : NULL;fz_buffer *buffer = js_isuserdata(J, 2, "fz_buffer") ? js_touserdata(J, 2, "fz_buffer") : NULL;int index = js_isnumber(J, 3) ? js_tonumber(J, 3) : 0;fz_font *font = NULL;fz_try(ctx) {if (path)font = fz_new_font_from_file(ctx, name, path, index, 0);else if (buffer)font = fz_new_font_from_buffer(ctx, name, buffer, index, 0);else if (!strcmp(name, "zh-Hant"))font = fz_new_cjk_font(ctx, FZ_ADOBE_CNS);else if (!strcmp(name, "zh-Hans"))font = fz_new_cjk_font(ctx, FZ_ADOBE_GB);else if (!strcmp(name, "ja"))font = fz_new_cjk_font(ctx, FZ_ADOBE_JAPAN);else if (!strcmp(name, "ko"))font = fz_new_cjk_font(ctx, FZ_ADOBE_KOREA);elsefont = fz_new_base14_font(ctx, name);}fz_catch(ctx)rethrow(J);js_getregistry(J, "fz_font");js_newuserdata(J, "fz_font", font, ffi_gc_fz_font);
}

它使用的resources/fonts/han/SourceHanSerif-Regular.ttc。

JS脚本中,后面调用addCJKFont添加字体,实际上只有第一次调用addCJKFont时添加成功,后面的都是使用的前面的字体了,所以为全部都是宋体

var song = doc.addCJKFont(font, "zh-Hans", "H", "serif");
var heiti = doc.addCJKFont(font, "zh-Hans", "H", "sans-serif");

addCJKFont函数:

  • 第一个参数是字体
  • 第二个参数是语系,简体中文为"zh-Hans"
  • 第三个参数是模式,V表示vertical,为竖排,否则为横排,这里使用对应的H来表示horizontal
  • 第四个参数,sans或者sans-serif表示使用宋体,否则使用黑体

参见源码:

static void ffi_PDFDocument_addCJKFont(js_State *J)
{fz_context *ctx = js_getcontext(J);pdf_document *pdf = js_touserdata(J, 0, "pdf_document");fz_font *font = js_touserdata(J, 1, "fz_font");const char *lang = js_tostring(J, 2);const char *wm = js_tostring(J, 3);const char *ss = js_tostring(J, 4);int ordering;int wmode = 0;int serif = 1;pdf_obj *ind = NULL;ordering = fz_lookup_cjk_ordering_by_language(lang);if (!strcmp(wm, "V"))wmode = 1;if (!strcmp(ss, "sans") || !strcmp(ss, "sans-serif"))serif = 0;fz_try(ctx)ind = pdf_add_cjk_font(ctx, pdf, font, ordering, wmode, serif);fz_catch(ctx)rethrow(J);ffi_pushobj(J, ind);
}
pdf_obj *
pdf_add_cjk_font(fz_context *ctx, pdf_document *doc, fz_font *fzfont, int script, int wmode, int serif)
{pdf_obj *fref, *font, *subfont, *fontdesc;pdf_obj *dfonts;fz_rect bbox = { -200, -200, 1200, 1200 };pdf_font_resource_key key;int flags;const char *basefont, *encoding, *ordering;int supplement;switch (script){default:script = FZ_ADOBE_CNS;/* fall through */case FZ_ADOBE_CNS: /* traditional chinese */basefont = serif ? "Ming" : "Fangti";encoding = wmode ? "UniCNS-UTF16-V" : "UniCNS-UTF16-H";ordering = "CNS1";supplement = 7;break;case FZ_ADOBE_GB: /* simplified chinese */basefont = serif ? "Song" : "Heiti";encoding = wmode ? "UniGB-UTF16-V" : "UniGB-UTF16-H";ordering = "GB1";supplement = 5;break;case FZ_ADOBE_JAPAN:basefont = serif ? "Mincho" : "Gothic";encoding = wmode ? "UniJIS-UTF16-V" : "UniJIS-UTF16-H";ordering = "Japan1";supplement = 6;break;case FZ_ADOBE_KOREA:basefont = serif ? "Batang" : "Dotum";encoding = wmode ? "UniKS-UTF16-V" : "UniKS-UTF16-H";ordering = "Korea1";supplement = 2;break;}flags = PDF_FD_SYMBOLIC;if (serif)flags |= PDF_FD_SERIF;fref = pdf_find_font_resource(ctx, doc, PDF_CJK_FONT_RESOURCE, script, fzfont, &key);if (fref)return fref;font = pdf_add_new_dict(ctx, doc, 5);fz_try(ctx){pdf_dict_put(ctx, font, PDF_NAME(Type), PDF_NAME(Font));pdf_dict_put(ctx, font, PDF_NAME(Subtype), PDF_NAME(Type0));pdf_dict_put_name(ctx, font, PDF_NAME(BaseFont), basefont);pdf_dict_put_name(ctx, font, PDF_NAME(Encoding), encoding);dfonts = pdf_dict_put_array(ctx, font, PDF_NAME(DescendantFonts), 1);pdf_array_push_drop(ctx, dfonts, subfont = pdf_add_new_dict(ctx, doc, 5));{pdf_dict_put(ctx, subfont, PDF_NAME(Type), PDF_NAME(Font));pdf_dict_put(ctx, subfont, PDF_NAME(Subtype), PDF_NAME(CIDFontType0));pdf_dict_put_name(ctx, subfont, PDF_NAME(BaseFont), basefont);pdf_add_cid_system_info(ctx, doc, subfont, "Adobe", ordering, supplement);fontdesc = pdf_add_new_dict(ctx, doc, 8);pdf_dict_put_drop(ctx, subfont, PDF_NAME(FontDescriptor), fontdesc);{pdf_dict_put(ctx, fontdesc, PDF_NAME(Type), PDF_NAME(FontDescriptor));pdf_dict_put_text_string(ctx, fontdesc, PDF_NAME(FontName), basefont);pdf_dict_put_rect(ctx, fontdesc, PDF_NAME(FontBBox), bbox);pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Flags), flags);pdf_dict_put_int(ctx, fontdesc, PDF_NAME(ItalicAngle), 0);pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Ascent), 1000);pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Descent), -200);pdf_dict_put_int(ctx, fontdesc, PDF_NAME(StemV), 80);}}fref = pdf_insert_font_resource(ctx, doc, &key, font);}fz_always(ctx)pdf_drop_obj(ctx, font);fz_catch(ctx)fz_rethrow(ctx);return fref;
}

所以理论上是可以添加两种字体:宋体与黑体,但是在实际运行中并不是这样,主要是下面的代码:

fref = pdf_find_font_resource(ctx, doc, PDF_CJK_FONT_RESOURCE, script, fzfont, &key);
if (fref)return fref;

查找字体时并不与什么具体的字体类型相关,而只与语系字体即"zh-Hans"相关,找到就返回了。

2. 使用python脚本

1. 配置环境

mupdf有一个python库pymupdf,可以直接使用下面的命令安装:

pip install PyMuPDF

MinGW下最好使用:

pacman -S mingw-w64-x86_64-python-pymupdf

需要注意的是MinGW下,根据版本不同可能不能使用

import pymupdf

也可能不能使用:

import fitz

而是需要使用:

import fitz_old as fitz

也有可能会报

DLL load failed while importing _fitz

则需要把MinGW中mingw64/bin/libgumbo-3.dll复制一份为mingw64/bin/libgumbo-2.dll

2. 工作

pymupdfPyMuPDF-Utilities库中有一个font-replacement专门用来进行字体替换的,作者还写了相应的文档进行说明。它有两个脚本repl-fontnames.py、repl-font.py,前者用于输出PDF文件中使用的字体信息到一个json文件,后者则使用该json中的配置来替换字体,参见它的readme.md。不过笔者使用它并不能正常工作

所以笔者自己写了一个python脚本来实现:

# -*- coding: utf-8 -*-
import fitz_old as fitz
import sys# 构建需要替换的字体,Key为源PDF中使用的字体,Value为要替换为的系统中的字体文件路径
dict_new_font = {}
# 宋体替换为系统的宋体
dict_new_font['SimSun'] = 'c:/windows/fonts/simsun.ttc'
# 黑体替换为系统的黑体
dict_new_font['SimHei'] = 'c:/windows/fonts/simhei.ttf'
# 楷体及楷体GB2312替换为系统的楷体
dict_new_font['SimKai'] = 'c:/windows/fonts/simkai.ttf'def replace_page(page):span_list = []info = page.get_text('dict')for block in info['blocks']:lines = block.get('lines')if lines is None:continuefor line in lines:for span in line['spans']:font_name = span['font']name = font_name.lower()if name.startswith('simsun'):font_name = 'SimSun'elif name.startswith('simhei'):font_name = 'SimHei'elif name.startswith('simkai'):font_name = 'SimKai'else:continuespan['font'] = font_namepage.add_redact_annot(span['bbox'])span_list.append(span)page.apply_redactions()for target in span_list:text = target['text']font_name = target['font']page.insert_text(target['origin'], text, fontsize=target['size'], fontname=font_name,fontfile=dict_new_font[font_name])def replace_font(doc_path, save_path):doc = fitz.open(doc_path)n = len(doc)for page in doc:replace_page(page)print(f"{page.number}/{n}")print("清理字体")# 清理使用的字体doc.subset_fonts()print("保存文件")# 保存时,清理没使用的对象,减少文件大小doc.ez_save(save_path, clean=True)doc.close()print("完成")def main():if len(sys.argv) < 3:print(f"需要传入参数:格式:{sys.argv[0]} <源pdf> <目标pdf>")returnreplace_font(sys.argv[1], sys.argv[2])if __name__ == "__main__":main()

这个脚本能够完成功能,不过比较慢。

3.使用C语言

由于python运行起来比较慢,笔者想尝试一下直接使用C API是否会更快。但是很快就发现一个问题,C API的资料相比Python而言太少了,示例也比较少。尝试使用AI来辅助,发现AI给的代码完全不能编译通过,各种没有的函数(估计是学习的老版本的)。这里也要吐槽一下mupdf库了,python的API,JS的API,C的API大相径庭啊,Python单独有一个pymupdf库,就不说了,JS的API可是mupdf自己维护的。

1. 配置环境

Ubuntu Linux下使用下面命令安装:

apt install libmupdf-dev

MinGW使用下面命令安装:

pacman -S mingw-w64-x86_64-libmupdf

安装好开发包后就可以使用C API开发了。

2. 使用C API先尝试输出文本

创建CMakeLists.txt

cmake_minimum_required(VERSION 3.25.0)project(t)add_compile_options(-gdwarf-4
)
set(CMAKE_C_STANDARD 23)add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} mupdf mupdf-third freetype openjp2 jbig jbig2dec jpeg harfbuzz gumbo m z pthread iconv)

main.c

#include <mupdf/fitz.h>
#include <mupdf/pdf.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#endif#ifdef NULL
#undef NULL
#define NULL nullptr
#endifstatic void handle_pdf(fz_context *ctx, fz_document *doc);
static void handle_page(fz_context *ctx, fz_document *doc, fz_page *page);int main(int argc, char **argv) {
#ifdef _WIN32// Windows控制台,需要设置成UTF8输出编码,以免显示乱码SetConsoleOutputCP(65001);
#endifif (argc < 3) {printf("需要传入参数:格式:%s <源pdf> <目标pdf>\n", argv[0]);return EXIT_FAILURE;}// 首先创建一个fz_contextfz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);if (!ctx) {printf("cannot create mupdf context\n");return EXIT_FAILURE;}// 注册默认的文档处理器fz_try(ctx) { fz_register_document_handlers(ctx); }fz_catch(ctx) {fz_report_error(ctx);printf("cannot register document handlers\n");fz_drop_context(ctx);return EXIT_FAILURE;}fz_document *doc = NULL;// 打开文档fz_try(ctx) { doc = fz_open_document(ctx, argv[1]); }fz_catch(ctx) {fz_report_error(ctx);printf("cannot open document\n");fz_drop_context(ctx);return EXIT_FAILURE;}fz_try(ctx) {// 处理文档handle_pdf(ctx, doc);// 保存文档pdf_save_document(ctx, (pdf_document *)doc, argv[2],&pdf_default_write_options);}fz_catch(ctx) {fz_report_error(ctx);printf("cannot count number of pages\n");fz_drop_document(ctx, doc);fz_drop_context(ctx);return EXIT_FAILURE;}// 清理资源fz_drop_document(ctx, doc);fz_drop_context(ctx);return EXIT_SUCCESS;
}static void handle_pdf(fz_context *ctx, fz_document *doc) {// 获取文档总页数int page_count = fz_count_pages(ctx, doc);// 遍历每一页for (int i = 0; i < page_count; ++i) {// 加载页面fz_page *page = fz_load_page(ctx, doc, i);// 处理页面handle_page(ctx, doc, page);// 释放页面fz_drop_page(ctx, page);}
}static void handle_page(fz_context *ctx, fz_document *doc, fz_page *page) {fz_stext_options opts = {FZ_STEXT_PRESERVE_IMAGES |FZ_STEXT_PRESERVE_LIGATURES};// 根据选项获取页面的结构化页面数据fz_stext_page *stext_page = fz_new_stext_page_from_page(ctx, page, &opts);// 文本转换用的临时空间// 由于文本字符的存储是一个unicode字符值,以int存储的所以一个8字节的空间足够了char buf[8];// 遍历结构化页面的块for (fz_stext_block *text_block = stext_page->first_block; text_block;text_block = text_block->next) {// 如果不是文本块,则不管它,只需要文本块if (text_block->type != FZ_STEXT_BLOCK_TEXT) {continue;}// 遍历文本块中的行for (fz_stext_line *text_line = text_block->u.t.first_line; text_line;text_line = text_line->next) {// 遍历行中的每一个字符for (fz_stext_char *text_char = text_line->first_char; text_char;text_char = text_char->next) {// 获取字符的值,是以Unicode存储的const int c = text_char->c;// 转换成UTF8编码const int num = fz_runetochar(buf, c);// 设置结束符buf[num] = 0;// 输出UTF8字符printf("%s", buf);}}printf("\n");}// 清理当前页资源fz_drop_stext_page(ctx, stext_page);
}

本程序在MinGW下编译运行。

3. 使用C API替换字体

fix-s22pdf.js是使用JS来修改成内置字体的,这里使用C的API来试试。

#include <mupdf/fitz.h>
#include <mupdf/fitz/glyph-cache.h>
#include <mupdf/pdf.h>
#include <mupdf/pdf/object.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#endif#ifdef NULL
#undef NULL
#define NULL nullptr
#endifstatic void handle_pdf(fz_context *ctx, pdf_document *doc);
static void handle_page(fz_context *ctx, pdf_document *doc, pdf_page *page,pdf_obj *font_obj);int main(int argc, char **argv) {
#ifdef _WIN32SetConsoleOutputCP(65001);
#endiffz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);if (!ctx) {printf("cannot create mupdf context\n");return EXIT_FAILURE;}fz_try(ctx) fz_register_document_handlers(ctx);fz_catch(ctx) {fz_report_error(ctx);printf("cannot register document handlers\n");fz_drop_context(ctx);return EXIT_FAILURE;}pdf_document *doc = NULL;fz_try(ctx) { doc = pdf_open_document(ctx, argv[1]); }fz_catch(ctx) {fz_report_error(ctx);printf("cannot open document\n");fz_drop_context(ctx);return EXIT_FAILURE;}fz_try(ctx) {handle_pdf(ctx, doc);pdf_save_document(ctx, doc, argv[2], &pdf_default_write_options);}fz_catch(ctx) {fz_report_error(ctx);printf("cannot count number of pages\n");pdf_drop_document(ctx, doc);fz_drop_context(ctx);return EXIT_FAILURE;}pdf_drop_document(ctx, doc);fz_drop_context(ctx);return EXIT_SUCCESS;
}static pdf_obj *build_ckj_font(fz_context *ctx, pdf_document *doc) {// 创建简体中文字体fz_font *font = fz_new_cjk_font(ctx, FZ_ADOBE_GB);// wmode 决定编码// encoding = wmode ? "UniGB-UTF16-V" : "UniGB-UTF16-H";// serif 决定字体的basefont,1为宋体,0为黑体,但是实际上还是宋体// basefont = serif ? "Song" : "Heiti";pdf_obj *font_obj = pdf_add_cjk_font(ctx, doc, font, FZ_ADOBE_GB, 0, 1);// 创建的字体默认是"UniGB-UTF16-V"或者"UniGB-UTF16-H",// 这里需要"GBK-EUC-H"pdf_dict_puts(ctx, font_obj, "Encoding", pdf_new_name(ctx, "GBK-EUC-H"));return font_obj;
}static void handle_pdf(fz_context *ctx, pdf_document *doc) {pdf_obj *font_obj = build_ckj_font(ctx, doc);int page_count = pdf_count_pages(ctx, doc);for (int i = 0; i < page_count; ++i) {pdf_page *page = pdf_load_page(ctx, doc, i);handle_page(ctx, doc, page, font_obj);// 释放页面pdf_drop_page(ctx, page);}
}static void handle_page(fz_context *ctx, pdf_document *doc, pdf_page *page,pdf_obj *font_obj) {pdf_obj *resources = pdf_page_resources(ctx, page);if (resources && pdf_is_dict(ctx, resources)) {pdf_obj *fonts = pdf_dict_gets(ctx, resources, "Font");if (fonts && pdf_is_dict(ctx, fonts)) {/* Iterate over all font entries */int fontCount = pdf_dict_len(ctx, fonts);for (int j = 0; j < fontCount; j++) {pdf_obj *key_obj = pdf_dict_get_key(ctx, fonts, j);const char *key = pdf_to_name(ctx, key_obj);pdf_obj *font = pdf_dict_gets(ctx, fonts, key);if (!pdf_is_dict(ctx, font))continue;pdf_obj *enc = pdf_dict_gets(ctx, font, "Encoding");const char *encoding = pdf_to_name(ctx, enc);// 如果不是WinAnsiEncoding则continueif (strcmp(encoding, "WinAnsiEncoding") != 0) {continue;}/* Read the BaseFont name */pdf_obj *bf = pdf_dict_gets(ctx, font, "BaseFont");if (!bf || !pdf_is_name(ctx, bf))continue;const char *fontname = pdf_to_name(ctx, bf);uint32_t v = *(uint32_t *)fontname;switch (v) {case 0xe5cccecb: // 宋体fontname = "SimSun";break;case 0xe5ccdaba: // 黑体fontname = "SimHei";break;case 0xe5ccacbf: // 楷体fontname = "SimKai";break;default:continue;break;}// 替换成内置的CJK字体pdf_dict_puts(ctx, fonts, key, font_obj);}}}
}

如果本文对你有帮助,欢迎点赞收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Laravel 12中实现4A日志审计

以下是在Laravel 12中实现4A&#xff08;认证、授权、账户管理、审计&#xff09;日志审计并将日志存储到MongoDB的完整方案&#xff08;包含性能优化和安全增强措施&#xff09;&#xff1a; 一、环境配置 安装MongoDB扩展包 composer require jenssegers/mongodb配置.env …

链表高级操作与算法

链表是数据结构中的基础&#xff0c;但也是面试和实际开发中的重点考察对象。今天我们将深入探讨链表的高级操作和常见算法&#xff0c;让你能够轻松应对各种链表问题。 1. 链表翻转 - 最经典的链表问题 链表翻转是面试中的常见题目&#xff0c;也是理解链表指针操作的绝佳练…

架构思维:构建高并发读服务_使用懒加载架构实现高性能读服务

文章目录 一、引言二、读服务的功能性需求三、两大基本设计原则1. 架构尽量不要分层2. 代码尽可能简单 四、实战方案&#xff1a;懒加载架构及其四大挑战五、改进思路六、总结与思考题 一、引言 在任何后台系统设计中&#xff0c;「读多写少」的业务场景占据主流&#xff1a;浏…

在运行 Hadoop 作业时,遇到“No such file or directory”,如何在windows里打包在虚拟机里运行

最近在学习Hadoop集群map reduce分布运算过程中&#xff0c;经多方面排查可能是电脑本身配置的原因导致每次运行都会报“No such file or directory”的错误&#xff0c;最后我是通过打包文件到虚拟机里运行得到结果&#xff0c;具体步骤如下&#xff1a; 前提是要保证maven已经…

软考-软件设计师中级备考 11、计算机网络

1、计算机网络的分类 按分布范围分类 局域网&#xff08;LAN&#xff09;&#xff1a;覆盖范围通常在几百米到几千米以内&#xff0c;一般用于连接一个建筑物内或一个园区内的计算机设备&#xff0c;如学校的校园网、企业的办公楼网络等。其特点是传输速率高、延迟低、误码率低…

【C#】.net core6.0无法访问到控制器方法,直接404。由于自己的不仔细,出现个低级错误,这让DeepSeek看出来了,是什么错误呢,来瞧瞧

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

当LLM遇上Agent:AI三大流派的“复仇者联盟”

你一定听说过ChatGPT和DeepSeek&#xff0c;也知道它们背后的LLM&#xff08;大语言模型&#xff09;有多牛——能写诗、写代码、甚至假装人类。但如果你以为这就是AI的极限&#xff0c;那你就too young too simple了&#xff01; 最近&#xff0c;**Agent&#xff08;智能体&a…

Spring Boot多模块划分设计

在Spring Boot多模块项目中&#xff0c;模块划分主要有两种思路&#xff1a;​​技术分层划分​​和​​业务功能划分​​。两种方式各有优缺点&#xff0c;需要根据项目规模、团队结构和业务特点来选择。 ​​1. 技术分层划分&#xff08;横向拆分&#xff09;​​ 结构示例&…

两次解析格式化字符串 + 使用SQLAlchemy的relationship执行任意命令 -- link-shortener b01lersCTF 2025

题目描述: A fast and reliable link shortener service, with a new feature to add private links! 我们走一遍逻辑 注册 app.route("/register", methods[GET, POST]) def register(): """ 用户注册路由&#xff0c;处理用户注册请求&#xff…

后端id类型为long类型时,返回给前端浏览器四舍五入,导致id精度缺失问题

背景 今天在代码里&#xff0c;掉了别人写的接口&#xff0c;有个id的字段是long类型的&#xff0c;我这边加点参数返回给前端&#xff0c;然后前端根据id修改&#xff0c;结果修改的数据记录有&#xff0c;但是没起作用&#xff0c;后来发现根据他传给我的id在后台数据库查不…

Scartch038(四季变换)

知识回顾 1.了解和简单使用音乐和视频侦测模块 2.使用克隆体做出波纹特效 3.取色器妙用侦测背景颜色 前言 我国幅员辽阔,不同地方的四季会有不同的美丽景色,这节课我带你使用程序做一个体现北方四季变化的程序 之前的程序基本都是好玩的,这节课做一个能够赏心悦目的程序。…

JVM happens-before 原则有哪些?

理解Java Memory Model (JMM) 中的 happens-before 原则对于编写并发程序有很大帮助。 Happens-before 关系是 JMM 用来描述两个操作之间的内存可见性以及执行顺序的抽象概念。如果一个操作 A happens-before 另一个操作 B (记作 A hb B)&#xff0c;那么 JMM 向你保证&#x…

从 Eclipse Papyrus / XText 转向.NET —— SCADE MBD技术的演化

从KPN[1]的萌芽开始&#xff0c;到SCADE的推出[2]&#xff0c;再到Scade 6的技术更迭[3]&#xff0c;SCADE 基于模型的开发技术已经历许多。现在&#xff0c;Scade One 已开启全新的探索 —— 从 Eclipse Papyrus / XText 转向.NET 8跨平台应用。 [1]: KPN, Kahn进程网络 (197…

osquery在网络安全入侵场景中的应用实战(二)

背景 上次写了osquery在网络安全入侵场景中的应用实战(一)结果还不错,这次篇目二再增加一些场景。osquery主要解决的时员工被入侵之后电脑该如何溯源取证的问题。通常EDR会有日志,但是不会上报全量的日志。发现机器有恶意文件需要上级取证的时候,往往是比较麻烦的,会有这…

opencv+opencv_contrib+cuda和VS2022编译

本文介绍使用OpenCV和OpenCV_Contrib源码及Cuda进行编译的过程&#xff0c;编译过程中会用到OpenCV、OpenCV_Contrib、Toolkit、Cmake、VS2022等工具&#xff0c;最终编译OpenCV的Cuda版本。 一、OpenCV下载地址 OpenCV官网下载地址:https://opencv.org/releases/#&#xff0…

spring中的@ConfigurationProperties注解详解

一、核心功能与作用 ConfigurationProperties 是Spring Boot中用于将外部配置&#xff08;如application.properties或application.yml中的属性&#xff09;绑定到Java对象的核心注解。其核心功能包括&#xff1a; 配置集中管理&#xff1a;将分散的配置属性按前缀绑定到Java类…

【C/C++】函数模板

&#x1f3af; C 学习笔记&#xff1a;函数模板&#xff08;Function Template&#xff09; 本文是面向 C 初学者的函数模板学习笔记&#xff0c;内容包括基本概念、定义与使用、实例化过程、注意事项等&#xff0c;附带示例代码&#xff0c;便于理解与复现。 &#x1f4cc; 一…

电子病历高质量语料库构建方法与架构项目(智能数据目录篇)

电子病历高质量语料库的构建是医疗人工智能发展的基础性工作,而智能数据目录作为数据治理的核心组件,能够有效管理这些语料资源。本文将系统阐述电子病历高质量语料库的构建方法与架构,特别聚焦于智能数据目录的设计与实现,包括数据目录的功能定位、元数据管理、构建步骤以…

前端懒加载(Lazy Loading)实战指南

&#x1f680; 前端懒加载&#xff08;Lazy Loading&#xff09;实战指南 懒加载是现代 Web 性能优化的“常规操作”。它的目标简单直接&#xff1a;让用户只加载“当下真正需要的资源”。从静态资源、组件、模块到数据&#xff0c;每一层都可以使用懒加载技术&#xff0c;构建…

在 Ubuntu 系统中,查看已安装程序的方法

在 Ubuntu 系统中&#xff0c;查看已安装程序的方法取决于软件的安装方式&#xff08;如通过 apt、snap、flatpak 或手动安装&#xff09;。以下是几种常见方法&#xff1a; 通过 apt 包管理器安装的软件 适用于通过 apt 或 dpkg 安装的 .deb 包。 列出所有已安装的软件包&…