阿里easyExcel -- excel单元格自定义下拉选择(升级版)

背景

很久很久以前写了一篇类似的文章 阿里easyExcel – excel下载/导出/读取 (单元格自定义下拉选择、不支持图片) ,用了没多久就发现不好用,限制太多(以后遇到你就知道了),然后就有了现在迟到很久的文章,主要懒得写文章。

必看

此篇文章的单元格下拉支持 1级,2级,多级联动下拉等 ,比较复杂,需耐心看一下。
再写之前,先讲几个excel注意的点:

  1. 你的excel必须支持创建 名称管理器 ,如下图所示:
    在这里插入图片描述
  2. excel必须支持 INDIRECTCONCATENATEVLOOKUP 函数,要是excel版本太太太太低,可能没有这些函数,检查方式如下,一般=后面加函数名就会有提示:
    在这里插入图片描述

缺点或限制

  1. 当下拉的数据太多时,会导致创建excel的速度变慢
  2. 下拉的总数据不能超过 1048576 行,不能超过16384列

先简单看下效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下拉的原理,怎么实现的下拉

创建一个sheet,然后把下拉数据放接某一列,如下:
在这里插入图片描述
当然也可以横着放,但是横着最多只能放 16384 列,而竖着可以放 1048576 行。
如果 只有1级下拉 那么不需要创建 名称管理器 ,如果是多级联动下拉,则除了最后1级,其余都要设置 名称管理器名称管理器 怎么设置下拉自己去百度。下面直接放代码,看不懂慢慢看,不想看直接复制使用,不想讲解了哈哈哈。

另外,由于 名称管理器 对name的设置要求很高,有些字符无法设置,为了实现下拉数据的千奇百怪,所以需要做一层转换。下面展示代码。

代码

ExcelUtils 工具:

public class ExcelUtils {/*** 下载** @param writeHandlers 处理器(可自定义,可为null)* @param os            输出流* @param clazz         操作对象字节* @param data          数据* @param sheetName     表名*/public static <T> void downLoad(List<WriteHandler> writeHandlers, OutputStream os, Class<T> clazz, List<T> data, String sheetName) {ExcelWriterSheetBuilder builder = EasyExcelFactory.write(os, clazz).sheet(sheetName);if (!CollectionUtils.isEmpty(writeHandlers)) {writeHandlers.forEach(builder::registerWriteHandler);}builder.doWrite(data);}
}

ExcelLinkageDropdown 多级下拉数据:

public class ExcelLinkageDropdown {/*** 是否允许设置其他的值。false:只能是下拉列表的值;true:允许列表之外的值*/private boolean isAllowOtherValue = false;/*** 表头名称(为bean对象时传字段名称,为map时且多个头用json:["头1","头2"])*/private String fieldName;/*** 第几列,为对象自动计算*/private Integer cellIndex;/*** 下拉内容,<上级,下级列表>,第一级的key为null,只有一级时key也为null*/private Map<String, List<String>> value = new HashMap<>();/*** 提示信息*/private String message = "只能选择列表中的值!!!";//...get set 自己生成
}

DropdownWriteHandler下拉处理器:

