EasyExcel 导出合并层级单元格

news2025/2/11 9:46:55

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;
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean isHead) {

        // 头不参与合并
        if (isHead) {
            return;
        }

        // 初始化 sheet
        if (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);
            }
        }
    }

    @Override
    public 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 {

    /**
     * 案例一
     */
    @Test
    public 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();
    }

    /**
     * 案例二
     */
    @Test
    public 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();
    }


    /**
     * 案例三
     */
    @Test
    public 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

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

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

相关文章

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

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

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根据提示信…

使用LLaMA Factory踩坑记录

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

电路研究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为低电平时芯片“截止”即将输出状态“锁存”…

B树详解及其C语言实现

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

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 领域的重大变革,已…

【如何掌握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;…

让office集成deepseek,支持office和WPS办公软件!(体验感受)

导读 AIGC:AIGC是一种新的人工智能技术&#xff0c;它的全称是Artificial Intelligence Generative Content&#xff0c;即人工智能生成内容。 它是一种基于机器学习和自然语言处理的技术&#xff0c;能够自动产生文本、图像、音频等多种类型的内容。这些内容可以是新闻文章、…