【EasyExcel实践】万能导出,一个接口导出多张表以及任意字段(可指定字段顺序)-简化升级版

文章目录

  • 前言
  • 正文
    • 一、项目简介
    • 二、核心代码
      • 2.1 pom.xml 依赖配置
      • 2.2 ExcelHeadMapFactory
      • 2.3 ExcelDataLinkedHashMap
      • 2.4 自定义注解 ExcelExportBean
      • 2.5 自定义注解 ExcelColumnTitle
      • 2.6 建造器接口 Builder
      • 2.7 表格工具类 ExcelUtils
      • 2.8 GsonUtil
      • 2.9 模版类 ExportDynamicColumnTemplate
      • 2.10 模版建造器 ExportDynamicColumnTemplateBuilder
    • 三、控制器&调试Bean的定义
      • 3.1 StudentDemo
      • 3.2 NameAndFactoryDemo
      • 3.3 控制器 ExcelDemoController
    • 四、启动类
    • 五、测试

前言

关于万能导出,前一阵子写过一个,功能是实现了的。
就是在使用时,感觉如果需要导出的页面比较多,那就会出现比较多的重复代码。就想着优化+简化一下,能够更方便的使用。

原版代码仓库和这一版的代码仓库相同:
https://gitee.com/fengsoshuai/excel-demo

原版中额外增加了转换器,枚举转换等功能,但是总觉得会让整体功能变复杂,所以在这一版中就去掉了。如果有兴趣看看的话,可以切换到master分支查看。

简化升级的这一版的代码分支是:simple-dynamic-column-export

正文

一、项目简介

本次简化升级,本着对使用者友好的目的去实现的。

另外,本文会粘贴全部代码!!

在这里插入图片描述

在使用上的简化体现在真正导出时,只需要几行代码,就能实现功能。可以让你更加专注于业务参数的组装。而且代码比较简洁。
举个例子:

@GetMapping("/exportDy")
public String exportDy(@RequestParam("table") List<String> table, HttpServletResponse response) throws IOException {

    // 构造导出模版
    ExportDynamicColumnTemplate<StudentDemo> dynamicColumnTemplate = new ExportDynamicColumnTemplateBuilder<>(StudentDemo.class)
            .columnNameList(table)
            .build();
    // 制造假数据
    dynamicColumnTemplate.appendExportDataList(studentDemos());

    // 转换为excel的字节数组
    byte[] bytes = dynamicColumnTemplate.toExcelByteArray();

    // 响应到web
    String fileName = System.currentTimeMillis() + ".xlsx";
    response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    response.setContentType("application/x-msdownload");
    response.setCharacterEncoding("utf-8");
    response.getOutputStream().write(bytes);
    response.getOutputStream().flush();

    return "success";
}

简化后的代码,只需要3步操作:

  1. 根据导出bean,使用建造器生成模版实例;
  2. 给模版实例中填充业务数据;
  3. 填充完数据后,将数据转换为excel格式的字节数组;

当我们抡完这三板斧之后,剩下的就是将字节数组响应到web导出。

二、核心代码

2.1 pom.xml 依赖配置

<dependencies>
  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.2</version>
    </dependency>


    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.2.11</version>

        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>

2.2 ExcelHeadMapFactory

表格头映射工厂,提供了注册表格头和获取表格头配置的静态方法。

package org.feng.export.factory;

import org.feng.export.system.ExcelColumnTitle;
import org.feng.export.system.ExcelExportBean;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * 表格头映射工厂
 *
 * @author feng
 */
public class ExcelHeadMapFactory {
    /**
     * 全局表头名映射,key内部变量的变量名和中文名映射为value
     */
    private static final Map<Class<?>, Map<String, String>> HEAD_NAME_MAP = new HashMap<>();


    public static void addHeadClass(Class<?> headClass) {
        if (!headClass.isAnnotationPresent(ExcelExportBean.class)) {
            throw new RuntimeException("HeadClass必须使用注解ExcelExportBean");
        }
        HEAD_NAME_MAP.put(headClass, mapToPrepareHead(headClass));
    }

    public static Map<String, String> getHeadMap(Class<?> headClass) {
        return HEAD_NAME_MAP.get(headClass);
    }

