阿里Esay-excel解析复杂表头方案实际应用

2023-11-01

目录

我遇到的场景:

思路:

一、观察表格整体结构

二、设计数据结构

三、引入依赖

四、建立实体

五、实现自定义解析逻辑

六、业务调用

七、其他代码段

可以参考的链接


我遇到的场景:

        业务上需要读取表格中的所有数据,接着入库保存。 

思路:


一、观察表格整体结构

值得注意的两个细节:

        “合并单元格实际上就是多个单元格都是同一个值”

        “左上原则:拆开合并单元格后,仅最左或最上单元格保留原有值,剩余单元格均为空”

二、设计数据结构

目标:设计一个围绕指标、综保区、的数据表,并且保证指标和综保区可扩展。

这里将指标和综保区统统编码化,便于扩展。

CREATE TABLE `t_cba_dispatch_summary` (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `sequence_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '序号',
  `first_indicator` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '一级指标 | \n1 一线进出口值(亿元)\n2 一线进出口值增长率(%)\n3 一线进出口值全国排名(全国155家)\n4 企业情况(家)\n5 招商引资(个)\n6 实际利用外资(亿美元)\n7 聚焦生产要素\n',
  `second_indicator` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '二级指标 | \n1_1	一线进出口值(亿元)\r\n2_1	一线进出口值增长率(%)\r\n3_1	一线进出口值全国排名(全国155家)\r\n3_2	一线进口值(亿元)\r\n3_3	一线进口值增长率(%)\r\n3_4	一线出口值(亿元)\r\n3_5	一线出口值增长率(%)\r\n4_1	期末注册企业数\r\n4_2	有进出口业务企业\r\n4_3	加工贸易企业数\r\n5_1	签约项目金额(亿元)\r\n5_2	外向型经济项目(个)\r\n5_3	100亿元以上项目(个)\r\n5_4	50亿元以上项目(个)\r\n5_5	10亿元以上项目(个)\r\n5_6	1亿元以上项目(个)\r\n5_7	3000万美元以上外资项目(个)\r\n5_8	在谈项目(个)\r\n6_1	实际利用外资(亿美元)\r\n7_1	引进各类人才(人)\r\n7_2	引进研发创新机构(个)\r\n',
  `cba_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '综保区名称 | \n武汉东湖综保区			wh_dh_cba\r\n武汉新港空港综保区			wh_xgkg_cba\r\n武汉经开综保区			wh_jk_cba\r\n宜昌综保区				yc_cba\r\n襄阳综保区(未运营 			xy_cba\r\n黄石棋盘洲综保区(在建)	hsqpz_cba\r\n',
  `cba_code` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '综保区编码|\n武汉东湖综保区 wh_dh_cba\n武汉新港空港综保区	wh_xgkg_cba\n武汉经开综保区 wh_jk_cba\n宜昌综保区 yc_cba\n襄阳综保区(未运营) xy_cba\n黄石棋盘洲综保区(在建) hsqpz_cba\n',
  `amount_to` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '合计',
  `create_user` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建部门code',
  `create_dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建部门name',
  `update_user` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` int DEFAULT '0' COMMENT '删除状态 |  0(默认) 未删除 1 已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='综保区年月调度汇总表(COMPREHENSIVE_BONDED_AREA)';

三、引入依赖

        <!--easy-excel最新依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.2.0</version>
        </dependency>

四、建立实体

分析excel表头,建立实体。多级表头时,设置各表头路径:

//多级表头在设置value时,每行表头都需要记录下来,可以理解成告诉程序基层表头的路径。
@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "一级指标"}, index = 1)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ComprehensiveBondedAreaDispatchVO {

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "序号"}, index = 0)
	private String sequenceNo;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "一级指标"}, index = 1)
	private String firstIndicator;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "二级指标"}, index = 2)
	private String secondIndicator;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "武汉东湖综保区"}, index = 3)
	private String wuhanDongHuCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "武汉新港空港综保区"}, index = 4)
	private String wuhanXinGangKongGangCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "武汉经开综保区"}, index = 5)
	private String wuhanJingKaiJkCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "宜昌综保区"}, index = 6)
	private String yiChangCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "襄阳综保区(未运营)"}, index = 7)
	private String xiangYangCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "黄石棋盘洲综保区(在建)"}, index = 8)
	private String huangShiQpzCba;

	@ExcelProperty(value = {"综保区2022年1-6月份调度汇总表", "合计"}, index = 9)
	private String amount;


}

五、实现自定义解析逻辑

通常这里只需要继承一下抽象类,然后根据自身业务覆写一些方法实现解析逻辑即可。