/*** 下拉处理器:单元格下拉列表格式* 最大行:1048576 / 65536* 最大列:16384 / 256*/
public class DropdownWriteHandler extends AbstractVerticalCellStyleStrategy implements SheetWriteHandler {private final Map<ExcelLinkageDropdown[], String> dropdowns = new HashMap<>(); //所有下拉值private final Class<?> clazz; //操作的类private final int headMax; //表头行数public DropdownWriteHandler(Class<?> clazz) {this.clazz = clazz;Field[] fields = clazz.getDeclaredFields();// 取表头行数this.headMax = Arrays.stream(fields).filter(field -> field.isAnnotationPresent(ExcelProperty.class)).map(field -> field.getAnnotation(ExcelProperty.class).value().length).reduce(Integer::max).orElse(0);}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook book = writeWorkbookHolder.getCachedWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 设置固定区域sheet.createFreezePane(0, headMax, 0, headMax);DataValidationHelper helper = sheet.getDataValidationHelper();// 联动下拉校验if (!CollectionUtils.isEmpty(dropdowns)) {long l = System.currentTimeMillis();for (Map.Entry<ExcelLinkageDropdown[], String> dropdownMap : dropdowns.entrySet()) {ExcelLinkageDropdown[] dropdown = dropdownMap.getKey();String key = StringUtils.isNotBlank(dropdownMap.getValue()) ? dropdownMap.getValue() :"t_" + Arrays.stream(dropdown).map(t -> t.getCellIndex().toString()).collect(Collectors.joining("_"));if (key.length() > 30) {key = "s_" + IdGenerator.getInstance().getId();}//设置下拉及校验数据setDropdownsAndValidationData(book, sheet, helper, dropdown, key);}long l2 = System.currentTimeMillis();System.out.println("下拉耗时" + (l2 - l));}}/*** 设置下拉及校验数据*/private void setDropdownsAndValidationData(Workbook book, Sheet sheet, DataValidationHelper helper, ExcelLinkageDropdown[] dropdown, String key) {// 设置多级下拉if (book.getSheetIndex(key) == -1) {buildDropdownSheet(book, dropdown, key);}//设置一级下拉List<String> val = dropdown[0].getValue().get(null);if (Objects.nonNull(val) && !val.isEmpty()) {String ss = ExcelTools.getRangeByCel(2, 2, val.size()); // A和B被占用,从C开始dropdownValidationData(String.format("='%s'!%s", key, ss), helper, this.headMax, 1000000, dropdown[0], sheet);}//二级及之后的下拉for (int i = 1; i < dropdown.length; i++) {String ci = CellReference.convertNumToColString(dropdown[i - 1].getCellIndex());String format = "INDIRECT(CONCATENATE(\"_\",VLOOKUP($" + ci + (this.headMax + 1) + "," + key + "!A:B,2,0),\"_\",\"" + key + "\"))"; // A:B写死dropdownValidationData(format, helper, this.headMax, 1000001, dropdown[i], sheet);}}/*** 验证下拉数据** @param formula   公式* @param firstRow  第一行* @param lastRow   最后一行* @param dropdown1 下拉数据* @param sheet     表*/private static void dropdownValidationData(String formula, DataValidationHelper helper, int firstRow, int lastRow, ExcelLinkageDropdown dropdown1, Sheet sheet) {DataValidationConstraint constraint = helper.createFormulaListConstraint(formula);DataValidation dataValidation = helper.createValidation(constraint, new CellRangeAddressList(firstRow, lastRow, dropdown1.getCellIndex(), dropdown1.getCellIndex()));dataValidation.setSuppressDropDownArrow(false);if (dataValidation instanceof XSSFDataValidation) {dataValidation.setSuppressDropDownArrow(true);dataValidation.setShowErrorBox(!dropdown1.isAllowOtherValue());  // 输入无效值时是否显示错误框dataValidation.setShowPromptBox(!dropdown1.isAllowOtherValue());  // 设置无效值时 是否弹出提示框dataValidation.createPromptBox("温馨提示", dropdown1.getMessage());   // 设置无效值时的提示框内容dataValidation.createErrorBox("温馨提示", dropdown1.getMessage());   // 设置无效值时的提示框内容}sheet.addValidationData(dataValidation);}/*** 设置单级或多级联动下拉,按顺序(1级,2级,3级...),否则将出错* 调用多次将设置多个多级联动* 单级或1级的key为null** @param head clazz为map时需要传头,否则传null*/public void setLinkageDropdown(List<List<String>> head, String excelName, ExcelLinkageDropdown... dropdowns) throws Exception {if (Objects.isNull(dropdowns) || dropdowns.length == 0) {throw new Exception("至少设置一个下拉参数");}boolean isMap = this.clazz.isAssignableFrom(Map.class);if (isMap && CollectionUtils.isEmpty(head)) {throw new Exception("head参数不能为空");}if (StringUtils.isNotBlank(excelName) && excelName.length() > 30) {throw new Exception("excelName长度不能超过30");}if (isMap) {for (ExcelLinkageDropdown dropdown : dropdowns) {if (dropdown.getValue().isEmpty()) {continue;}List<String> heads = head.stream().map(t -> String.join(",", t)).collect(Collectors.toList());int i = heads.indexOf(dropdown.getFieldName());dropdown.setCellIndex(i);}this.dropdowns.put(dropdowns, excelName);return;}List<ExcelLinkageDropdown> ds = new ArrayList<>();for (ExcelLinkageDropdown dropdown : dropdowns) {if (dropdown.getValue().isEmpty()) {continue;}Field field;try {field = this.clazz.getDeclaredField(dropdown.getFieldName());} catch (Exception e) {throw new Exception("填写的字段不存在:" + dropdown.getFieldName() + "," + e.getMessage());}int index = field.getAnnotation(ExcelProperty.class).index(); // 获取头的位置if (index == -1) {for (Field f : clazz.getDeclaredFields()) {if (!f.isAnnotationPresent(ExcelProperty.class)) {continue;}index++;if (f.getName().equals(dropdown.getFieldName())) {break;}}}dropdown.setCellIndex(index);ds.add(dropdown);}this.dropdowns.put(ds.toArray(new ExcelLinkageDropdown[0]), excelName);}/*** 设置单级或多级联动下拉,按顺序(1级,2级,3级...),否则将出错* 调用多次将设置多个多级联动* 单级或1级的key为null** @param head clazz为map时需要传头,否则传null*/public void setLinkageDropdown(List<List<String>> head, ExcelLinkageDropdown... dropdowns) throws Exception {this.setLinkageDropdown(head, null, dropdowns);}/*** 构建下拉列表sheet页,用于下拉框展示的数据源** @param book 工作簿*/private void buildDropdownSheet(Workbook book, ExcelLinkageDropdown[] dropdowns, String hiddenArea) {//创建新的隐藏表Sheet hideSheet = createNewHideSheet(book, hiddenArea);// 设置map的key格式化, 设置一级下拉this.setKeyFormat(dropdowns, hideSheet);// 第4列开始将具体的数据写入到每一列中int col = 3;int startRow = 1; //开始行CellStyle cellStyle = this.getFixRed(book);for (int i = 1; i < dropdowns.length; i++) {ExcelLinkageDropdown dropdown = dropdowns[i];for (Map.Entry<String, List<String>> entry : dropdown.getValue().entrySet()) {int rows = entry.getValue().size();//起始行如果超出最大行数,则新增一列,起始行重新计算,当前列总行数重新计算;if (startRow >= 1048576 || startRow + rows >= 1048576) {startRow = 1;col++;}//当前列超出最大列数,报错if (col > 16384) {throw new RuntimeException("当前列超出最大列数");}//设置下拉值和名称管理器this.setNameName(book, hideSheet, hiddenArea, col, startRow, cellStyle, entry, rows);//重新计算起始行startRow = startRow + rows + 2;}}}/*** 设置下拉值和名称管理器*/private void setNameName(Workbook book, Sheet hideSheet, String hiddenArea, int col, int startRow, CellStyle cellStyle, Map.Entry<String, List<String>> entry, int rows) {Row row0 = Objects.isNull(hideSheet.getRow(startRow - 1)) ? hideSheet.createRow(startRow - 1) : hideSheet.getRow(startRow - 1);Cell cell = row0.createCell(col);cell.setCellValue(ExcelTools.replaceAscii(entry.getKey()));cell.setCellStyle(cellStyle); //设置样式,区分下拉值for (int j = 0; j < rows; j++) {int r = j + startRow;Row row = Objects.isNull(hideSheet.getRow(r)) ? hideSheet.createRow(r) : hideSheet.getRow(r);row.createCell(col).setCellValue(entry.getValue().get(j));}// 添加名称管理器String range = ExcelTools.getRangeByCel(col, startRow + 1, rows);String nameName = "_" + ExcelTools.replaceAscii(entry.getKey()) + "_" + hiddenArea;Name name = book.createName();name.setNameName(nameName); // key不可重复String formula = hiddenArea + "!" + range;name.setRefersToFormula(formula);}/*** 设置map的key格式化*/private void setKeyFormat(ExcelLinkageDropdown[] dropdowns, Sheet hideSheet) {// 第1-2列设置匹配表int rowId = 0;  // 设置区域的头行for (int i = 1; i < dropdowns.length; i++) {ExcelLinkageDropdown d = dropdowns[i];for (Map.Entry<String, List<String>> kv : d.getValue().entrySet()) {// 原始key-第一列Row row0 = hideSheet.createRow(rowId);row0.createCell(0).setCellValue(kv.getKey()); // 第一列row0.createCell(1).setCellValue(ExcelTools.replaceAscii(kv.getKey())); // 处理后的key-第二列rowId++;}}// 设置第1级List<String> dropdownVal = dropdowns[0].getValue().get(null); // 得到第一级Row r1 = Objects.isNull(hideSheet.getRow(0)) ? hideSheet.createRow(0) : hideSheet.getRow(0);r1.createCell(2).setCellValue(dropdowns[0].getFieldName());for (int i = 0; i < dropdownVal.size(); i++) {Row r1_ = Objects.isNull(hideSheet.getRow(i + 1)) ? hideSheet.createRow(i + 1) : hideSheet.getRow(i + 1);r1_.createCell(2).setCellValue(dropdownVal.get(i));}}/*** 创建新的隐藏表*/private Sheet createNewHideSheet(Workbook book, String hiddenArea) {// 创建一个专门用来存放下拉的隐藏sheet页Sheet hideSheet = book.createSheet(hiddenArea);// 这一行作用是将此sheet隐藏book.setSheetHidden(book.getSheetIndex(hideSheet), true);//如果是SXSSFWorkbook类型,转XSSFWorkbook类型,否则sheet.getRow()可能为空if (book instanceof SXSSFWorkbook) {SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook) book;hideSheet = sxssfWorkbook.getXSSFWorkbook().getSheetAt(book.getSheetIndex(hideSheet));}return hideSheet;}/*** 红色固定样式*/private CellStyle getFixRed(Workbook book) {CellStyle style = book.createCellStyle();Font font = book.createFont();font.setBold(true);font.setColor(IndexedColors.RED.getIndex());style.setFont(font);return style;}
}