    public static boolean containsHeadClass(Class<?> headClass) {
        return HEAD_NAME_MAP.containsKey(headClass);
    }

    private static Map<String, String> mapToPrepareHead(Class<?> excelHeadClass) {
        Map<String, String> namedMap = new HashMap<>();
        Field[] declaredFields = excelHeadClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            boolean annotationPresent = declaredField.isAnnotationPresent(ExcelColumnTitle.class);
            if (annotationPresent) {
                ExcelColumnTitle excelProperty = declaredField.getAnnotation(ExcelColumnTitle.class);
                String chineseFieldName = excelProperty.value();
                // 保存字段名和中文变量名
                namedMap.put(declaredField.getName(), chineseFieldName);
            }
        }
        return namedMap;
    }
}

2.3 ExcelDataLinkedHashMap

自定义LinkedHashMap,用于实现字段的顺序,以及动态字段展示。
只提供put 和 putAll方法的实现。想要别的put方法的话,读者请自行实现。

package org.feng.export.system;

import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 表格数据专用的map,带顺序,而且初始化的时候,依据指定的表头变量字段名确定导出数据的顺序
 *
 * @author feng
 */
public class ExcelDataLinkedHashMap extends LinkedHashMap<String, Object> {

    private static final long serialVersionUID = -8554095999151235982L;

    /**
     * 头部字段名缓存
     */
    private final Set<String> headColumnNamesCache;

    /**
     * 指定被忽略的列名
     */
    private final Set<String> ignoreColumnSet;

    /**
     * ExcelDataLinkedHashMap构造器
     *
     * @param headColumnNames 表头字段变量名,例如:[name,studentNo,age,className]
     */
    public ExcelDataLinkedHashMap(List<Object> headColumnNames, String... ignoreColumns) {
        if (ignoreColumns != null && ignoreColumns.length > 0) {
            ignoreColumnSet = new HashSet<>(Arrays.asList(ignoreColumns));
        } else {
            ignoreColumnSet = Collections.emptySet();
        }
        // 字段名去重
        List<String> headColumnStringNames = headColumnNames.stream().distinct().map(Object::toString).collect(Collectors.toList());
        // 构建字段名缓存
        this.headColumnNamesCache = new HashSet<>(headColumnStringNames);
        // 指定列数据排列顺序
        for (String headColumnName : headColumnStringNames) {
            this.put(headColumnName, null);
        }
    }

    @Override
    public Object put(String key, Object value) {
        // 只保存字段名缓存中的key以及value
        if (headColumnNamesCache.contains(key)) {
            // 设置了被忽略的列,进行判断处理,匹配到了就不保存
            if (!CollectionUtils.isEmpty(ignoreColumnSet) && ignoreColumnSet.contains(key)) {
                return null;
            }
            return super.put(key, value);
        }
        return null;
    }

    @Override
    public void putAll(Map<? extends String, ?> map) {
        map.forEach(this::put);
    }
}

2.4 自定义注解 ExcelExportBean

package org.feng.export.system;

import java.lang.annotation.*;

/**
 * 指定表格导出的bean
 *
 * @author feng
 */
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelExportBean {
}

2.5 自定义注解 ExcelColumnTitle

package org.feng.export.system;

import java.lang.annotation.*;

/**
 * 列名标题注解,标注列的标题
 *
 * @author feng
 */
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumnTitle {
    String value();
}

2.6 建造器接口 Builder

package org.feng.export.util;

/**
 * 建造器接口
 *
 * @author feng
 */
public interface Builder<T> {
    T build();
}

2.7 表格工具类 ExcelUtils

提供将数据写入表格的静态方法。

package org.feng.export.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * excel工具类
 *
 * @author feng
 */
public class ExcelUtils {

    /**
     * 导出数据(单sheet)
     *
     * @param exportData key 是sheet名称,value是每个sheet里面的数据,支持自定义表头
     */
    public static byte[] easyOut(List<Map<String, Object>> exportData) {
        return easyOut("Sheet", exportData);
    }

    /**
     * 导出数据(单sheet)
     *
     * @param exportData key 是sheet名称,value是每个sheet里面的数据,支持自定义表头
     */
    public static byte[] easyOut(String sheetName, List<Map<String, Object>> exportData) {
        return easyOut(Collections.singletonMap(sheetName, exportData));
    }