extends AnalysisEventListener<M> 

以下面我的代码为例,由于我需要解析多个类型不同的sheet,扩展需要,我这里是定义了一个抽象类去继承的 AnalysisEventListener<M> 。接着用具体的子类去继承它,并在其中实现了我需要的通用解析业务。代码如下:

@Getter
@Slf4j
public abstract class AbstractDataIntegrationListener<M> extends AnalysisEventListener<M> {
	/**
	 * The constant TOTAL_KEY.
	 */
	public static final String TOTAL_KEY = "总计";
	/**
	 * The constant AMOUNT_TO_KEY.
	 */
	public static final String AMOUNT_TO_KEY = "合计";

	/**
	 * The Head row number.
	 */
	public int headRowNumber;

	/**
	 * The Start row number.
	 */
	public int startRowNumber;

	/**
	 * The constant HEAD_OFFSET.
	 */
	public static final AtomicInteger HEAD_OFFSET = new AtomicInteger();

	/**
	 * The Model class.
	 */
	public Class<M> modelClass;

	/**
	 * The constant BATCH_COUNT.
	 *
	 * @Description 每100条存库, 然后清理list, 方便GC。
	 * @Author chentl
	 * @Create: 2023 /2/8 10:30 上午
	 */
	protected static final int BATCH_COUNT = 100;

	/**
	 * The Cached model data.
	 *
	 * @Description 缓存的excel数据
	 * @Author chentl
	 * @Create: 2023 /2/8 10:34 上午
	 */
	public List<M> cachedModelData = Lists.newArrayListWithExpectedSize(BATCH_COUNT);

	@Override
	public void invoke(M m, AnalysisContext analysisContext) {
		if (toJumpHeadRow(analysisContext)) {
			return;
		}
		cachedModelData.add(m);
	}

	@Override
	public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
		if (HEAD_OFFSET.incrementAndGet() == headRowNumber) {
			super.invokeHeadMap(headMap, context);
			checkExcelHeader(headMap, modelClass);
		}

		super.invokeHeadMap(headMap, context);
	}


	@Override
	public void doAfterAllAnalysed(AnalysisContext analysisContext) {
		saveData();
		log.info("[easy-excel]解析完成。");
	}

	/**
	 * 获取监听器,由子类实现
	 *
	 * @param enums 枚举入参
	 * @return {@link AbstractDataIntegrationListener<M>}
	 * @author chentl
	 * @version v1.0.0
	 * @since 9 :45 上午 2023/2/9
	 */
	public abstract AbstractDataIntegrationListener<M> getListener(DataIntegrationReadEnum enums);

	/**
	 * 保存数据业务
	 *
	 * @author chentl
	 * @version v1.0.0
	 * @since 2 :18 下午 2023/2/9
	 */
	protected void saveData() {
		log.info("[easy-excel]{}条数据,开始处理。", cachedModelData.size());

		log.info("[easy-excel]处理结束,未见异常。");
	}

	/**
	 * 分批操作
	 *
	 * @author chentl
	 * @version v1.0.0
	 * @since 11 :13 上午 2023/2/8
	 */
	protected void saveInBatches() {
		if (cachedModelData.size() >= BATCH_COUNT) {
			saveData();
			// 存储完成清理 list
			cachedModelData = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
		}
	}

	/**
	 * 是否跳过表头
	 *
	 * @param analysisContext the analysis context
	 * @return {@link boolean}
	 * @author chentl
	 * @version v1.0.0
	 * @since 3 :04 下午 2023/2/9
	 */
	protected boolean toJumpHeadRow(AnalysisContext analysisContext) {
		return analysisContext.readRowHolder().getRowIndex() < startRowNumber - 1;
	}

	/**
	 * 传递合并"列"单元格的值
	 *
	 * @param m                  表格泛型对象
	 * @param targetFieldNameArr 目标列索引名称
	 * @author chentl
	 * @version v1.0.0
	 * @since 2 :35 下午 2023/2/9
	 */
	protected void passMergedColValue(M m, String... targetFieldNameArr) {
		try {
			List<String> fieldList = Arrays.stream(m.getClass().getDeclaredFields()).map(Field::getName).collect(Collectors.toList());
			for (String currentField : targetFieldNameArr) {
				//上一列
				String previousFiled = fieldList.get(fieldList.indexOf(currentField) - 1);

				Method curFieldGetMethod = m.getClass().getMethod("get" + StringUtils.capitalize(currentField));
				Method curFiledSetMethod = m.getClass().getMethod("set" + StringUtils.capitalize(currentField), String.class);
				Method preFieldGetMethod = m.getClass().getMethod("get" + StringUtils.capitalize(previousFiled));

				val currentFieldVal = ValueUtils.parseString(curFieldGetMethod.invoke(m, null));
				if (StringUtils.isBlank(currentFieldVal)) {
					val previousFieldVal = ValueUtils.parseString(preFieldGetMethod.invoke(m, null));
					curFiledSetMethod.invoke(m, previousFieldVal);
				}
			}
		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
			log.error("[easy-excel]传递合并横向单元格的值时发生异常", e);
		}
	}


	/**
	 * 传递合并"行"单元格的值
	 *
	 * @param m                  表格泛型对象
	 * @param targetFiledNameArr 目标字段名称
	 * @author chentl
	 * @version v1.0.0
	 * @since 1 :20 下午 2023/2/9
	 */
	protected void passMergedRowValue(M m, String... targetFiledNameArr) {
		try {
			for (String fn : targetFiledNameArr) {
				Method setMethod = m.getClass().getMethod("set" + StringUtils.capitalize(fn), String.class);
				Method getMethod = m.getClass().getMethod("get" + StringUtils.capitalize(fn));
				M lastField = cachedModelData.stream().reduce((f, s) -> s).orElse(null);
				if (CollectionUtil.isEmpty(cachedModelData) || Objects.isNull(lastField)) {
					return;
				}
				val currentFieldVal = ValueUtils.parseString(getMethod.invoke(m, null));
				if (StringUtils.isBlank(currentFieldVal)) {
					val lastFieldVal = ValueUtils.parseString(getMethod.invoke(lastField, null));
					setMethod.invoke(m, lastFieldVal);
				}
			}
		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
			log.error("[easy-excel]传递合并竖向单元格的值时发生异常", e);
		}
	}

}