工具:

public class ExcelTools {/*** 计算formula:纵向** @param offset   偏移量,如果给0,表示从A列开始,1,就是从B列* @param rowId    第几行开始* @param rowCount 一共多少行* @return 如果给入参 0,2,10. 表示从A2-A11。最终返回 $A$2:$A$11*/public static String getRangeByCel(int offset, int rowId, int rowCount) {String columnLetter1 = CellReference.convertNumToColString(offset);return String.format("$%s$%s:$%s$%s", columnLetter1, rowId, columnLetter1, rowId + rowCount - 1);}/*** 把非(中文、英文、下划线、点)替换为 ascii码,因为excel不支持其他字符设置名称管理器* 如:”审核订单(一级)(1)“ 替换为 ”审核订单.40.一级.41..40.1.41.“*/public static String replaceAscii(String str) {if (StringUtils.isBlank(str)) {return str;}StringBuilder sb = new StringBuilder();for (String s : str.split("")) {if (s.matches("[^\\d\\u4e00-\\u9fa5.a-zA-Z_]")) {s = "." + (int) s.charAt(0) + ".";}sb.append(s);}return sb.toString();}
}

测试

@HeadStyle(fillForegroundColor = 1)
public class User implements Serializable {private static final long serialVersionUID = 1L;@ExcelProperty(value = {"一级"})private String borth;@ExcelProperty(value = {"二级"})@HeadStyle(fillForegroundColor = 29)private String name;@ExcelProperty(value = {"三级"})private String url;@ExcelProperty(value = {"性别"})private String sex;// 日期校验@ExcelProperty(value = "图片")private WriteCellData<Void> imgs;// 日期校验@ExcelProperty(value = "字节图片")private byte[] byteArray;
}
	//测试excel多级联动下拉@GetMapping("/test4")public void test4(HttpServletResponse response, int n) throws Exception {long l = System.currentTimeMillis();Map<String, List<String>> m1 = new HashMap<>();m1.put(null, new ArrayList<>());for (int i = 0; i < n; i++) {m1.get(null).add("A" + (i == 0 ? "" : i));m1.get(null).add("AA" + (i == 0 ? "" : i));m1.get(null).add("AAA" + (i == 0 ? "" : i));m1.get(null).add("AAAA" + (i == 0 ? "" : i));}Map<String, List<String>> m2 = new HashMap<>();for (String s : m1.get(null)) {List<String> a = new ArrayList<>();for (int i = 1; i < 11; i++) {a.add(s + "-" + i + "B");}m2.put(s, a);}List<String> v2 = m2.values().stream().flatMap(List::stream).collect(Collectors.toList());Map<String, List<String>> m3 = new HashMap<>();for (String s : v2) {int t = 50001;List<String> a = new ArrayList<>();for (int i = 1; i < t; i++) {a.add(s + "-" + i + "C");if (i > 1) {t = 1;}}m3.put(s, a);System.out.println(s);}DropdownWriteHandler handler = new DropdownWriteHandler(User.class);ExcelLinkageDropdown dropdown11 = new ExcelLinkageDropdown();dropdown11.setFieldName("borth");dropdown11.setValue(m1);ExcelLinkageDropdown dropdown111 = new ExcelLinkageDropdown();dropdown111.setFieldName("name");dropdown111.setValue(m2);ExcelLinkageDropdown dropdown1111 = new ExcelLinkageDropdown();dropdown1111.setFieldName("url");dropdown1111.setValue(m3);Map<String, List<String>> sexMap = new HashMap<>();sexMap.put(null, m3.values().stream().flatMap(List::stream).collect(Collectors.toList()));ExcelLinkageDropdown sex = new ExcelLinkageDropdown();sex.setFieldName("sex");sex.setValue(sexMap);long l2 = System.currentTimeMillis();System.out.println("设置耗时" + (l2 - l));handler.setLinkageDropdown(null, dropdown11, dropdown111, dropdown1111);handler.setLinkageDropdown(null, sex);long l3 = System.currentTimeMillis();System.out.println("设置耗时" + (l3 - l2));ExcelUtils.downLoad(Lists.newArrayList(handler), ExcelTools.getOutputStream("测试excel多级联动下拉", response), User.class, null, "测试excel多级联动下拉");}