    /**
     * 导出数据(多sheet)
     *
     * @param exportData key 是sheet名称,value是每个sheet里面的数据,可以自定义
     */
    public static byte[] easyOut(Map<String, List<Map<String, Object>>> exportData) {
        // 导出数据
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        com.alibaba.excel.ExcelWriter excelWriter = EasyExcel.write(out).build();
        int i = 0;
        for (Map.Entry<String, List<Map<String, Object>>> entry : exportData.entrySet()) {
            WriteSheet writeSheet = EasyExcel.writerSheet(i, entry.getKey()).head(head(entry.getValue().get(0))).build();
            i++;
            excelWriter.write(data(entry.getValue(), true), writeSheet);
        }
        excelWriter.finish();

        return out.toByteArray();
    }

    private static List<List<String>> head(Map<String, Object> cellData) {
        List<List<String>> head = new ArrayList<>();
        for (String key : cellData.keySet()) {
            head.add(Collections.singletonList(key));
        }
        return head;
    }

    private static List<List<Object>> data(List<Map<String, Object>> sheetData, boolean skipHead) {
        List<List<Object>> data = new ArrayList<>();
        for (int i = 0; i < sheetData.size(); i++) {
            if (i == 0 && skipHead) {
                continue;
            }
            data.add(new ArrayList<>(sheetData.get(i).values()));
        }
        return data;
    }
}

2.8 GsonUtil

gson工具类,提供json处理、转换的静态方法。

package org.feng.export.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import java.util.Map;

/**
 * gson工具类
 *
 * @version v1.0
 * @author: fengjinsong
 * @date: 2023年08月26日 15时00分
 */
public class GsonUtil {
    /**
     * 不会序列化空字段的Gson对象
     */
    private static final Gson GSON = new GsonBuilder()
            .create();
    /**
     * 可以序列化空字段的Gson对象
     */
    private static final Gson GSON_WITH_NULL = new GsonBuilder()
            .serializeNulls().create();

    private static final Gson GSON_WITH_DISABLE_HTML_ESCAPING = new GsonBuilder()
            .disableHtmlEscaping().create();

    public static Gson gson() {
        return GSON;
    }

    /**
     * 转换对象为json字符串(不会序列化空字段)
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJson(Object object) {
        return GSON.toJson(object);
    }

    /**
     * 转换对象为json字符串(可以序列化空字段)
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJsonWithNull(Object object) {
        return GSON_WITH_NULL.toJson(object);
    }

    /**
     * 转换对象为json字符串(禁止html转义)
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJsonWithDisableTtmlEscaping(Object object) {
        return GSON_WITH_DISABLE_HTML_ESCAPING.toJson(object);
    }

    public static <T> T fromJson(String jsonStr, Class<T> clazz) {
        return GSON.fromJson(jsonStr, clazz);
    }

    public static Map<String, String> toStringMap(String jsonStr) {
        return GSON.fromJson(jsonStr, new TypeToken<Map<String, String>>() {
        }.getType());
    }

    /**
     * 校验字符串是否是一个json格式
     * <br> 注意:{@code "{}"} 也是符合条件的json
     *
     * @param jsonStr 目标字符串
     * @return true表示目标是一个正确的json格式
     */
    public static boolean validateJson(String jsonStr) {
        JsonElement jsonElement;
        try {
            jsonElement = JsonParser.parseString(jsonStr);
        } catch (Exception e) {
            return false;
        }
        if (jsonElement == null) {
            return false;
        }
        return jsonElement.isJsonObject();
    }
}

2.9 模版类 ExportDynamicColumnTemplate

package org.feng.export;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.feng.export.factory.ExcelHeadMapFactory;
import org.feng.export.system.ExcelDataLinkedHashMap;
import org.feng.export.util.ExcelUtils;
import org.feng.export.util.GsonUtil;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 导出动态列模版
 *
 * @author feng
 */
@Slf4j
@Getter
public class ExportDynamicColumnTemplate<T> {

    /**
     * 导出的bean类型
     */
    private Class<T> exportBean;

    /**
     * 导出的数据
     */
    private List<Map<String, Object>> exportData;

    /**
     * 当前表头映射
     */
    private Map<String, Object> currentHeadMap;