定义具体的解析器 ,处理略微复杂的表格内容。重点在覆写 invoke(...) 方法:

这里有三段扩展的业务:

1、跳过表头,目的是为了直接读取数据

2、传递横向被合并单元格的数据。(因为 |A|B| 合并为 |AB|后,读取时只有A那一列有数据,这个方法 passMergedColValue 就是将A的数据传递给B。)

3、传递纵向被合并单元格的数据。( 因为

——          ——                       ——

A               B                            A                              

——   和   ——         ==>        B

                                               —— 

被合并后,读取时只有B那一行有数据,这个方法 passMergedRowValue 就是将A的值传递给B。

)

@Getter
@NoArgsConstructor
@Slf4j
public class ComprehensiveBondedAreaDispatchDiListener extends AbstractDataIntegrationListener<ComprehensiveBondedAreaDispatchVO> {

	public ComprehensiveBondedAreaDispatchDiListener(int startRowNumber, int headRowNumber, Class<ComprehensiveBondedAreaDispatchVO> clazz) {
		super();
		super.startRowNumber = startRowNumber;
		super.headRowNumber = headRowNumber;
		super.modelClass = clazz;
	}

	@Override
	public void invoke(ComprehensiveBondedAreaDispatchVO cba, AnalysisContext analysisContext) {
		List<Integer> skipRow = Arrays.asList(9, 13, 23);
		//跳过指定行号
		boolean toJump = skipRow.contains(analysisContext.readRowHolder().getRowIndex());
		if (toJumpHeadRow(analysisContext) || toJump) {
			return;
		}

		passMergedColValue(cba, "secondIndicator");

		passMergedRowValue(cba, "sequenceNo", "firstIndicator", "secondIndicator");

		cachedModelData.add(cba);

		saveInBatches();
	}

	/**
	 * 获取监听器,由子类实现
	 *
	 * @param enums 监听器枚举
	 * @return {@link AbstractDataIntegrationListener< ComprehensiveBondedAreaDispatchVO >}
	 * @author chentl
	 * @version v1.0.0
	 * @since 3:48 下午 2023/2/8
	 */
	@Override
	public AbstractDataIntegrationListener<ComprehensiveBondedAreaDispatchVO> getListener(DataIntegrationReadEnum enums) {
		return new ComprehensiveBondedAreaDispatchDiListener(enums.startRowNumber, enums.headRowNumber, ComprehensiveBondedAreaDispatchVO.class);
	}

}

六、业务调用

下面是controller以及service下的业务实现:

@PostMapping("/read-test-4")
	@ApiOperationSupport(order = 4)
	@ApiOperation(value = "上传4", notes = "传入file")
	public R<Object> upload4(@RequestParam MultipartFile file) {
		Object result = dataIntegrationService.readExcel(file, DataIntegrationReadEnum.COMPREHENSIVE_BONDED_AREA_DISPATCH);
		return R.data(result);
	}