好了,又不想写了,就到此为止吧,有问题评论留言,看到能回就回。

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

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

相关文章

Python中的字符频率统计:探索最频繁字符

Python中的字符频率统计&#xff1a;探索最频繁字符 一、引言 在Python编程中&#xff0c;字符串处理是一个常见的任务。有时&#xff0c;我们可能需要了解字符串中某个字符的出现频率&#xff0c;或者找出出现次数最多的字符。这种需求在文本分析、数据清洗、密码学等多个领…

python abs函数怎么用

abs()函数是Python的数字函数&#xff0c;用以返回数字的绝对值。 语法 以下是 abs() 方法的语法&#xff1a; abs( x ) 参数 x -- 数值表达式&#xff0c;可以是整数&#xff0c;浮点数&#xff0c;复数。 返回值 函数返回 x&#xff08;数字&#xff09;的绝对值&#x…

(十五)Servlet教程——Servlet文件上传

JSP和HTML标签一起使用&#xff0c;来允许用户把文件上传到服务器。 首先我们需要创建一个前端界面&#xff0c;创建上传文件表单时&#xff0c;需要注意以下几点&#xff1a; (1) 表单的method属性必须设置为POST方法&#xff0c; 不能使用GET方法。 (2) 表单enctype属性应该…

accelerator入门