    /**
     * 真正导出的列名
     */
    private List<Object> realExportColumnNameList;

    /**
     * 是否转换前打印导出数据(含表头)
     */
    @Setter
    private boolean logExportData;

    /**
     * 是否转换前打印实际的表头信息
     */
    @Setter
    private boolean logCurrentHead;

    ExportDynamicColumnTemplate(Class<T> exportBean, List<String> columnNameList) {
        this(exportBean, columnNameList, false, true);
    }

    ExportDynamicColumnTemplate(Class<T> exportBean, List<String> columnNameList, boolean logExportData, boolean logCurrentHead) {
        init(exportBean, columnNameList);
        this.logExportData = logExportData;
        this.logCurrentHead = logCurrentHead;
    }

    /**
     * 追加导出数据
     *
     * @param data          数据
     * @param ignoreColumns 设置忽略的列名
     */
    public void appendExportData(T data, String... ignoreColumns) {
        Objects.requireNonNull(data);
        ExcelDataLinkedHashMap dataMap = new ExcelDataLinkedHashMap(realExportColumnNameList, ignoreColumns);
        exportData.add(dataMap);
        String dataJson = GsonUtil.toJson(data);
        dataMap.putAll(GsonUtil.toStringMap(dataJson));
    }

    /**
     * 追加导出数据列表
     *
     * @param dataList      数据列表
     * @param ignoreColumns 设置忽略的列名
     */
    public void appendExportDataList(List<T> dataList, String... ignoreColumns) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        dataList.forEach(data -> appendExportData(data, ignoreColumns));
    }

    /**
     * 将当前需要导出的数据转换为表格的字节数组
     *
     * @return 字节数组
     */
    public byte[] toExcelByteArray() {
        return toExcelByteArray("Sheet");
    }

    /**
     * 将当前需要导出的数据转换为表格的字节数组
     *
     * @param sheetName 指定sheet名
     * @return 字节数组
     */
    public byte[] toExcelByteArray(String sheetName) {
        if (logExportData) {
            logExportData();
        }
        if (logCurrentHead) {
            log.info("导出数据表头信息:{}", currentHeadMap);
        }
        return ExcelUtils.easyOut(sheetName, exportData);
    }


    private void logExportData() {
        for (int i = 0; i < exportData.size(); i++) {
            Map<String, Object> data = exportData.get(i);
            log.info("导出数据[{}]:{}", i, data);
        }
    }

    /**
     * 初始化导出模版信息,主要是表头信息
     *
     * @param exportBean     导出bean的类型
     * @param columnNameList 需要导出的字段名列表
     */
    private void init(Class<T> exportBean, List<String> columnNameList) {
        this.exportBean = exportBean;
        exportData = new ArrayList<>();
        currentHeadMap = new LinkedHashMap<>();

        // 获取代码配置的表头信息
        if (!ExcelHeadMapFactory.containsHeadClass(exportBean)) {
            ExcelHeadMapFactory.addHeadClass(exportBean);
        }
        Map<String, String> headMap = ExcelHeadMapFactory.getHeadMap(exportBean);
        Assert.notEmpty(headMap, "表头不能为空,请检查exportBean的类型");

        // 初始化真正的表头信息,过滤无效配置或找不到的列名
        realExportColumnNameList = new ArrayList<>();
        for (String field : columnNameList) {
            String fieldChineseName = headMap.get(field);
            if (StringUtils.isEmpty(fieldChineseName)) {
                log.info("代码配置的导出表头不完整,不存在字段:{}", field);
            } else {
                currentHeadMap.put(fieldChineseName, field);
                realExportColumnNameList.add(field);
            }
        }

        // 记录当前的表头信息
        exportData.add(currentHeadMap);
    }
}

2.10 模版建造器 ExportDynamicColumnTemplateBuilder

用于生成模版实例。

package org.feng.export;

import lombok.AccessLevel;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.feng.export.system.ExcelExportBean;
import org.feng.export.util.Builder;
import org.springframework.util.Assert;

import java.util.List;

/**
 * 导出动态列模版建造器
 *
 * @author feng
 */
@Accessors(chain = true, fluent = true)
@Setter
public class ExportDynamicColumnTemplateBuilder<T> implements Builder<ExportDynamicColumnTemplate<T>> {

