当前位置: 首页 > article >正文

EasyExcel 导出合并层级单元格

EasyExcel 导出合并层级单元格

一、案例

案例一

  • 1.相同订单号单元格进行合并

image-20250207145806662

合并结果

image-20250207145841217

案例二

  • 1.相同订单号的单元格进行合并
  • 2.相同订单号的总数和总金额进行合并

image-20250207150108895

合并结果

image-20250207150033347

案例三

  • 1.相同订单号的单元格进行合并
  • 2.相同订单号的商品分类进行合并
  • 3.相同订单号的总数和总金额进行合并
  • 4.相同订单号和相同商品分类的分类总数、分类总金额进行合并

image-20250207150213477

合并结果

image-20250207150139989

二、代码实现

相关依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.26</version>
</dependency>
2.1 AbstractMergeStrategy
import com.alibaba.excel.write.handler.CellWriteHandler;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
public abstract class AbstractMergeStrategy implements CellWriteHandler {/*** 最大行索引*/public final static int EXCEL_LAST_INDEX = 1048575;/*** 默认合并起始行*/public final static int DEFAULT_START_ROW_INDEX = 1;/*** 合并抽象方法* @param sheet* @param cell*/public abstract void merge(Sheet sheet, Cell cell);/*** 获取单元格值* @param cell */public Object getCellValue(Cell cell) {return cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();}
}
2.2 ColumnMergeStrategy
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.easy.excel.demo.model.MergeRowColumn;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;import java.util.*;
import java.util.stream.Collectors;/*** 合并单元格策略:适用于列合并*/
@Slf4j
public class ColumnMergeStrategy extends AbstractMergeStrategy {/*** 合并起始行索引*/private int mergeStartRowIndex;/*** 合并结束行索引*/private int mergeEndRowIndex;/*** 待合并的列(如果没有指定,则所有的列都会进行合并)*/private List<Integer> mergeColumnIndexList;/*** 待合并的列父级依赖关系 <需要合并的列索引, 依赖的父级列索引>* key 需要合并的列, value 所依赖的父级列的列表    */private Map<Integer, List<Integer>> mergeColumnIndexMap = new HashMap<>();/*** 合并的行列索引数据(存储每列的数据合并的行列索引范围)*/private Map<Integer, MergeRowColumn> mergeRowColumnMap = new HashMap<>();private Sheet sheet;public ColumnMergeStrategy() {this(DEFAULT_START_ROW_INDEX, EXCEL_LAST_INDEX);}public ColumnMergeStrategy(List<Integer> mergeColumnIndexList) {this(DEFAULT_START_ROW_INDEX, EXCEL_LAST_INDEX, mergeColumnIndexList);}public ColumnMergeStrategy(Map<Integer, List<Integer>> mergeColumnIndexMap) {this.mergeColumnIndexMap = mergeColumnIndexMap;this.mergeColumnIndexList = mergeColumnIndexMap.keySet().stream().collect(Collectors.toList());this.mergeStartRowIndex = DEFAULT_START_ROW_INDEX;this.mergeEndRowIndex = EXCEL_LAST_INDEX;}public ColumnMergeStrategy(int mergeStartRowIndex) {this(mergeStartRowIndex, EXCEL_LAST_INDEX);}public ColumnMergeStrategy(int mergeStartRowIndex, int mergeEndRowIndex) {this(mergeStartRowIndex, mergeEndRowIndex, new ArrayList<>());}public ColumnMergeStrategy(int mergeStartRowIndex, int mergeEndRowIndex, List<Integer> mergeColumnIndexList) {this.mergeStartRowIndex = mergeStartRowIndex;this.mergeEndRowIndex = mergeEndRowIndex;this.mergeColumnIndexList = mergeColumnIndexList;}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean isHead) {// 头不参与合并if (isHead) {return;}// 初始化 sheetif (sheet == null) {this.sheet = writeSheetHolder.getSheet();}// 如果当前行大于合并起始行则进行合并if (cell.getRowIndex() >= mergeStartRowIndex && cell.getRowIndex() <= mergeEndRowIndex) {// 判断是否是全列合并或者当前列在需要合并列中if (CollUtil.isEmpty(mergeColumnIndexList) || (CollUtil.isNotEmpty(mergeColumnIndexList) && mergeColumnIndexList.contains(cell.getColumnIndex()))) {// 合并单元格this.merge(writeSheetHolder.getSheet(), cell);}}}@Overridepublic void merge(Sheet sheet, Cell cell) {// 当前单元格行、列索引int curRowIndex = cell.getRowIndex();int curColumnIndex = cell.getColumnIndex();// 当前单元格的值为Object curCellValue = this.getCellValue(cell);// 上一行的行索引int aboveRowIndex = curRowIndex - 1;if (aboveRowIndex < 0 || aboveRowIndex < mergeStartRowIndex) {// 初始化当前列的 合并区域范围MergeRowColumn mergeRowColumn = new MergeRowColumn(curRowIndex, curRowIndex, curColumnIndex, curColumnIndex);mergeRowColumnMap.put(curColumnIndex, mergeRowColumn);return;}// 获取上一个单元格Cell aboveCell = sheet.getRow(aboveRowIndex).getCell(curColumnIndex);// 上一个单元格的值Object aboveCellValue = this.getCellValue(aboveCell);// 判断上一个单元格是否能合并if (Objects.equals(curCellValue, aboveCellValue)) {boolean needMerge = true;// 父级列 列表List<Integer> parentColumnIndexList = mergeColumnIndexMap.get(curColumnIndex);if (parentColumnIndexList != null && !parentColumnIndexList.isEmpty()) {for (Integer parentColumnIndex : parentColumnIndexList) {Cell mainCell = sheet.getRow(curRowIndex).getCell(parentColumnIndex);Cell aboveMainCell = sheet.getRow(aboveRowIndex).getCell(parentColumnIndex);Object mainCellValue = this.getCellValue(mainCell);Object aboveMainCellValue = this.getCellValue(aboveMainCell);// 所有主列都需要满足合并条件才能合并副列if (!Objects.equals(mainCellValue, aboveMainCellValue)) {needMerge = false;break;}}}// 允许合并if (needMerge){// 修改当前列的行合并索引范围MergeRowColumn mergeRowColumn = mergeRowColumnMap.get(curColumnIndex);mergeRowColumn.setEndRowIndex(curRowIndex);} else {// 合并已有的单元格,修改行索引指向mergeRowColumnCell(sheet, curRowIndex,curColumnIndex);}} else {// 合并已有的单元格,修改行索引指向mergeRowColumnCell(sheet, curRowIndex,curColumnIndex);}}/*** 检查给定的单元格是否在一个或多个合并区域中。** @return 如果指定单元格是合并区域的一部分,则返回 true;否则返回 false。*/private boolean isMergedRegion(Sheet sheet, Integer rowIndex, Integer columnIndex) {// 获取当前工作表中的所有合并区域数量int numMergedRegions = sheet.getNumMergedRegions();// 遍历所有合并区域for (int i = 0; i < numMergedRegions; i++) {CellRangeAddress region = sheet.getMergedRegion(i);// 检查指定的单元格是否在当前合并区域内if (region.isInRange(rowIndex, columnIndex)) {return true;}}return false;}/*** 合并区域单元格** @param sheet* @param curRowIndex* @param curColumnIndex*/private void mergeRowColumnCell(Sheet sheet, Integer curRowIndex, Integer curColumnIndex) {// 获取当前的列的合并区域索引对象MergeRowColumn mergeRowColumn = mergeRowColumnMap.get(curColumnIndex);// 合并单元格mergeCell(sheet, mergeRowColumn, curRowIndex, curColumnIndex);}/*** 手动合并最后的单元格* (最后一段单元格需要手动合并)*/public void finalMergeCell() {// 遍历所有列的合并索引,合并最后的单元格for (Map.Entry<Integer, MergeRowColumn> entry : mergeRowColumnMap.entrySet()) {Integer columnIndex = entry.getKey();MergeRowColumn mergeRowColumn = entry.getValue();Integer endRowIndex = mergeRowColumn.getEndRowIndex();mergeCell(sheet, mergeRowColumn, endRowIndex, columnIndex);}}/*** 合并单元格** @param sheet* @param mergeRowColumn* @param curRowIndex* @param curColumnIndex*/private void mergeCell(Sheet sheet, MergeRowColumn mergeRowColumn,Integer curRowIndex, Integer curColumnIndex) {// 获取合并的行起始索引Integer startRowIndex = mergeRowColumn.getStartRowIndex();// 获取合并的行结束索引Integer endRowIndex = mergeRowColumn.getEndRowIndex();// 合并单元格(至少有两个单元格以上才能进行合并)if (startRowIndex < endRowIndex) {CellRangeAddress cellAddresses = new CellRangeAddress(startRowIndex, endRowIndex, curColumnIndex, curColumnIndex);// 判断起始单元格是否已经合并过了boolean mergedRegion = isMergedRegion(sheet, startRowIndex, curColumnIndex);if (!mergedRegion) {// 合并指定区域的单元格sheet.addMergedRegion(cellAddresses);}}// 重置合并索引(当前列的行指针下移)mergeRowColumn.setStartRowIndex(curRowIndex);mergeRowColumn.setEndRowIndex(curRowIndex);}
}