一、目录 1 定义 2. DP、DPP的区别 3 实现 4. 测试比较 二、实现 定义 accelerator 是由大名鼎鼎的huggingface发布的&#xff0c;专门适用于Pytorch的分布式训练框架,是torchrun 的封装。 GitHub: https://github.com/huggingface/accelerate 官网教程&#xff1a;https://…

Python中的生成器是什么

生成器的工作原理 只要Python函数的主体中有yield关键字,该函数就是生成器函数。调用生成器函数,返回一个生成器对象。也就是说,生成器函数是生成器工厂。 下面以一个简单的函数说明生成器的行为: def gen123():yield 1yield 2yield 3print(gen123) # <function gen…

8、卷积Convolutions (CNN)

Today we finish off our study of collaborative filtering by looking closely at embeddings—a critical building block of many deep learning algorithms. Then we’ll dive into convolutional neural networks (CNNs) and see how they really work. We’ve used plen…

java: 通过证书访问etcd

一、首先&#xff0c;要使用cfssl生成etcd证书相关的文件(ca.pem server.pem server-key.pem ),然后把server-key.pem进行转换&#xff1a; openssl pkcs8 -topk8 -nocrypt -in server-key.pem -out server.key二、带证书启动etcd ./etcd --name infra0 --cert-file/root/s…

kaggle叶子分类比赛(易理解)