    /**
     * 字段名列表
     */
    private List<String> columnNameList;

    /**
     * 导出的bean类型
     */
    @Setter(AccessLevel.NONE)
    private final Class<T> exportBean;

    /**
     * 是否转换前打印导出数据(含表头)
     */
    private Boolean logExportData;
    /**
     * 是否转换前打印实际的表头信息
     */
    private Boolean logCurrentHead;

    public ExportDynamicColumnTemplateBuilder(Class<T> exportBean) {
        if (!exportBean.isAnnotationPresent(ExcelExportBean.class)) {
            throw new RuntimeException("ExportBean必须使用注解ExcelExportBean");
        }
        this.exportBean = exportBean;
    }

    @Override
    public ExportDynamicColumnTemplate<T> build() {
        check();

        ExportDynamicColumnTemplate<T> template = new ExportDynamicColumnTemplate<>(exportBean, columnNameList);
        if(logCurrentHead != null) {
            template.setLogCurrentHead(logCurrentHead);
        }
        if(logExportData != null) {
            template.setLogExportData(logExportData);
        }
        return template;
    }

    private void check() {
        Assert.notNull(exportBean, "导出的实例类型不能为空");
        Assert.notEmpty(columnNameList, "字段名列表不能为空");
    }
}

三、控制器&调试Bean的定义

这一部分是非核心代码,属于对核心代码使用的一种演示。读者可以按照对应的写法来实现功能。

注意:导出的bean定义,需要使用注解 ExcelExportBean,其中的字段需要使用注解ExcelColumnTitle

3.1 StudentDemo

package org.feng.headbean;

import org.feng.export.system.ExcelColumnTitle;
import lombok.Data;
import org.feng.export.system.ExcelExportBean;

/**
 * 学生demo导出bean
 *
 * @author feng
 */
@Data
@ExcelExportBean
public class StudentDemo {
    @ExcelColumnTitle("姓名")
    private String name;
    @ExcelColumnTitle("年龄")
    private String age;
    @ExcelColumnTitle("性别")
    private String sex;
    @ExcelColumnTitle("学号")
    private String studentNo;
    @ExcelColumnTitle("班级")
    private String className;
}

3.2 NameAndFactoryDemo

该类对本次演示无实际意义,保留着是因为,需要演示加载多个配置表头的实例时的写法。
具体的可以查看启动类ExcelDemoApplication中的内容。

package org.feng.headbean;

import org.feng.export.system.ExcelColumnTitle;
import lombok.Data;
import org.feng.export.system.ExcelExportBean;

/**
 * TODO
 *
 * @author feng
 */
@Data
@ExcelExportBean
public class NameAndFactoryDemo {
    @ExcelColumnTitle("名字")
    private String name;
    @ExcelColumnTitle("工厂")
    private String factory;
}

3.3 控制器 ExcelDemoController

package org.feng.controller;

import org.feng.export.ExportDynamicColumnTemplate;
import org.feng.export.ExportDynamicColumnTemplateBuilder;
import org.feng.headbean.StudentDemo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Controller
@RequestMapping("/excel")
public class ExcelDemoController {

    @GetMapping("/exportDy")
    public String exportDy(@RequestParam("table") List<String> table, HttpServletResponse response) throws IOException {

        // 构造导出模版
        ExportDynamicColumnTemplate<StudentDemo> dynamicColumnTemplate = new ExportDynamicColumnTemplateBuilder<>(StudentDemo.class)
                .columnNameList(table)
                .build();
        // 制造假数据
        dynamicColumnTemplate.appendExportDataList(studentDemos());

        // 转换为excel的字节数组
        byte[] bytes = dynamicColumnTemplate.toExcelByteArray();

        // 响应到web
        String fileName = System.currentTimeMillis() + ".xlsx";
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        response.setContentType("application/x-msdownload");
        response.setCharacterEncoding("utf-8");
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();

        return "success";
    }

    private List<StudentDemo> studentDemos() {
        List<StudentDemo> studentDemos = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            StudentDemo studentDemo = new StudentDemo();
            studentDemo.setStudentNo(100 + "-" + i);
            studentDemo.setAge(String.valueOf(20 + i));
            studentDemo.setSex(i > 2 ? "男" : "女");
            studentDemo.setClassName("一班");
            studentDemo.setName("小米" +(i+1));
            studentDemos.add(studentDemo);
        }