源码分析

  • mergeRowColumnMap 是进行合并的关键所在,存储了所有需要合并的列的行合并区域索引,在遍历数据过程中,根据情况进行单元格合并然后偏移指针,或者只修改指针,这样就不需要频繁的进行合并操作。
2.3 MergeRowColumn
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeRowColumn {/*** 开始行索引*/private Integer startRowIndex;/*** 结束行索引*/private Integer endRowIndex;/*** 开始列索引*/private Integer startColumnIndex;/*** 结束列索引*/private Integer endColumnIndex;
}

三、测试

案例测试代码

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.easy.excel.demo.handler.ColumnMergeStrategy;
import com.easy.excel.demo.model.OrderDetailEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import java.io.File;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;@Slf4j
@SpringBootTest
public class EasyExcelDemoTest2 {/*** 案例一*/@Testpublic void testMerge1(){File file = new File("order1.xlsx");WriteSheet writeSheet = EasyExcel.writerSheet("sheet1").head(OrderDetailEntity.class).registerWriteHandler(new ColumnMergeStrategy(Arrays.asList(0)))// 订单号.build();ExcelWriter excelWriter = EasyExcel.write(file).build();// 写入数据excelWriter.write(data(), writeSheet);// 手动合并最后的单元格(最后的单元格没有办法合并,需要手动进行合并)writeSheet.getCustomWriteHandlerList().stream().filter(handler -> handler instanceof ColumnMergeStrategy).map(handler -> (ColumnMergeStrategy) handler).forEach(handler -> handler.finalMergeCell());excelWriter.finish();}/*** 案例二*/@Testpublic void testMerge2(){// 输出文件路径File file = new File("order2.xlsx");// 初始化列合并列父级依赖关系Map<Integer, List<Integer>> mergeColumnIndexMap = new HashMap<>();mergeColumnIndexMap.put(0, new ArrayList<>());//订单号mergeColumnIndexMap.put(10, Arrays.asList(0));//总数 ==> 订单号mergeColumnIndexMap.put(11, Arrays.asList(0));//总金额 ==> 订单号WriteSheet writeSheet = EasyExcel.writerSheet("sheet1").head(OrderDetailEntity.class).registerWriteHandler(new ColumnMergeStrategy(mergeColumnIndexMap)).build();ExcelWriter excelWriter = EasyExcel.write(file).build();excelWriter.write(data(), writeSheet);// 手动合并最后的单元格(分段数据注入时最后的单元格没有办法合并,需要手动进行合并)writeSheet.getCustomWriteHandlerList().stream().filter(handler -> handler instanceof ColumnMergeStrategy).map(handler -> (ColumnMergeStrategy) handler).forEach(handler -> handler.finalMergeCell());excelWriter.finish();}/*** 案例三*/@Testpublic void testMerge3(){// 输出文件路径File file = new File("order3.xlsx");// 初始化列合并上级依赖关系Map<Integer, List<Integer>> mergeColumnIndexMap = new HashMap<>();mergeColumnIndexMap.put(0, new ArrayList<>());//订单号mergeColumnIndexMap.put(2, Arrays.asList(0));//商品分类 ==> 订单号mergeColumnIndexMap.put(8, Arrays.asList(0, 2));//分类总数 ==> 订单号,商品分类mergeColumnIndexMap.put(9, Arrays.asList(0, 2));//分类总金额 ==> 订单号,商品分类mergeColumnIndexMap.put(10, Arrays.asList(0));//总数 ==> 订单号mergeColumnIndexMap.put(11, Arrays.asList(0));//总金额 ==> 订单号WriteSheet writeSheet = EasyExcel.writerSheet("sheet1").head(OrderDetailEntity.class).registerWriteHandler(new ColumnMergeStrategy(mergeColumnIndexMap)).build();ExcelWriter excelWriter = EasyExcel.write(file).build();// 模拟分页查询数据for (int i = 0; i < 3; i++) {excelWriter.write(data(), writeSheet);}// 手动合并最后的单元格(分段数据注入时最后的单元格没有办法合并,需要手动进行合并)writeSheet.getCustomWriteHandlerList().stream().filter(handler -> handler instanceof ColumnMergeStrategy).map(handler -> (ColumnMergeStrategy) handler).forEach(handler -> handler.finalMergeCell());excelWriter.finish();}/*** 随机生成测试数据* @return*/private Collection<?> data() {Map<String, List<String>> productMap = getProductMap();List<String> statusList = Arrays.asList("待发货", "已发货", "运输中", "待取货", "已完成");List<OrderDetailEntity> dataList = new ArrayList<>();Random random = new Random();int orderCount = random.nextInt(2) + 5;for (int i = 0; i < orderCount; i++) {String orderCode = "PL" + DateUtils.format(new Date(), "yyyyMMddHHmm") + "000" + i;int orderDetailCount = random.nextInt(10) + 1;List<OrderDetailEntity> detailEntities = new ArrayList<>();Map<String, BigDecimal> categoryTotalQuantityMap = new HashMap<>();Map<String, BigDecimal> categoryTotalPriceMap = new HashMap<>();BigDecimal totalQuantity = BigDecimal.ZERO;BigDecimal totalPrice = BigDecimal.ZERO;for (int j = 0; j < orderDetailCount; j++) {String orderDetailCode = UUID.randomUUID().toString();String productCategory = new ArrayList<String>(productMap.keySet()).get(random.nextInt(productMap.size()));List<String> productList = productMap.get(productCategory);String productCode = "SKU" + (random.nextInt(1000)+1000);String productName = productList.get(random.nextInt(productList.size())) + "-A" + random.nextInt(50);BigDecimal price = new BigDecimal(random.nextInt(2000) + 800);BigDecimal quantity = new BigDecimal(random.nextInt(5) + 1);String status = statusList.get(random.nextInt(statusList.size()));String key = orderCode + "-" + productCategory;BigDecimal categoryTotalQuantity = categoryTotalQuantityMap.get(key);if (categoryTotalQuantity == null) {categoryTotalQuantity = quantity;} else {categoryTotalQuantity = categoryTotalQuantity.add(quantity);}categoryTotalQuantityMap.put(key, categoryTotalQuantity);BigDecimal categoryTotalPrice = categoryTotalPriceMap.get(key);if (categoryTotalPrice == null) {categoryTotalPrice = price.multiply(quantity);} else {categoryTotalPrice = categoryTotalPrice.add(price.multiply(quantity));}categoryTotalPriceMap.put(key, categoryTotalPrice);totalQuantity = totalQuantity.add(quantity);totalPrice = totalPrice.add(price.multiply(quantity));detailEntities.add(OrderDetailEntity.builder().orderCode(orderCode).orderDetailCode(orderDetailCode).productCategory(productCategory).productCode(productCode).productName(productName).price(price).quantity(quantity).status(status).build());}for (OrderDetailEntity item : detailEntities) {String key = item.getOrderCode() + "-" + item.getProductCategory();item.setCategoryTotalQuantity(categoryTotalQuantityMap.get(key));item.setCategoryTotalPrice(categoryTotalPriceMap.get(key));item.setTotalQuantity(totalQuantity);item.setTotalPrice(totalPrice);}detailEntities = detailEntities.stream().sorted(Comparator.comparing(OrderDetailEntity::getOrderCode).thenComparing(OrderDetailEntity::getProductCategory)).collect(Collectors.toList());dataList.addAll(detailEntities);}return dataList;}private Map<String, List<String>> getProductMap() {Map<String, List<String>> productMap = new HashMap<>();// 家电List<String> householdList = new ArrayList<>();householdList.add("电视机");householdList.add("冰箱");householdList.add("洗衣机");householdList.add("空调");productMap.put("家电", householdList);// 数码产品List<String> digitalList = new ArrayList<>();digitalList.add("手机");digitalList.add("摄影机");digitalList.add("电脑");digitalList.add("照相机");digitalList.add("投影仪");digitalList.add("智能手表");productMap.put("数码产品", digitalList);// 健身器材List<String> gymEquipmentList = new ArrayList<>();gymEquipmentList.add("动感单车");gymEquipmentList.add("健身椅");gymEquipmentList.add("跑步机");productMap.put("健身器材", gymEquipmentList);return productMap;}
}

OrderDetailEntity

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.BorderStyleEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
// 头背景设置
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
//标题高度
@HeadRowHeight(30)
//内容高度
@ContentRowHeight(20)
//内容居中,左、上、右、下的边框显示
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
public class OrderDetailEntity {@ExcelProperty(value = "订单号")@ColumnWidth(25)private String orderCode;@ExcelProperty(value = "订单明细")@ColumnWidth(40)private String orderDetailCode;@ExcelProperty(value = "商品分类")@ColumnWidth(20)private String productCategory;@ExcelProperty(value = "商品编码")@ColumnWidth(20)private String productCode;@ExcelProperty(value = "商品名称")@ColumnWidth(20)private String productName;@ExcelProperty(value = "单价")@ColumnWidth(10)private BigDecimal price;@ExcelProperty(value = "数量")@ColumnWidth(10)private BigDecimal quantity;@ExcelProperty(value = "状态")@ColumnWidth(10)private String status;@ExcelProperty(value = "分类总数")@ColumnWidth(20)private BigDecimal categoryTotalQuantity;@ExcelProperty(value = "分类总金额")@ColumnWidth(20)private BigDecimal categoryTotalPrice;@ExcelProperty(value = "总数")@ColumnWidth(10)private BigDecimal totalQuantity;@ExcelProperty(value = "总金额")@ColumnWidth(10)private BigDecimal totalPrice;
}

参考文章

https://blog.csdn.net/xhmico/article/details/141814528

相关文章:

EasyExcel 导出合并层级单元格

EasyExcel 导出合并层级单元格 一、案例 案例一 1.相同订单号单元格进行合并 合并结果 案例二 1.相同订单号的单元格进行合并2.相同订单号的总数和总金额进行合并 合并结果 案例三 1.相同订单号的单元格进行合并2.相同订单号的商品分类进行合并3.相同订单号的总数和总金额…...

青少年编程与数学 02-009 Django 5 Web 编程 01课题、概要

青少年编程与数学 02-009 Django 5 Web 编程 01课题、概要 一、Django 5Django 5 的主要特性包括&#xff1a; 二、MVT模式三、官方网站四、内置功能数据库 ORM&#xff08;对象关系映射&#xff09;用户认证和授权表单处理模板引擎URL 路由缓存框架国际化和本地化安全性功能管…...

Oracle认证大师(OCM)学习计划书

Oracle认证大师&#xff08;OCM&#xff09;学习计划书 一、学习目标 Oracle Certified Master&#xff08;OCM&#xff09;是Oracle官方认证体系中的最高级别认证&#xff0c;要求考生具备扎实的数据库管理技能、丰富的实战经验以及解决复杂问题的能力。本计划旨在通过系统化的…...

2.7学习

crypto buu-还原大师 仔细阅读题目&#xff0c;这里有一段字符串&#xff0c;但是其中有四个大写字母被替换成了‘&#xff1f;’&#xff0c;那么我们写脚本&#xff1a;首先将四个问号均换成26个大写字母并且组成不同的组合&#xff0c; 所以有四个循环让四个问号都遍历26个…...

oracle ORA-27054报错处理

现象 在oracle执行expdp&#xff0c;rman备份&#xff0c;xtts的时候,由于没有足够的本地空间&#xff0c;只能使用到NFS的文件系统但有时候会出现如下报错 ORA-27054: NFS file system where the file is created or resides is not mounted with correct options根据提示信…...

核显是什么

核显&#xff08;Integrated Graphics&#xff0c;集成显卡&#xff09;是指集成在中央处理器&#xff08;CPU&#xff09;或者主板上的显卡。与独立显卡不同&#xff0c;核显不需要额外的显卡硬件&#xff0c;而是直接使用系统内存&#xff08;RAM&#xff09;和处理器的资源来…...

使用LLaMA Factory踩坑记录

前置条件&#xff1a;电脑显卡RTX 4080 问题&#xff1a;LLaMA-Factory在运行的时候&#xff0c;弹出未检测到CUDA的报错信息 结论&#xff1a;出现了以上的报错&#xff0c;主要可以归结于以下两个方面&#xff1a; 1、没有安装GPU版本的pytorch&#xff0c;下载的是CPU版本…...

在qtcreator中添加片段,提高开发效率。

文件名&#xff1a;text.xml <?xml version"1.0" encoding"utf-8"?> <snippets><!-- 版权声明 --><snippet group"Text" trigger"copyright" id"comment_copyright">/*!* file %{CurrentDocum…...

redis的数据结构介绍(string

redis是键值数据库&#xff0c;key一般是string类型&#xff0c;value的类型很多 string&#xff0c;hash&#xff0c;list&#xff0c;set&#xff0c;sortedset&#xff0c;geo&#xff0c;bitmap&#xff0c;hyperlog redis常用通用命令&#xff1a; keys&#xff1a; …...

ASP.NET Core JWT Version

目录 JWT缺点 方案 实现 Program.cs IdentityHelper.cs Controller NotCheckJWTVersionAttribute.cs JWTVersionCheckkFilter.cs 优化 JWT缺点 到期前&#xff0c;令牌无法被提前撤回。什么情况下需要撤回&#xff1f;用户被删除了、禁用了&#xff1b;令牌被盗用了&…...

电路研究9.3——合宙Air780EP中的AT开发指南(含TCP 示例)

根据合宙的AT研发推荐&#xff0c; AT指令基本上也简单看完了&#xff0c;这里开始转到AT的开发了。 AT 命令采用标准串口进行数据收发&#xff0c;将以前复杂的设备通讯方式转换成简单的串口编程&#xff0c; 大大简化了产品的硬件设计和软件开发成本&#xff0c;这使得几乎所…...

Reqable使用实践

一、背景 日常开发中&#xff0c;难免要抓取请求数据&#xff0c;查看接口数据&#xff0c;从而更好定位问题&#xff0c;基于这个原因&#xff0c;查找了一些抓包工具&#xff0c;例如&#xff1a; HttpCanary、 Steam 、Fiddler等&#xff0c;不是要钱&#xff0c;就是只对苹…...

【蓝桥杯嵌入式】2_LED

全部代码网盘自取 链接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码&#xff1a;3ii2 1、电路图 74HC573是八位锁存器&#xff0c;当控制端LE脚为高电平时&#xff0c;芯片“导通”&#xff0c;LE为低电平时芯片“截止”即将输出状态“锁存”…...

Flink 调用海豚调度器 SQL 脚本实现1份SQL流批一体化的方案和可运行的代码实例

目录 一、流批一体化概述 二、Flink 与海豚调度器结合实现流批一体化的好处 2.1 代码复用性增强 2.2 开发和维护成本降低 2.3 数据一致性保证 2.4 提高系统的灵活性和可扩展性 三、实现思路步骤 3.1 环境准备 3.2 编写 SQL 脚本并上传到海豚调度器 3.3 实现资源下载功…...

B树详解及其C语言实现

目录 一、B树的基本原理 二、B树操作过程图形化演示 三、B树的应用场景 四、C语言实现B树及示例 五、代码执行结果说明 六、应用实例&#xff1a;文件系统目录索引 七、总结 一、B树的基本原理 B树&#xff08;B-Tree&#xff09; 是一种自平衡的树数据结构&#xff0c;…...

【Go语言快速上手】第二部分:Go语言进阶

文章目录 并发编程goroutine&#xff1a;创建和调度 goroutinechannel&#xff1a;无缓冲 channel、有缓冲 channel、select 语句无缓冲 channel有缓冲 channelselect 语句 sync 包&#xff1a;Mutex、RWMutex、WaitGroup 等同步原语Mutex&#xff1a;互斥锁RWMutex&#xff1a…...

ARM64 Linux 内核学习指南:从基础到实践

前言 ARM64 作为当今主流的处理器架构&#xff0c;被广泛应用于移动设备、嵌入式系统和服务器领域。学习 ARM64 在 Linux 内核中的实现&#xff0c;不仅有助于深入理解操作系统底层机制&#xff0c;还能提升在内核开发、驱动编写、虚拟化等领域的专业能力。 本指南面向对 Lin…...

零基础都可以本地部署Deepseek R1

文章目录 一、硬件配置需求二、详细部署步骤1. 安装 Ollama 工具2. 部署 DeepSeek-R1 模型3. API使用4. 配置图形化交互界面&#xff08;可选&#xff09;5. 使用与注意事项 一、硬件配置需求 不同版本的 DeepSeek-R1 模型参数量不同&#xff0c;对硬件资源的要求也不尽相同。…...

掌握Spring @SessionAttribute:跨请求数据共享的艺术

SessionAttribute注解在Spring中的作用&#xff0c;就像是一个“数据中转站”。 在Web应用中&#xff0c;我们经常需要在多个请求之间共享数据。比如&#xff0c;用户登录后&#xff0c;我们需要在多个页面或请求中保持用户的登录状态。这时&#xff0c;SessionAttribute注解就…...

视频采集卡接口

采集卡的正面有MIC IN、LINE IN以及AUDIO OUT三个接口&#xff0c; MIC IN为麦克风输入&#xff0c;我们如果要给采集到的视频实时配音或者是在直播的时候进行讲解&#xff0c;就可以在这里插入一个麦克风&#xff0c; LINE IN为音频线路输入&#xff0c;可以外接播放背景音乐…...

64【32与64位程序的区别】

很多人可能有一个观念&#xff0c;那就是64位的程序NB&#xff0c;有技术含量&#xff0c;但是要说nb在哪&#xff0c;很多人又说不上来&#xff0c;本节来对这个问题做一个探讨 下图中左边的是加载的64程序&#xff0c;右边的是32位程序&#xff0c; 在上一节课我们已经理解…...

ai智能DeepSeek 在 Cursor 中的配置与应用实践

DeepSeek 是一款高效的深度搜索引擎&#xff0c;能够为开发者提供更智能、更精准的搜索体验。在数据量大、查询复杂的场景中&#xff0c;DeepSeek 能够帮助提升查询的响应速度和精确度。本文将介绍 DeepSeek 在 Cursor 中的配置与应用&#xff0c;帮助开发者理解如何在实际开发…...

Deepseek的起源与发展

文章目录 前言一、Deepseek的起源二、DeepSeek的发展脉络三、Deepseek的突破与优势(1)功能强大:核心能力与应用场景(2)性能优势:效率与效果的革命性提升四、Deepseek开源引发关注前言 DeepSeek 在网络安全领域带来的新机遇,DeepSeek 从崭露头角到引领 AI 领域的重大变革,已…...

ubuntu conda运行kivy时报“No matching FB config found”

错误描述&#xff1a;本人使用ubuntu自带的python环境运行kivy是没有问题的&#xff0c;就是在使用conda时发生了错误&#xff0c;去网上寻找报错原因&#xff0c;却一直没有头绪&#xff08;这个问题有诸多问题导致的&#xff0c;不敢说用我的这个方法100%能好&#xff09; 1…...

1-1二分查找

二分查找 1 基础版1.1 算法描述1.2 算法流程图1.3 算法实现1.3.1 Java实现 2 改动版2.1 算法描述2.2 算法流程图2.3 算法实现2.3.1 Java实现 2.4 改进点分析2.4.1 区间定义差异2.4.2 核心改进原理2.4.3 数学等价性证明 3 平衡版3.1 算法描述3.2 算法流程图3.3 算法实现3.3.1 Ja…...

【如何掌握CSP-J 信奥赛中的深搜算法】

CSP-J 信奥赛中的深搜&#xff08;深度优先搜索&#xff09;算法是一个重要知识点&#xff0c;以下是一些学习深搜算法的建议&#xff1a; 理解基础概念 定义与原理&#xff1a;深度优先搜索是一种用于遍历或搜索图、树等数据结构的算法。它从起始节点开始&#xff0c;沿着一条…...

Unity笔试常考

线程同步的几种方式 1.信号量pv操作 2.互斥加锁 3.条件变量 五层网络协议指的是哪五层 1.应用层 2.运输层 3.网络层 4.链路层 5.物理层 TCP和UDP区别 tcp 面向连接&#xff0c;保证发送顺序&#xff0c;速度慢&#xff0c;必须在线&#xff0c;三次握手&#xff0c;4次挥手…...

知识图谱智能应用系统:基于人工智能的知识提取架构

在知识图谱智能应用系统中,知识提取是将非结构化数据(如文本、文档)转化为结构化知识的关键步骤。通过人工智能技术,系统能够自动识别文本中的实体、关系、属性和事件,并将其转化为可用于知识图谱构建的三元组数据。以下是对知识提取架构的详细描述,包括环境准备、数据标…...

Qt:Qt基础介绍

目录 Qt背景介绍 什么是Qt Qt的发展史 Qt支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的成功案例 Qt的发展前景及就业分析 Qt背景介绍 什么是Qt Qt是⼀个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是完全面向…...

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置 本地版部署deepSeek R1 可以参考文章 3分钟教你搭建属于自己的本地大模型 DeepSeek R1 Ollama 是一个开源工具&#xff0c;旨在帮助用户轻松在本地计算机上运行、部署和管理大型语言模型&#xff08;LLMs&#xff09;…...