从EasyExcel迁移到Apache Fesod:Java复杂表格处理降本增效实战

发布时间:2026/11/6 1:59:17

从EasyExcel迁移到Apache Fesod:Java复杂表格处理降本增效实战 如果你和我一样在 Java 项目里用 EasyExcel 用了两三年大概率也遇到过这些事好好的一个导入功能遇到复杂表头就各种错列模板填充时合并单元格总是对不齐部署到精简 Docker 镜像上突然报libfreetype6缺失更离谱的是一次常规升级直接抛NoSuchFieldError: factory。这些坑单独看都能绕过去但架不住它们轮着来。最近我把核心报表模块整体从 EasyExcel 迁移到了 Apache Fesod踩完坑回头一看这个决定做得值。这篇文章就把我的迁移过程、对比思考和实操代码完整记下来给同样被表格处理折腾的 Java 开发者一个参考。1. 为什么要从 EasyExcel 迁移到 Apache Fesod1.1 EasyExcel 确实是好库但“好”不等于适合所有场景先说句公道话EasyExcel 在简单场景下是真省心。两三行代码就能完成 List 对象的导入导出注解模型直观学习成本低性能也够用。我们团队早期选型就是看中这点线上跑了两年普通报表从没出过乱子。但问题在于业务不会一直停留在简单场景。当系统越做越复杂报表模块开始出现多级表头、动态列、嵌套列表模板填充、单元格换行、合并区域联动这些需求时EasyExcel 就显得力不从心了。它的设计逻辑是“薄封装 回调扩展”简单的部分帮你封装好复杂的部分留给你自己写 Handler、Listener、策略类。这套逻辑没错但它把一个很重的负担转移到了使用者身上。举个例子EasyExcel 解析带合并单元格的多级表头时你要在AnalysisEventListener里自己维护表头坐标自己判断当前 Cell 是否属于某个 merge 区域再自己映射到实体字段。一套逻辑写下来两三百行很常见而且换一张表就要重写一遍。这种代码不是不能写而是每次迭代都在消耗团队精力。1.2 到底哪些痛点让我下定决心换真正让我下定决心换库的是下面这几个高频问题你如果也在维护报表模块应该都不陌生复杂表头导入三层的客户信息表头解析后字段错乱排查半天发现是合并单元格的坐标算错了。单元格换行数据库里存的地址带换行符导出到 Excel 后换行消失全挤在一格里。模板填充合并模板里预置了合并单元格但数据行数超过模板预留行数时合并区域不自动扩展样式全乱。libfreetype6 依赖问题部署到无 GUI 的 Alpine Docker 镜像时频繁因为底层图形库问题起不来。NoSuchFieldError: factoryEasyExcel 升级后某个版本内部字段名调整热部署时直接抛反射异常。这些问题在社区里都有大量讨论不是个例。每一个单拎出来都有临时方案放在一起就是持续的维护成本。尤其是当团队里有人调走了新同事接手报表模块看着几百行自定义回调代码入职第一周基本就是在崩溃边缘。1.3 Apache Fesod 是什么它解决了什么Apache Fesod 是 Apache 孵化器里的新项目面向 Java 生态的表格处理内核采用 SAX 流式解析加自研模板引擎。它的定位很明确让复杂表格场景也能像简单场景一样用声明式模型去描述结构而不是靠命令式回调去逐格处理。我第一次看它的设计文档最打动我的一句话是“把表格结构交给模型把解析逻辑交给引擎把样式规则交给声明。”这正好命中我上面那些痛点。Fesod 的复杂表头可以通过注解直接描述层次结构单元格换行有原生的样式支持模板填充支持动态合并区间和嵌套列表展开而且它完全不依赖 Java AWT 相关库不用再担心libfreetype6这种环境问题。我花了两周时间做技术验证把原来的订单导入、对账单导出、客户台账模板这些功能全部迁移到 Fesod 上。结论是能跑通而且代码量比原来少了一大截。下面是这两个库在几个关键维度上的对比对比维度EasyExcelApache Fesod复杂表头需要自定义 Handler 处理坐标注解声明层级结构自动映射模板填充合并预置合并区域数据超出后需手写扩展动态合并区间随数据自动展开单元格换行样式支持有限读取时需额外处理读取保留换行符导出声明式换行依赖体积依赖底层图形库特定环境需额外安装纯 Java 实现无图形依赖嵌套 List 渲染需要多层对象循环加手动 POI 操作模板指令原生支持区域展开反射安全内部缓存模型字段可能带来版本问题接口绑定字段无内部缓存反射2. 核心设计思路与 API 拆解2.1 声明式模型 vs 命令式回调底层思路的差异想真正理解 Fesod 为什么能解决那些问题得先搞清楚它和 EasyExcel 在核心设计思路上的区别。EasyExcel 的解析流程是启动 SAX 事件解析 - 触发invoke回调 - 在回调里判断表头/数据 - 手动映射字段。这个流程非常灵活灵活到所有逻辑都要你亲自写。读取多级表头时你要自己记录“当前表头合并区域的列范围和行范围”再根据行号判断数据属于哪一层级。这个过程本质上是在写一个“表格结构翻译器”EasyExcel 只负责把 Cell 给我剩下的全靠自己。Fesod 的解析流程完全不同你先定义一个模型类用注解声明表头层级、字段对应关系、合并规则。Fesod 引擎解析时会自动读取合并区域信息把二维表格结构还原成对象图。你不需要关心当前解析到第几行也不需要手动判断某个 Cell 属于哪个 merge 区域因为结构已经在模型里表达清楚了。用一个类比来说EasyExcel 像手动挡操作每一步都要自己踩离合换挡Fesod 像自动挡你只需要告诉它目的地它自己完成剩下的动作。对于简单场景手动挡更可控但一旦路况复杂自动挡的优势就体现出来了。2.2 模型层核心注解Sheet、ExcelColumn、CellStyleFesod 的模型层设计得并不复杂核心注解一共就几个我逐个说。首先是Sheet标注在实体类上用来描述整个工作表的信息。常用的属性包括name表名、headerRow表头起始行解决表头前面有空行的问题、dataStartRow数据起始行等。Sheet(name 订单台账, headerRow 1, dataStartRow 3) public class OrderImportVO { }其次是ExcelColumn标注在字段上最核心的属性是header。注意这里header是一个字符串数组它可以精确表达多级表头。比如“订单信息”下有两列“订单号”和“下单时间”那在字段上声明两个层级的表头文本即可Fesod 会自动建立映射关系。ExcelColumn(header {订单信息, 订单号}, width 20) private String orderNo; ExcelColumn(header {订单信息, 下单时间}, width 18) private LocalDateTime orderTime;再往下一个是CellStyle负责样式描述比如换行、边框、字体、对齐方式。易错点在于wrapText必须和合适的行高配合否则文字虽然折行显示但行高不够还是会被截断。ExcelColumn(header {备注}) CellStyle(wrapText true, verticalAlign center, rowHeight auto) private String remark;最后是模板填充场景下的TemplateVar标注在导出模板的占位符上用于声明动态合并规则。比如“按供应商分组合并名称列”就通过merge true和mergeGroupBy supplierId来声明引擎在渲染时会自动按分组值合并相邻单元格。2.3 合并单元格、换行、嵌套 List 在 Fesod 里的表达方式这部分是 Fesod 和 EasyExcel 拉开差距的地方。先说合并单元格的声明式表达。在 EasyExcel 里如果你想按数据值动态合并列只能在afterRowDispose里拿当前行和上一行做比较再调用addMergedRegion手动合并。过程繁琐不说数据量一上来合并区域坐标算错的概率极高。Fesod 的做法是把合并规则直接声明在字段上。ExcelColumn(header {供应商信息, 供应商名称}, merge true, mergeGroupBy supplierId) private String supplierName;引擎渲染数据时会自动根据supplierId的值判断是否连续连续的相同值自动合并单元格。你不用写一行坐标计算逻辑。再说单元格换行。这个问题的本质在于Java 字符串里的\n如果被写到单元格 XML 里必须配合wrapText1这个样式Excel 才允许它正常换行显示。EasyExcel 的默认写样式没有开启折行所以你需要额外设置WriteCellStyle并传给WriteHandler。而 Fesod 把换行作为字段样式的一部分声明后引擎统一写入模板填充和直接导出都生效。嵌套 List 的处理则更接近模板引擎的思维。EasyExcel 做嵌套列表导出要么手动拼接多个 Sheet要么在CellWriteHandler里去操作 Row 对象。而 Fesod 在模板里使用区域指令直接声明“这一块区域要循环展开”。模型上的表达清晰得多也更容易维护。3. 实操五类高频场景从 EasyExcel 迁移到 Fesod3.1 环境准备与依赖引入先说依赖怎么引。Fesod 的核心包发布在 Maven Central坐标是org.apache.fesod:fesod-core。如果你的场景需要用到模板填充再加一个fesod-template模块。dependency groupIdorg.apache.fesod/groupId artifactIdfesod-core/artifactId version1.2.1/version /dependency dependency groupIdorg.apache.fesod/groupId artifactIdfesod-template/artifactId version1.2.1/version /dependency关键点来了fesod-core不传递任何 Java AWT 相关依赖。这意味着在精简的 Docker 镜像上不会再出现libfreetype6缺失这种破事。我们线上环境是 Alpine Linux之前 EasyExcel 偶尔会触发字体或图形环境错误需要额外安装系统包换成 Fesod 之后这个问题直接从根上消失了。同时注意Fesod 最低要求 Java 11。如果你们项目还在 Java 8需要先评估升级成本。不过从当前 Java 生态的趋势看Java 11 已经是底线了这个问题不大。3.2 复杂表头导入从硬编码坐标到模型声明以我们最常遇见的“客户月度对账单”导入为例。原表表头长这样第一行是“客户信息”跨两列客户编码、客户名称第二行是“订单汇总”跨三列订单总数、订单总额、已付金额数据从第三行开始用 EasyExcel 实现时你要在AnalysisEventListener里判断headMap的坐标还得手动跟踪当前行号。一旦表头层级变化代码就得跟着改。我贴一段简化后的示意代码你能直观感受到那种“写了半天都是为了算坐标”的窒息感// EasyExcel 风格需要自己处理表头合并和字段映射 public class OrderListener extends AnalysisEventListenerMapInteger, String { private int currentRow 0; private final ListOrderImportVO result new ArrayList(); Override public void invoke(MapInteger, String row, AnalysisContext context) { currentRow context.readRowHolder().getRowIndex(); // 表头行 0-1跳过 if (currentRow 2) { return; } OrderImportVO vo new OrderImportVO(); // 手写坐标映射0/1/2列属于订单信息区3/4/5列属于客户信息区 vo.setOrderNo(row.get(0)); vo.setOrderTime(row.get(1)); vo.setCustomerCode(row.get(3)); // 还要处理合并单元格合并后的行数据只在首行出现后续行是 null if (vo.getCustomerCode() null) { // 需要额外字段记录上一行的值又是一堆状态管理 } result.add(vo); } }换成 Fesod 之后整个导入逻辑就变成了“定义一个模型 调用一次解析”坐标、合并、字段映射全部由引擎处理。下面是完整的模型定义和调用代码Sheet(name 对账单, headerRow 0, dataStartRow 2) public class SettleImportVO { ExcelColumn(header {客户信息, 客户编码}) private String customerCode; ExcelColumn(header {客户信息, 客户名称}) private String customerName; ExcelColumn(header {订单汇总, 订单总数}) private Integer orderCount; ExcelColumn(header {订单汇总, 订单总额}) private BigDecimal orderAmount; ExcelColumn(header {订单汇总, 已付金额}) private BigDecimal paidAmount; }InputStream is new FileInputStream(月度对账单.xlsx); ListSettleImportVO result Fesod.read(is) .as(SettleImportVO.class) .autoTrim() .parse();这个示例很直接地说明了两种模式的区别。你在模型里声明的header数组层级和 Excel 表头层级是一一对应的引擎通过读取合并单元格信息自动把“客户信息”下的两列映射到customerCode和customerName上。原来在回调里几十行的坐标计算现在一行注解搞定。3.3 单元格换行读入和导出的正确姿势单元格换行听起来是小问题但实际处理起来很坑。先说导入。供应商提供给我们的 Excel 里地址字段经常带换行比如“XX大厦\n3层302室”。用 EasyExcel 读取时默认行为会把换行符吞掉或者在某些版本里表现不稳定导致地址变成“XX大厦3层302室”对接方还得反复确认。使用 Fesod 之后导入时换行符默认保留。需要做的只是在模型字段上加一个CellStyle声明wrapText true不过这只是导出时的样式声明不影响导入。如果你想在读取时保留原始换行符直接读取字符串即可不需要额外配置// 导入时换行符保留读取到的 remark 字段是完整带换行文本 ExcelColumn(header {地址}) private String address;再讲导出。要让换行在 Excel 里真正显示成多行需要两个条件单元格开启自动换行以及行高足够。Fesod 的写法是这样的ExcelColumn(header {备注信息}) CellStyle(wrapText true, rowHeight auto, width 30) private String remark;注意rowHeight auto这个值Fesod 引擎在填充数据时会根据本行所有字段的最大文本宽度自动估算行高。如果不设置这个属性即使开启了换行行高依然是默认值导致文字被截断显示。这里有个经验“auto” 不是万能的如果某行数据特别长自动估算的极限大约是 128 磅再长就需要手动指定固定行高。如果是模板文件导出同样只需要在模板里把该单元格格式设为“自动换行”然后在 Java 代码里给对应字段添加CellStyle(wrapText true, rowHeight auto)。Fesod 模板引擎填充时会保留模板自身的单元格样式同时应用声明中的补充样式两者并不冲突。3.4 模板填充与合并单元格嵌套 List 的最终解法模板填充一直是 EasyExcel 用户绕不过去的坎尤其是当模板里既有普通变量又有列表循环还有合并单元格联动时写起来非常痛苦。我们之前的做法是模板里预留 10 行空行数据超过 10 行就用 POI 手动插入行然后再把模板里预设好的合并区域一套一套复制到新插入的行上。听起来就复杂实际做起来更复杂光动态合并代码就维护了三个版本。Fesod 的模板模块解决这个问题的方式是引入区域指令。模板文件中你可以把一段区域标记为循环块引擎在渲染时会将这个区域复制 N 份并自动扩展所有涉及的合并单元格。模板里要这样写注意占位符的语法{{#items}} {{supplierName}} {{orderCount}} {{orderAmount}} {{/items}}对应的 Java 代码MapString, Object data new HashMap(); data.put(title, 2024年12月对账单); ListSupplierOrderDO items service.listOrders(); data.put(items, items); InputStream templateStream new FileInputStream(对账单模板.xlsx); OutputStream out new FileOutputStream(对账单_202412.xlsx); Fesod.template(templateStream) .root(data) .writeTo(out);关于动态合并要在模型的合并字段上声明规则。比如supplierName列要求同一供应商的相邻行自动合并就在字段上加上mergeGroupByExcelColumn(header {供应商名称}, merge true, mergeGroupBy supplierId) private String supplierName; ExcelColumn(header {供应商编码}) private String supplierId;这样当items里连续两条数据的supplierId相同时渲染结果会把这些行的供应商名称单元格合并成一个。数据量无论多少行合并区域都会自动跟随扩展不再需要预留空行或写复制合并区域的策略代码。嵌套 List 的本质是把对象图渲染成扁平表格。Fesod 的区域指令天然支持“一个对象循环里嵌套另一个对象循环”层级再多也只是模板里多套一层指令的问题。我之前遇到过一个“部门 - 员工 - 当月绩效”的三层结构用 Fesod 模板写出来三层循环非常直观。3.5 反射报错的规避NoSuchFieldError factory 的前因后果NoSuchFieldError: factory这个问题我印象太深刻了。那是一个周五下午系统例行发版刚启动完就收到告警。排查发现是因为 EasyExcel 的内部缓存模型在某个版本里对字段做了重构而 JVM 里残留的类缓存引用了旧的字段名。热部署场景尤其容易触发因为动态类加载最容易踩到这种坑。从根源上说这类问题的本质是“基于内部反射字段名 缓存”的实现策略。字段名在编译器眼里只是一个字符串一旦库内部改了字段名而新代码没有同步更新缓存运行时就抛出 NoSuchFieldError。EasyExcel 本身很优秀但它的内部实现里有不少这种“通过反射拼接字段名”的逻辑遇到版本升级和热部署就容易出问题。Fesod 在设计上规避了这条路径。它默认的模型绑定方式是基于 Java 接口或 Record 的字段约束不维护内部反射缓存。以 Record 为例你定义导入模型public record SettleRecord( String customerCode, String customerName, Integer orderCount, BigDecimal orderAmount ) { }Fesod 在解析时直接通过访问器方法绑定字段不通过反射拼接字段名。这样做有两个好处一是运行时不会因为内部字段改名而产生 NoSuchFieldError二是访问性能更稳定因为 Java 编译器会对接口方法生成直接调用不用每次都走反射开销。如果你的模型对象不方便改成 RecordFesod 也支持自定义 Converter 模式核心思想是把“字段访问方式”完全交给用户定义而不是依赖库内部的缓存猜测。这个设计从根上断绝了那类反射异常问题。4. 迁移避坑与问题排查速查表4.1 从 EasyExcel 迁移到 Fesod 的真实踩坑记录任何技术切换都不是一帆风顺的Fesod 也不是完全没有学习成本。我在迁移过程中遇到几个值得记录的坑都写在这里你可以少走点弯路。第一个坑是模板指令的大小写敏感性。Fesod 模板引擎的指令{{#items}}是大小写敏感的如果你在模板里写{{#Items}}它不会报错但不会渲染任何数据。这属于静默失败排查起来很费时间。我的经验是先用最简单的单个字符串变量做验证确认模板引擎链路通了再加循环结构。第二个坑是mergeGroupBy必须声明在“被合并字段”旁边。我一开始只给展示字段加了merge true但没写mergeGroupBy结果字段没有按预期合并。原因是引擎不知道按哪个值来判断“是否相同”。所以这个属性不能省。mergeGroupBy指定的字段一定是对象里真实存在的字段否则解析时会抛FieldNotFoundException。第三个坑是 Fesod 默认只读取第一个工作表。如果你导入的文件包含多个 Sheet需要在读取时显式指定sheetIndex或sheetName。我们的业务有个 Excel 是“订单明细”和“客户信息”两个 Sheet 放在一起当时没指定导致客户信息一直读不到。代码示例ListSettleImportVO result Fesod.read(is) .sheet(1) // 指定第二个 Sheet .as(SettleImportVO.class) .parse();第四个坑是数字类型转换。Excel 单元格里的数字默认会被解析为 BigDecimal 或 Double如果你的模型字段是Integer或Long某些情况下会抛出类型转换异常。解决方案是给字段声明显式类型转换器。Fesod 内置了常用类型转换器一般不需要自己写但要在模型上标注清楚。4.2 常见问题速查表把前面所有问题汇总成一个表格方便你做迁移时对照排查。这些都是真实场景不只是理论推演。场景/报错现象原因Fesod 解决方式复杂表头导入数据错列、字段对应不上合并单元格坐标需要手算SheetExcelColumn声明层级自动映射单元格换行导入地址、备注的换行符丢失默认解析未保留\n字符串字段直接保留换行符无需额外处理单元格换行导出文字挤在一行显示不完整未开启 wrapText 或行高不够CellStyle(wrapText true, rowHeight auto)模板填充合并数据超过模板行数时合并区域错乱预置合并区域不会自动扩展模板区域指令{{#items}}mergeGroupBy动态合并libfreetype6 缺失容器启动报底层图形库错误依赖了 Java AWT 相关库Fesod 纯 Java 实现不依赖系统图形库NoSuchFieldError: factory热部署/升级后启动异常内部反射字段缓存与版本不一致基于接口/Record 绑定不维护字段名缓存嵌套 List 渲染列表数据只能渲染第一层缺少区域循环能力模板区域指令支持任意层级嵌套循环多 Sheet 读取数据从第二个 Sheet 读不到默认只读第一个 Sheet.sheet(1)或.sheet(名称)显式指定数字类型转换失败字符串/Integer 字段报类型异常Excel 数值默认解析为浮点字段标注显式类型转换器模板变量不渲染输出文件里占位符原样保留变量名大小写不一致或作用域错误检查指令大小写确认变量在 root 中4.3 迁移后的收益与我的个人体会迁移完成之后我把线上跑的 100 万行导入任务做了压力对比。EasyExcel 处理 100 万行大约需要 13 到 15 秒峰值内存约 900MBFesod 在同一台机器上处理同样的数据耗时 10 秒左右峰值内存约 600MB。这个结果符合它的设计预期SAX 流式解析加更轻的字段绑定模型确实省内存。代码量的变化更直观。原先报表模块里复杂表头导入、模板动态合并、嵌套列表导出这几块的代码加起来接近 1800 行。迁移到 Fesod 后模型类加调用代码只有 700 行左右。减少的主要是坐标计算、条件判断和重复的 POI 操作代码。团队新同事接手报表模块看模型定义就能明白逻辑不需要再从一堆回调里猜业务意图。最后分享一个我的小习惯每次切换核心库之前不要只看文档就上生产。我会先在本地写一个针对真实业务的压力测试脚本把最典型的几个场景全部跑一遍包括大文件导入、模板导出、特殊字符处理。库好不好不是看它宣传了什么而是看它在你自己的场景下能不能站稳。这次迁移 Fesod 之所以顺畅很大程度也归功于迁移前做了扎实的验证。希望你也能用这套方法少踩一些我踩过的坑。
延伸阅读

更多相关文章

2026/11/5 19:53:01

告别沉重Postman:Bruno——10MB开源的轻量API客户端实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 15:16:23

SPIRAL框架解析:轻量级Web组件开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:58:59

质量控制常用方法以及案例

在现代企业管理中,质量控制(QC, Quality Control)的核心在于通过系统化的工具来预防缺陷、找出问题根源并实现持续改进。最常用的质量控制方法(以经典的“品管七大手法”与现代管理理论为主),并结合实际工业…

2026/11/6 1:58:59

800V超充技术:让电动汽车补能像加油一样快

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:58:59

解决uni.app框架下的APP读取NFC硬件UID的冷启动问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:03:39

MCU晶振电路设计全解析:从负载电容计算到PCB布局调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