        return studentDemos;
    }
}

四、启动类

启动项目时,加载代码配置信息。

package org.feng;

import org.feng.export.factory.ExcelHeadMapFactory;
import org.feng.headbean.NameAndFactoryDemo;
import org.feng.headbean.StudentDemo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class ExcelDemoApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(ExcelDemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        List<Class<?>> needRegisterExcelHeadClassList = new ArrayList<>();
        needRegisterExcelHeadClassList.add(NameAndFactoryDemo.class);
        needRegisterExcelHeadClassList.add(StudentDemo.class);
        needRegisterExcelHeadClassList.forEach(ExcelHeadMapFactory::addHeadClass);
    }
}

五、测试

在谷歌浏览器访问:
http://localhost:8080/excel/exportDy?table=name,className,studentNo

会下载得到这样的文件:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【每日一题】得到山形数组的最少删除次数

文章目录 Tag题目来源解题思路方法一&#xff1a;最长递增子序列 写在最后 Tag 【最长递增子序列】【数组】【2023-12-22】 题目来源 1671. 得到山形数组的最少删除次数 解题思路 方法一&#xff1a;最长递增子序列 前后缀分解 根据前后缀思想&#xff0c;以 nums[i] 为山…

最新ChatGPT网站系统源码+AI绘画系统+支持GPT语音对话+详细图文搭建教程/支持GPT4.0/H5端系统/文档知识库

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

TYPE C 接口知识详解

1、Type C 概述 Type-C口有4对TX/RX分线&#xff0c;2对USBD/D-&#xff0c;一对SBU&#xff0c;2个CC&#xff0c;另外还有4个VBUS和4个地线。 当Type-C接口仅用作传输DP信号时&#xff0c;则可利用4对TX/RX&#xff0c;从而实现4Lane传输&#xff0c;这种模式称为DPonly模式…

C++ 检测 是不是 com组件 的办法 已解决

在日常开发中&#xff0c;遇到动态库和 com组件库的调用 无法区分。检测是否com组件的办法 在头部文件&#xff0c;引入文件 如果能编译成功说明是 com组件&#xff0c;至于动态库如何引入&#xff0c;还在观察中 #import "TerraExplorerX.dll" no_namespace, nam…

云原生之深入解析基于FunctionGraph在Serverless领域的FinOps的探索和实践

一、背景 Serverless 精确到毫秒级的按用付费模式使得用户不再需要为资源的空闲时间付费。然而&#xff0c;对于给定的某个应用函数&#xff0c;由于影响其计费成本的因素并不唯一&#xff0c;使得用户对函数运行期间的总计费进行精确的事先估计变成了一项困难的工作。以传统云…

TCP_滑动窗口介绍

简介 TCP协议中有两个窗口&#xff0c;滑动窗口和拥塞窗口&#xff0c;两者均是一种流控机制&#xff1b;滑动窗口是接收方的流控机制&#xff0c;拥塞窗口是发送方的流控机制。 本文介绍滑动窗口&#xff0c;接收方为TCP连接设置了接收缓存。当TCP连接接收到正确、按序的字节…

Mybatis3系列课程8-带参数查询

简介 上节课内容中讲解了查询全部, 不需要带条件查, 这节我们讲讲 带条件查询 目标 1. 带一个条件查询-基本数据类型 2.带两个条件查询-连个基本数据类型 3.带一个对象类型查询 为了实现目标, 我们要实现 按照主键 查询某个学生信息, 按照姓名和年级编号查询学生信息 按照学生…

MyBatis中延迟加载,全局和局部的开启使用与关闭

文章目录 MyBatis中延迟加载&#xff0c;全局和局部的开启使用与关闭1、问题提出2、延迟加载和立即加载延迟加载立即加载 3、三种对应的表关系中的加载4、打开全局延迟加载&#xff08;实现一对一的延迟加载&#xff09;5、实现一对多的延迟加载&#xff08;将上面设置的全局延…

渲染控制之条件渲染

目录 1、使用规则 2、更新机制 3、使用if进行条件渲染 4、if ... else ...语句和子组件状态 5、嵌套if语句 ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使用if、else和else if渲染对应状态下的UI内容。 1、使用规则 支持if、else和else if语句…

