基于EasyExcel实现通用版一对一、一对多、多层嵌套结构数据导出并支持自动合并单元格

news2025/1/12 1:26:22

接口功能

        通用

        支持一对一数据结构导出

        支持一对多数据结构导出

        支持多层嵌套数据结构导出

        支持单元格自动合并

原文来自:https://blog.csdn.net/qq_40980205/article/details/136564176

新增及修复

基于我自己的使用场景,新增并能修复一下功能:

  • 修复了只有单条一对一数据无法导出问题;
  • 修复了多层嵌套数据结构中存在某个嵌套集合为空无法导出问题
  • 修复了只有一条一对多数据导出时报错问题;

完整源码解析

  • @MyExcelProperty 作用到导出字段上面,表示这是Excel的一列
  • @MyExcelCollection 表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示
  • @MyExcelEntity 表示一个还存在嵌套关系的导出实体

property为基本数据类型的注解类

package com.example.excel.annotation;


import java.lang.annotation.*;

@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExcelProperty {
    String name() default "";

    int index() default Integer.MAX_VALUE;

}

property为集合的注解类

package com.example.excel.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExcelCollection {
    String name();
}

property为实体对象的注解类

package com.example.excel.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExcelEntity {
    String name() default "";
}

导出参数类

package com.example.excel.excel;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class ExportParam {
    private String fieldName;
    private String columnName;
    private Integer order;
}

导出实体类

package com.example.excel.excel;

import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class ExportEntity {

    /**
     * 导出参数
     */
    private List<ExportParam> exportParams;

    /**
     * 平铺后的数据
     */
    private List<Map<String,Object>> data;

    /**
     * 每列的合并行数组
     */
    private Map<String, List<Integer[]>> mergeRowMap;
}

导出工具类

package com.example.excel.excel;

import com.alibaba.excel.EasyExcel;
import com.example.excel.annotation.MyExcelCollection;
import com.example.excel.annotation.MyExcelEntity;
import com.example.excel.annotation.MyExcelProperty;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

public class MyExcelUtil {
    private final String DELIMITER = ".";