说实话网上很多关于叶子分类比赛的代码能取得的成绩都很好,但对于我这个业余人员太专业了&#xff0c;而且很多文章都有自己的想法&#xff0c;这让我这个仿写沐神代码的小菜鸡甚是头痛。 但好在我还是完成了&#xff0c;虽然结果并不是很好&#xff0c;但是如果跟着沐神走的同…

AI编码时代到来?实现编程梦想的利器—Baidu Comate测评

文章目录 Comate智能编码是什么&#xff1f;Comate支持的环境 Comate应用安装实际操作对话式生成代码生成代码注释智能单测项目测试调优功能 总结 Comate智能编码是什么&#xff1f; 在如今这个拥抱AI的时代&#xff0c;市面上已经产出了很多Ai代码助手&#xff0c;如果你还没…

【LeetCode算法】28. 找出字符串中第一个匹配项的下标

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案四、JAVA截取字符串的常用方法4.1 通过subString()截取字符串* 一、题目 给你两个字符串 haystack 和 needle &#xff0c;请你在…

鸿蒙OpenHarmony南向:【Hi3516标准系统入门(IDE方式)】

Hi3516标准系统入门&#xff08;IDE方式&#xff09; 注意&#xff1a; 从3.2版本起&#xff0c;标准系统不再针对Hi3516DV300进行适配验证&#xff0c;建议您使用RK3568进行标准系统的设备开发。 如您仍然需要使用Hi3516DV300进行标准系统相关开发操作&#xff0c;则可能会出现…

CST电磁仿真查看模型的截面结构和生成Spice模型【入门教程】

通过Logfile查看仿真统计 一次性了解仿真统计! Post-Processing > Manage Results > Logfile 利用CPU Threads、Mesh Cells、Time Steps以及Total Solver Time等Logfile&#xff0c;可以一目了然地了解仿真统计。 &#xff08;1&#xff09;点击Post-Processing选项卡…

PPT如何录制视频?看这里,让你轻松上手!

在现代社会&#xff0c;演示文稿&#xff08;ppt&#xff09;已成为我们工作、学习和生活中不可或缺的一部分。无论是商务报告、教育培训还是产品展示&#xff0c;ppt都能以其直观、生动的形式有效地传达信息。可是你知道ppt如何录制视频吗&#xff1f;本文将为您介绍两种ppt录…

pycharm code行太长显示波浪线取消

实际操作如下&#xff1a;个人比较合适的位置为160,180时有点多 效果&#xff1a;

算法练习17——罗马数字转整数

LeetCode 13 罗马数字转整数 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#xff0c; 罗马数字 2 写做 II &#xff0c;即为两个并列的 1 。12 写做 XII …

c++ memset 指针示例

目录 C 传一个float指针&#xff0c;在函数内部修改指针的值 c memset 指针示例 C 传一个float指针&#xff0c;在函数内部修改指针的值 #include <iostream>// 定义一个函数&#xff0c;它接受一个指向float的指针 void modifyValue(float* ptr) {// 通过解引用指针来…

Tomcat8集群快速搭建指南

作为一名热衷于分布式系统的开发者&#xff0c;你或许对Tomcat8已经不陌生了。然而&#xff0c;单个Tomcat实例的性能和可用性总有瓶颈&#xff0c;要实现高可用性和更强的性能&#xff0c;就需要搭建一个Tomcat集群。在这篇文章中&#xff0c;我将带你一步步搭建一个基于Tomca…

TypeScript学习笔记(三) 数组

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲 TypeScript 中的数组以及元组类型 1、概述 在 JavaScript 中的数组&#xff0c;在 TypeScript 里&#xff0c;可具体分为数组以及元组两种类型 先来简单对比一下区别&#xff1a; JavaScript 中的数组&#xff0c;可以用…

《视觉十四讲》例程运行记录(3)——运行ch6的例程中Ceres和g2o库的安装

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、安装Ceres1. 安装依赖2. 编译安装 二、安装g2o1. 安装依赖项2. 编译安装3. 可能出现的报错(1) 报错一 一、安装Ceres 1. 安装依赖 终端输入&#xff1a; sud…

wow-debug文件说明

wow-debug文件说明 项目地址&#xff1a;https://gitee.com/wow-iot/wow-iot7本文件的的功能为输出打印信息&#xff0c;目前架构debug信息按照模块名称进行区别使用&#xff0c;且支持打印级别设置&#xff1b;加入shell指令&#xff0c;可通过debug XXX on/off 来实时开启或…