public class DataIntegrationService {


	public Object readExcel(MultipartFile file, DataIntegrationReadEnum diReadEnum) {

		Map<String, Object> sheetResultMap = Maps.newLinkedHashMapWithExpectedSize(5);
		AbstractDataIntegrationListener<?> diListener;
		Class<?> excelModel;
		try {
			diListener = ((AbstractDataIntegrationListener) Class.forName(diReadEnum.listenerClassName).newInstance()).getListener(diReadEnum);
			excelModel = diListener.modelClass;
			if (Objects.isNull(excelModel)) {
				return null;
			}
			String sheetName = diReadEnum.sheetName;

			ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build();
			ReadSheet sheet = EasyExcel.readSheet(sheetName).head(excelModel).registerReadListener(diListener).build();
			excelReader.read(sheet);

			sheetResultMap.put(sheetName, diListener.getCachedModelData());


		} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
			log.error("[easy-excel]读取模板解析类时发生异常:", e);
		} catch (IOException e) {
			log.error("[easy-excel]读取excel时发生IO异常:", e);
		}


		return R.data(sheetResultMap);

	}

}

七、其他代码段

上述代码中涉及的枚举类 DataIntegrationReadEnum

@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum DataIntegrationReadEnum {

	//数据统纳EXCEL待读取表格枚举
	VOLUME_OF_FREIGHT_BY_DISTRICT(0, "进出口货运量分市州汇总表", 4, 5, "com.dsj.message.excel.listener.VofByDistrictDiListener"),
	VOLUME_OF_FREIGHT_BY_TRANSPORTATION_MODE(1, "进出口货运量按运输方式汇总表", 4, 5, "com.dsj.message.excel.listener.VofByTransportationModeDiListener"),
	VOLUME_OF_FREIGHT_BY_CUSTOMS_STATISTICS(2, "海关货运量业务统计表", 4, 5, "com.dsj.message.excel.listener.VofByCustomStatisticsDiListener"),
	COMPREHENSIVE_BONDED_AREA_DISPATCH(3, "综保区2022年01-06月份调度汇总表", 2, 3, "com.dsj.message.excel.listener.ComprehensiveBondedAreaDispatchDiListener"),
	BONDED_LOGISTICS_CENTER_DISPATCH(4, "保税物流中心(B型)2022年1-6月份调度汇总表", 2, 3, "com.dsj.message.excel.listener.BondedLogisticsCenterDispatchDiListener"),
	;
	public int sheetIndex;

	public String sheetName;

	public int headRowNumber;

	public int startRowNumber;

	public String listenerClassName;

}

可以参考的链接

Easy Excel 官方api

easy excel github地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

阿里Esay-excel解析复杂表头方案实际应用 的相关文章