    /**
     * @param sourceData 导出数据源
     * @param fileName   导出文件名
     * @param response
     */
    public void exportData(Collection<Object> sourceData, String sheetName, HttpServletResponse response) {
        if (CollectionUtils.isEmpty(sourceData)) return;
        ExportEntity exportEntity = flattenObject(sourceData);
        List<String> columnList = exportEntity.getExportParams().stream()
                .sorted(Comparator.comparing(ExportParam::getOrder))
                .map(ExportParam::getFieldName)
                .collect(Collectors.toList());
        List<List<Object>> exportDataList = new ArrayList<>();
        //导出数据集
        for (Map<String, Object> objectMap : exportEntity.getData()) {
            List<Object> data = new ArrayList<>();
            columnList.stream().forEach(columnName -> {
                data.add(objectMap.get(columnName));
            });
            exportDataList.add(data);
        }

        List<List<String>> headList = columnList.stream().map(Lists::newArrayList).collect(Collectors.toList());
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            EasyExcel.write(response.getOutputStream()).sheet(sheetName)
                    .head(headList)
                    .registerWriteHandler(new MergeStrategy(exportEntity))
                    .registerWriteHandler(new HeadHandler(exportEntity))
                    .doWrite(exportDataList);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public ExportEntity flattenObject(Collection<Object> sourceData) {
        Map<String, List<Integer[]>> mergeMap = new HashMap<>();
        Class aClass = sourceData.stream().findFirst().get().getClass();
        List<ExportParam> exportParams = getAllExportField(aClass, "");
        Preconditions.checkArgument(!exportParams.isEmpty(), "export field not found !");
        List<Map<String, Object>> target = new ArrayList<>();
        Integer startRow = 0;
        for (Object sourceDataLine : sourceData) {
            List<Map<String, Object>> flatData = flattenObject(sourceDataLine, "", startRow, mergeMap);
            startRow += flatData.size();
            target.addAll(flatData);
        }
        ExportEntity exportEntity = new ExportEntity();
        exportEntity.setExportParams(exportParams);
        exportEntity.setData(target);
        exportEntity.setMergeRowMap(mergeMap);
        return exportEntity;
    }


    /**
     * @param data     数据行
     * @param preNode  上一层级字段名
     * @param startRow 当前数据行所在行数
     * @param mergeMap 各字段合并行数组
     * @return
     */
    private List<Map<String, Object>> flattenObject(Object data, String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
        List<Map<String, Object>> flatList = new ArrayList<>();
        if (null == data) return flatList;

        Class<?> aClass = data.getClass();
        //获取不为null的excel导出字段
        List<Field> collectionFields = new ArrayList<>();
        List<Field> entityFields = new ArrayList<>();
        List<Field> propertyFields = new ArrayList<>();
        for (Field field : aClass.getDeclaredFields()) {
            Object value = getFieldValue(field, data);
            if (null == value) continue;
            if (isCollection(field)) {
                collectionFields.add(field);
            } else if (isEntity(field)) {
                entityFields.add(field);
            } else if (isProperty(field)) {
                propertyFields.add(field);
            }
        }

        List<Map<String, Object>> entityFlatData = flattenEntityFields(entityFields, data, preNode, startRow, mergeMap);
        List<List<Map<String, Object>>> collectionFlatData = flattenCollectionFields(collectionFields, data, preNode, startRow, mergeMap);
        Map<String, Object> objectMap = Collections.emptyMap();
        if (collectionFields.isEmpty() && entityFields.isEmpty()) {
            objectMap = flattenPropertyFields(propertyFields, data, preNode);
        }
        if (collectionFlatData.isEmpty() && entityFlatData.isEmpty()) {
            objectMap = flattenPropertyFields(propertyFields, data, preNode);
        }
        if (!objectMap.isEmpty()) {
            flatList.add(objectMap);
        }

        //当前层级所有平铺列表
        List<List<Map<String, Object>>> allFlatData = Lists.newArrayList();
        if (!entityFlatData.isEmpty()) {
            allFlatData.add(entityFlatData);
        }
        if (!collectionFlatData.isEmpty()) {
            allFlatData.addAll(collectionFlatData);
        }

        List<Map<String, Object>> mergeList = mergeList(data, allFlatData, propertyFields, preNode, startRow, mergeMap);
        if (!mergeList.isEmpty()) {
            flatList.addAll(mergeList);
        }

        return flatList;
    }


    /**
     * @param preNode   上一层级字段名
     * @param fieldName 当前字段名
     * @return
     */
    private String buildPreNode(String preNode, String fieldName) {
        StringBuffer sb = new StringBuffer();
        return StringUtils.isEmpty(preNode) ? fieldName : sb.append(preNode).append(DELIMITER).append(fieldName).toString();
    }


    /**
     * @param mergeFields   需要合并的字段
     * @param preNode       上一层级字段名
     * @param mergeStartRow 合并开始行
     * @param mergeEndRow   合并结束行
     * @param mergeMap      各字段合并行数组
     */
    private void buildMerge(List<Field> mergeFields, String preNode, Integer mergeStartRow, Integer mergeEndRow, Map<String, List<Integer[]>> mergeMap) {
        for (int k = 0; k < mergeFields.size(); k++) {
            String fieldName = buildPreNode(preNode, mergeFields.get(k).getName());
            List<Integer[]> list = mergeMap.get(fieldName);
            if (null == list) {
                mergeMap.put(fieldName, new ArrayList<>());
            }

            Integer[] rowInterval = new Integer[2];
            //合并开始行
            rowInterval[0] = mergeStartRow;
            //合并结束行
            rowInterval[1] = mergeEndRow;
            mergeMap.get(fieldName).add(rowInterval);
        }
    }

    private List<Map<String, Object>> flattenEntityFields(List<Field> entityFields, Object data, String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
        List<Map<String, Object>> entityFlatData = Lists.newArrayList();
        for (Field entityField : entityFields) {
            entityFlatData = flattenObject(getFieldValue(entityField, data), buildPreNode(preNode, entityField.getName()), startRow, mergeMap);
        }
        return entityFlatData;
    }

    private List<List<Map<String, Object>>> flattenCollectionFields(List<Field> collectionFields, Object data, String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
        List<List<Map<String, Object>>> collectionFlatData = Lists.newArrayList();
        for (Field collectionField : collectionFields) {
            Collection collectionValue = (Collection) getFieldValue(collectionField, data);

            //当前集合字段平铺而成的数据列表
            List<Map<String, Object>> collectionObjectValue = new ArrayList<>();
            //间隔行数
            Integer row = 0;
            for (Object value : collectionValue) {
                List<Map<String, Object>> flatData = flattenObject(value, buildPreNode(preNode, collectionField.getName()), startRow + row, mergeMap);
                if (!flatData.isEmpty()) {
                    collectionObjectValue.addAll(flatData);
                    //下条数据的起始间隔行
                    row += flatData.size();
                }
            }
            if (!collectionObjectValue.isEmpty()) {
                collectionFlatData.add(collectionObjectValue);
            }
        }
        return collectionFlatData;
    }

    private Map<String, Object> flattenPropertyFields(List<Field> propertyFields, Object data, String preNode) {
        Map<String, Object> flatMap = new HashMap<>();
        for (Field field : propertyFields) {
            flatMap.put(buildPreNode(preNode, field.getName()), getFieldValue(field, data));
        }
        return flatMap;
    }


    private List<Map<String, Object>> mergeList(Object data, List<List<Map<String, Object>>> allFlatData, List<Field> propertyFields, String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
        List<Map<String, Object>> flatList = new ArrayList<>();

        //当前层级下多个集合字段的最大长度即为当前层级其他字段的合并行数
        Integer maxSize = 0;
        for (List<Map<String, Object>> list : allFlatData) {
            maxSize = Math.max(maxSize, list.size());
        }

        //记录集合同层级其他字段的合并行数
        if (maxSize > 0) {
            buildMerge(propertyFields, preNode, startRow, startRow + maxSize, mergeMap);
        }

        //需要合并
        if (maxSize > 1) {
            //重新构建平铺的list
            List<Map<String, Object>> mergeFlatData = new ArrayList<>(maxSize);
            for (int i = 0; i < maxSize; i++) {
                mergeFlatData.add(new HashMap<>());
            }

            for (List<Map<String, Object>> flatData : allFlatData) {
                for (int i = 0; i < flatData.size(); i++) {
                    Map<String, Object> flatMap = new HashMap<>();
                    flatMap.putAll(flatData.get(i));

                    //添加同层级字段值
                    if (CollectionUtils.isNotEmpty(propertyFields)) {
                        for (Field field : propertyFields) {
                            flatMap.put(buildPreNode(preNode, field.getName()), getFieldValue(field, data));
                        }
                    }
                    mergeFlatData.get(i).putAll(flatMap);
                }
            }
            flatList.addAll(mergeFlatData);
        } else {
            for (List<Map<String, Object>> list : allFlatData) {
                flatList.addAll(list);
            }
        }
        return flatList;
    }


    /**
     * @param clazz   导出类
     * @param preNode 上一层级字段名
     * @return
     */
    private List<ExportParam> getAllExportField(Class<?> clazz, String preNode) {
        List<ExportParam> exportFields = new ArrayList<>();
        for (Field declaredField : clazz.getDeclaredFields()) {
            MyExcelProperty myExcelProperty = declaredField.getDeclaredAnnotation(MyExcelProperty.class);
            MyExcelEntity myExcelEntity = declaredField.getDeclaredAnnotation(MyExcelEntity.class);
            MyExcelCollection myExcelCollection = declaredField.getDeclaredAnnotation(MyExcelCollection.class);
            String fieldName = buildPreNode(preNode, declaredField.getName());
            if (null != myExcelProperty) {
                ExportParam exportParam = new ExportParam()
                        .setFieldName(fieldName)
                        .setColumnName(myExcelProperty.name())
                        .setOrder(myExcelProperty.index());
                exportFields.add(exportParam);
            } else if (null != myExcelEntity) {
                exportFields.addAll(getAllExportField(declaredField.getType(), fieldName));
            } else if (null != myExcelCollection) {
                boolean isCollection = Collection.class.isAssignableFrom(declaredField.getType());
                if (!isCollection) continue;
                ParameterizedType pt = (ParameterizedType) declaredField.getGenericType();
                Class<?> clz = (Class<?>) pt.getActualTypeArguments()[0];
                exportFields.addAll(getAllExportField(clz, fieldName));
            }
        }
        return exportFields;
    }

    private boolean isProperty(Field field) {
        return null != field.getDeclaredAnnotation(MyExcelProperty.class) ? true : false;
    }


    private boolean isEntity(Field field) {
        return null != field.getDeclaredAnnotation(MyExcelEntity.class) ? true : false;
    }

    private boolean isCollection(Field field) {
        boolean isCollection = Collection.class.isAssignableFrom(field.getType());
        return isCollection && null != field.getAnnotation(MyExcelCollection.class) ? true : false;
    }

    private Object getFieldValue(Field field, Object sourceObject) {
        Object fieldValue;
        try {
            field.setAccessible(true);
            fieldValue = field.get(sourceObject);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return fieldValue;
    }

}

单元格合并策略


```java
package com.example.excel.excel;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.util.CollectionUtils;

import java.util.List;

public class MergeStrategy extends AbstractMergeStrategy {

    private ExportEntity exportEntity;

    public MergeStrategy(ExportEntity exportEntity) {
        this.exportEntity = exportEntity;
    }


    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
        //mergeMap需要加上head的行数
        String fieldName = head.getHeadNameList().get(0);
        List<Integer[]> mergeRow = exportEntity.getMergeRowMap().get(fieldName);
        if (CollectionUtils.isEmpty(mergeRow)) return;
        int currentColumnIndex = cell.getColumnIndex();
        int currentRowIndex = cell.getRowIndex();
        //表头所占行数
        int headHeight = head.getHeadNameList().size();
        for (int i = 0; i < mergeRow.size(); i++) {
            if (currentRowIndex == mergeRow.get(i)[1] + headHeight - 1) {
                sheet.addMergedRegion(new CellRangeAddress(mergeRow.get(i)[0] + headHeight, mergeRow.get(i)[1] + headHeight - 1,
                        currentColumnIndex, currentColumnIndex));
            }
        }

    }

}

表头导出处理


```java
package com.example.excel.excel;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class HeadHandler implements CellWriteHandler {

    private ExportEntity exportEntity;


    public HeadHandler(ExportEntity exportEntity) {
        this.exportEntity = exportEntity;
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                 List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (!isHead) return;
        List<String> headNameList = head.getHeadNameList();
        Map<String, String> collect = exportEntity.getExportParams().stream().collect(Collectors.toMap(ExportParam::getFieldName, ExportParam::getColumnName));
        cell.setCellValue(collect.get(headNameList.get(0)));
    }
}

导出示例

        被导出实体

 public class CompanyConfigEnntity{
    @MyExcelProperty(name = "公司名称")
    private String companyName;

    @MyExcelCollection(name = "线路配置")
    private List<LineConfigEntity> lineConfigs;
 }

 public class LineConfigEntity{
    @MyExcelProperty(name = "线路号")
    private String lineNumber;

    @MyExcelCollection(name = "消息配置")
    private List<MsgConfigEntity> msgConfigs;
 }

public class MsgConfigEntity{
    @MyExcelProperty(name = "消息类型")
    private String msgType;

    @MyExcelCollection(name = "子类型配置")
    private List<SubTypeConfigEntity> subTypeConfigs;
 }

public class SubTypeConfigEntity{
    @MyExcelProperty(name = "子类型")
    private String subType;

    @MyExcelCollection(name = "集中站配置")
    private List<RtuConfigEntity> rtuConfigs;
 }
public class RtuConfigEntity{
    @MyExcelProperty(name = "集中站号")
    private String rtuNumber;

 }

接口调用

@RestController
@ReqeutMapping("/companyConfig/companyConfig")
public class CompanyConfigController{
    @AutoWired
    private CompanyConfigService companyConfigService;
    
    @PostMappinng("/export")
    public void expooort(HttpServletResponse response){
        List<CompanyConfigEnityt> list = companyConfigService.queryAll();
        new MyExcelUtil().exportData(list, "公司配置内容数据", resposne);
    }
}

效果展示

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

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

相关文章

【数据库】一、数据库系统概述

文章目录 一、数据库系统概述1 基本概念2 现实世界的信息化过程3 数据库系统内部体系结构4 数据库系统外部体系结构5 数据管理方式 一、数据库系统概述 1 基本概念 数据&#xff1a;描述事物的符号记录 数据库&#xff08;DB&#xff09;&#xff1a;长期存储在计算机内的、…

网络安全建设方案,信息安全风险评估报告,信息安全检测文档(Word原件完整版)

一、概述 1.1工作方法 1.2评估依据 1.3评估范围 1.4评估方法 1.5基本信息 二、资产分析 2.1 信息资产识别概述 2.2 信息资产识别 三、评估说明 3.1无线网络安全检查项目评估 3.2无线网络与系统安全评估 3.3 ip管理与补丁管理 3.4防火墙 四、威胁细…

数据分析工作流

数据分析工作流 1.流程 数据产生阶段 业务系统生成数据&#xff1a;在各种业务场景下&#xff0c;如用户在电商平台上进行购物&#xff08;产生订单信息、浏览记录等&#xff09;、在金融系统中进行交易&#xff08;产生交易流水、账户余额变动等&#xff09;或者在企业内部的…

【Go】:图片上添加水印的全面指南——从基础到高级特性

前言 在数字内容日益重要的今天&#xff0c;保护版权和标识来源变得关键。为图片添加水印有助于声明所有权、提升品牌认知度&#xff0c;并防止未经授权的使用。本文将介绍如何用Go语言实现图片水印&#xff0c;包括静态图片和带旋转、倾斜效果的文字水印&#xff0c;帮助您有…

PyQt5 UI混合开发,控件的提升

PromoteLabelTest.py 提升的类 import sys from PyQt5.QtWidgets import QApplication, QWidget,QVBoxLayout,QTextEdit,QPushButton,QHBoxLayout,QFileDialog,QLabelclass PromoteLabel(QLabel):def __init__(self,parent None):super().__init__(parent)self.setText("…

CI/CD 流水线

CI/CD 流水线 CI 与 CD 的边界CI 持续集成CD&#xff08;持续交付/持续部署&#xff09;自动化流程示例&#xff1a; Jenkins 引入到 CI/CD 流程在本地或服务器上安装 Jenkins。配置 Jenkins 环境流程设计CI 阶段&#xff1a;Jenkins 流水线实现CD 阶段&#xff1a;Jenkins 流水…

ROS核心概念解析:从Node到Master,再到roslaunch的全面指南

Node 在ROS中&#xff0c;最小的进程单元就是节点&#xff08;node&#xff09;。一个软件包里可以有多个可执行文件&#xff0c;可执行文件在运行之后就成了一个进程(process)&#xff0c;这个进程在ROS中就叫做节点。 从程序角度来说&#xff0c;node就是一个可执行文件&…

深入Android架构(从线程到AIDL)_22 IPC的Proxy-Stub设计模式04

目录 5、 谁来写Proxy及Stub类呢? 如何考虑人的分工 IA接口知识取得的难题 在编程上&#xff0c;有什么技术可以实现这个方法&#xff1f; 范例 5、 谁来写Proxy及Stub类呢? -- 强龙提供AIDL工具&#xff0c;给地头蛇产出Proxy和Stub类 如何考虑人的分工 由框架开发者…

风水算命系统架构与功能分析

系统架构 服务端&#xff1a;Java&#xff08;最低JDK1.8&#xff0c;支持JDK11以及JDK17&#xff09;数据库&#xff1a;MySQL数据库&#xff08;标配5.7版本&#xff0c;支持MySQL8&#xff09;ORM框架&#xff1a;Mybatis&#xff08;集成通用tk-mapper&#xff0c;支持myb…

551 灌溉

常规解法&#xff1a; #include<bits/stdc.h> using namespace std; int n,m,k,t; const int N105; bool a[N][N],b[N][N]; int cnt; //设置滚动数组来存贮当前和下一状态的条件 //处理传播扩散问题非常有效int main() {cin>>n>>m>>t;for(int i1;i&l…

HDFS编程 - 使用HDFS Java API进行文件操作

文章目录 前言一、创建hdfs-demo项目1. 在idea上创建maven项目2. 导入hadoop相关依赖 二、常用 HDFS Java API1. 简介2. 获取文件系统实例3. 创建目录4. 创建文件4.1 创建文件并写入数据4.2 创建新空白文件 5. 查看文件内容6. 查看目录下的文件或目录信息6.1 查看指定目录下的文…

Java面试题~~

深拷贝和浅拷贝区别了解吗?什么是引用拷贝? 关于深拷贝和浅拷贝区别&#xff0c;我这里先给结论&#xff1a; 浅拷贝&#xff1a;浅拷贝会在堆上创建一个新的对象&#xff08;区别于引用拷贝的一点&#xff09;&#xff0c;不过&#xff0c;如果原对象内部的属性是引用类型的…

el-table 自定义表头颜色

第一种方法&#xff1a;计算属性 <template><div><el-table:data"formData.detail"border stripehighlight-current-row:cell-style"{ text-align: center }":header-cell-style"headerCellStyle"><el-table-column fixed…

MySQL笔记大总结20250108

Day2 1.where (1)关系运算符 select * from info where id>1; select * from info where id1; select * from info where id>1; select * from info where id!1;(2)逻辑运算符 select * from info where name"吴佩奇" and age19; select * from info wh…

精选2款.NET开源的博客系统

前言 博客系统是一个便于用户创建、管理和分享博客内容的在线平台&#xff0c;今天大姚给大家分享2款.NET开源的博客系统。 StarBlog StarBlog是一个支持Markdown导入的开源博客系统&#xff0c;后端基于最新的.Net6和Asp.Net Core框架&#xff0c;遵循RESTFul接口规范&…

SEO内容优化:如何通过用户需求赢得搜索引擎青睐?

在谷歌SEO优化中&#xff0c;内容一直是最重要的因素之一。但要想让内容真正发挥作用&#xff0c;关键在于满足用户需求&#xff0c;而不是简单地堆砌关键词。谷歌的算法越来越智能化&#xff0c;更注重用户体验和内容的实用性。 了解目标用户的需求。通过工具如Google Trends…

Spring——自动装配

假设一个场景&#xff1a; 一个人&#xff08;Person&#xff09;有一条狗&#xff08;Dog&#xff09;和一只猫(Cat)&#xff0c;狗和猫都会叫&#xff0c;狗叫是“汪汪”&#xff0c;猫叫是“喵喵”&#xff0c;同时人还有一个自己的名字。 将上述场景 抽象出三个实体类&…

计算机网络(三)——局域网和广域网

一、局域网 特点&#xff1a;覆盖较小的地理范围&#xff1b;具有较低的时延和误码率&#xff1b;使用双绞线、同轴电缆、光纤传输&#xff0c;传输效率高&#xff1b;局域网内各节点之间采用以帧为单位的数据传输&#xff1b;支持单播、广播和多播&#xff08;单播指点对点通信…

错误的类文件: *** 类文件具有错误的版本 61.0, 应为 52.0 请删除该文件或确保该文件位于正确的类路径子目录中

一、问题 用maven对一个开源项目打包时&#xff0c;遇到了“错误的类文件: *** 类文件具有错误的版本 61.0, 应为 52.0 请删除该文件或确保该文件位于正确的类路径子目录中。”&#xff1a; 二、原因 原因是当前java环境是Java 8&#xff08;版本52.0&#xff09;&#xff0c;但…

【大模型入门指南 07】量化技术浅析

【大模型入门指南】系列文章&#xff1a; 【大模型入门指南 01】深度学习入门【大模型入门指南 02】LLM大模型基础知识【大模型入门指南 03】提示词工程【大模型入门指南 04】Transformer结构【大模型入门指南 05】LLM技术选型【大模型入门指南 06】LLM数据预处理【大模型入门…