pip 常用指令 pip list 命令用法介绍

&#x1f4d1;pip 常用命令归类整理 pip list 是一个用于列出已安装的 Python 包的命令。这个命令会显示出所有已安装的包&#xff0c;以及它们的版本号。 pip list 命令有以下参数 -o, --outdated&#xff1a;列出所有过时的包&#xff0c;即有新版本可用的包。-u, --uptod…

DPDK单步跟踪(3)-如何利用visual studio 2019和visual gdb来单步调试dpdk

准备工作 因为时间的关系&#xff0c;我想到哪说到哪&#xff0c;可能没那么高的完成度。 但其实有心的人&#xff0c;看到这个标题&#xff0c;就关了本文自己能做了。 why和how to build debug version DPDK,见前两篇。这里我们准备开始。 首先&#xff0c;你有一台linux机…

什么是“人机协同”机器学习?

“人机协同”&#xff08;HITL&#xff09;是人工智能的一个分支&#xff0c;它同时利用人类智能和机器智能来创建机器学习模型。在传统的“人机协同”方法中&#xff0c;人们会参与一个良性循环&#xff0c;在其中训练、调整和测试特定算法。通常&#xff0c;它的工作方式如下…

《软件方法(下)》8.2.4 类和属性的命名

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.2 建模步骤C-1 识别类和属性 8.2.4 类和属性的命名 8.2.4.2 关于DDD话语中的“通用语言” DDD&#xff08;领域驱动设计&#xff09;话语中有“通用语言&#xff08;Ubiquitous L…

【JAVA面试题】什么是代码单元?什么是码点?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 目录 前言 思路 代码单元&#xff08;Code Unit&#xff09;&#xff1a; 码点&#xff08;Code Point&#xff09;&#xff1a; 作…

vscode | python | remote-SSH | Debug 配置 + CLIP4Clip实验记录

安装Extension 本地安装Remote-SSH、python 远程服务器上安装Python 难点&#xff1a;主机和远程服务器上安装Python扩展失败&#xff0c;可能是网络、代理等原因导致解决方法&#xff1a; 主机在官方网站下载Python扩展&#xff1a;https://marketplace.visualstudio.com/it…

RobotFramework 自动化测试实战进阶篇

工具 Robotframework, 采用PO设计模式 PO模型 PO模型即Page Objects&#xff0c;直译意思就是“页面对象”&#xff0c;通俗的讲就是把一个页面&#xff0c;或者说把一个页面的某个区域当做一个对象&#xff0c;通过封装这个对象可以实现调用。 PO设计的好处 代码复用&…

【沁恒蓝牙mesh】CH58x DataFlash 详解

本文主要介绍了 沁恒蓝牙芯片 CH58x 的 DataFlash 分区以及读写操作以及原理 &#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#x1f4dd; 个人主页&#xff1a;欢迎访问我的 Ethernet_Comm 博…

P3375 【模板】KMP

【模板】KMP 题目描述 给出两个字符串 s 1 s_1 s1​ 和 s 2 s_2 s2​&#xff0c;若 s 1 s_1 s1​ 的区间 [ l , r ] [l, r] [l,r] 子串与 s 2 s_2 s2​ 完全相同&#xff0c;则称 s 2 s_2 s2​ 在 s 1 s_1 s1​ 中出现了&#xff0c;其出现位置为 l l l。 现在请你求…

链表常见题型(1)

1.反转链表 1.1反转链表 如果我们想要反转链表&#xff0c;那应该有head的next指针指向空&#xff0c;其余结点的next指针反过来&#xff0c;指向它的上一个结点&#xff0c;那我们在执行该操作的时候就需要定义变量cur(current)表示我们当前遍历到的结点&#xff0c;变量pre(…

Linux应用程序管理(rpm yum 源码安装)

一.Linux应用程序基础 当我们主机安装Linux操作系统时候&#xff0c;也会同时安装一些软件或网络服务等等&#xff0c;但是随着系统一起安装的软件包毕竟他是少数的&#xff0c;能够实现的功能也是有限的&#xff0c;如果需要实现更丰富的功能&#xff0c;那就需要安装应用程序…