随机推荐

  • 支持向量机(SVM)和逻辑回归(LR)

    支持向量机 SVM 和逻辑回归 LR 支持向量机文档 逻辑回归文档 一 相同点 1 都是常用的分类算法 2 如果不考虑核函数 LR和SVM都是线性分类算法 也就是说他们的分类决策面都是线性的 3 LR和SVM都是监督学习算法 4 LR和SV
  • 封装 视频以及监控组件

    1 引入插件 文件Git 地址 https gitee com wang xiaowang123 liveplayer html 文件中 liveplayer lib min js 文件在Git仓库中 2 封装组件 LivePlayerDe
  • 区块链共识协议最详细的分析

    共识机制是区块链的核心基石 是区块链系统安全性的重要保障 区块链是 一个去中心化的系统 共识机制通过数学的方式 让分散在全球各地成千上万的节点就区块的创建达成一致的意见 共识机制中还包含了促使区块链系统有效运 转的激励机制 是区块链建立信任
  • Web 请求过程剖析笔记

    1 服务器渲染 在服务器那边直接把数据和HTML整在一起 统一返回给浏览器 在页面源代码中看得到数据 2 客户端渲染 第一次请求只要一个 HTML 骨架 第二次请求拿到数据 进行数据显示 在页面源代码中看不到数据
  • LVS原理详解以及部署

    linux virtual server简称LVS Internet的快速增长使多媒体网络服务器面对的访问数量快速增加 服务器需要具备提供大量并发访问服务的能力 因此对于大负载的服务器来讲 CPU I O处理能力很快会成为瓶颈 由于单台服务
  • Cannot prompt because user interactivity has been disabled 问题处理

    问题描述 git 使用的是ssh访问的 配置好之后 拉取远程仓库没有问题 但是在项目中代码拉去项目私有仓库时 报 fatal Cannot prompt because user interactivity has been disable
  • 华为上机题7(四则运算)

    题目 输入一个只包含个位数字的简单四则运算表达式字符串 计算该表达式的值 注 3 1 表达式只含 四则运算符 不含括号 3 2 表达式数值只包含个位整数 0 9 且不会出现0作为除数的情况 3 3 要考虑加减乘除按通常四则运算规定的计算优先
  • C语言中从键盘输入字符串时的一些问题

    C语言中从键盘输入字符串时的一些问题 1 scanf scanf 在输入字符串时有很大的弊端 例如 1 scanf 在从键盘读入字符时并不会根据所定义的字符数组的大小来控制读入多少个 而是从scanf 中传入的地址开始一直访问下一个元素的内
  • HTTPS原理 如何实现安全通信

    目录 HTTP存在的问题 HTTPS原理 数字证书 CA可不可以用公钥加密服务器的公钥 参考 HTTPS理论基础及其在Android中的最佳实践 孙群的博客 CSDN博客 android httpshttps blog csdn net i
  • wazuh all in one 一步步部署

    Wazuh 是一个免费 开源和企业级的安全监控解决方案 用于威胁检测 完整性监控 事件响应和合规性 Adding the Wazuh repository yum install curl unzip wget libcap y rpm i
  • Java开发技巧!网上java培训师

    01 JAVA基础 1 1 java知识点 Hashmap 源码级掌握 扩容 红黑树 最小树化容量 hash冲突解决 有些面试官会提出发自灵魂的审问 比如为什么是红黑树 别的树不可以吗 为什么8的时候树化 4不可以吗 等等 concuree
  • 判断单链表是否有环,如何寻找环的入口结点

    今天我们来讨论一个有趣的链表问题 判断链表是否有环 如果有环 环的入口结点如何寻找 文章目录 1 如何判断一个单链表是否有环呢 2 如何找到环的入口结点 1 如何判断一个单链表是否有环呢 一个单链表如果有环 那它只可能是这样的 如下图所示
  • vue实现注册界面

    p 在 Vue 中实现注册界面可以分为以下几步 p ol li p 在组件中创建一个表单 用来输入用户名 密码 电子邮件等信息 p li li p 使用 Vue 的表单绑定特性 将表单中的输入绑定到组件的数据中 p li li p 创建一个
  • acc 蓝牙_蓝牙音频传输格式:ACC,SBC,APTX和LDAC

    ACC Advanced Audio Coding 高级音频编码 ACC是杜比实验室为音乐社区提供的技术 是一种高压缩比的编码算法 实际体验上都认为同样的码率下面 ACC的听感比MP3好 apple上面ACC的音频很多 所以现在的iphon
  • Qt之事件处理机制

    目录 一 事件简介 二 事件的处理 1 重写notify处理函数 2 事件过滤器 3 重写event处理函数 4 重写特定事件处理函数 三 事件的发送 一 事件简介 Qt 是一个基于 C 的框架 主要用来开发带窗口的应用程序 使用的基于窗口
  • SMP,NUMA,MPP

    一 概念介绍 SMP Symmetric Multi Processor 对称多处理器 NUMA Non Uniform Memory Access 非一致存储访问结构 MPP Massive Parallel Processing 海量并
  • exynos5410 TMU系统的critical情况处理方法

    根据TMU 将CPU的温升分为四种情况来处理 分别是下述情况 THERMAL TRIP HOT THERMAL TRIP ACTIVE THERMAL TRIP PASSIVE THERMAL TRIP CRITICAL THERMAL T
  • PhotoShop 快捷键及技巧

    复制图层 图层 新建 复制图层 图层上右键 点击复制图层 ctrl J 删除图层 选中图层 按del键删除图层 对图像实现自由变换 ctrl T 同过图像边框八个方块对图像实现大小变化 按回车键确认变换 按住shift键实现等比例变换 勾选
  • Python程序报错:TypeError: read() missing 1 required positional argument: ‘self‘

    文章目录 问题描述 问题定位 解决方法 总结 问题描述 最近在学习python面向对象执行调用方法时程序报错 TypeError read missing 1 required positional argument self 一看这个报错
  • 阿里Esay-excel解析复杂表头方案实际应用

    目录 我遇到的场景 思路 一 观察表格整体结构 二 设计数据结构 三 引入依赖 四 建立实体 五 实现自定义解析逻辑 六 业务调用 七 其他代码段 可以参考的链接 我遇到的场景 业务上需要读取表格中的所有数据 接着入库保存 思路 一 观察表