Excel Module: Iteration #1 EasyExcel生成下拉列表模版时传入动态参数查询下拉数据

系列文章

  1. EasyExcel生成带下拉列表或多级级联列表的Excel模版+自定义校验导入数据(修订)

目录

  • 系列文章
  • 前言
  • 仓库
  • 一、实现
    • 1.1 下拉元数据对象
    • 1.2 构建下拉元数据的映射关系
    • 1.3 框架方式
      • 1.3.1 框架实现
      • 1.3.2 框架用例
        • 模版类
        • 加载下拉业务
        • 导出接口
    • 1.4 EasyExcel方式
      • 1.4.1 EasyExcel用例
        • 导出接口
  • 二、效果
    • 2.1 下拉sheet
    • 2.2 业务sheet
  • 附录
    • SpELHelper


前言

有同好提过这问题, 有一定的使用场景, 最近设计了一下并落地了, 支持直接使用框架方式或直接使用EasyExcel方式.

该迭代仅限使用SpEL能力最新版本代码, 历史版本忽略.


仓库

仓库: excel-common-spring-boot-starter (请参考最新代码, 文档内容更新不会很勤快, 没License看得上就随便用)


一、实现

核心逻辑: 创建下拉元数据对象时需要通过解析SpEL表达式获取下拉数据, 该能力提供的上下文对象EvaluationContext支持传入自定义参数, 并可以根据表达式指定的参数名从上下文中获取到指定参数并传给指定的调用方法.

调用方式: 使用框架和直接使用EasyExcel的两种方式, 在传入动态参数的方式上有所不同, 用例 部分会通过代码说明.

例如: SpEL表达式为 @xxxBean.findAllByTypeAndCode(#type, #code)

  • @开头指定选择SpringBean的名称
  • .findAllByTypeAndCode指定调用该bean的方法
  • #type和#code指定从表达式上下文中解析名为 type 和 code 的 变量, 并传入findAllByTypeAndCode()方法.

1.1 下拉元数据对象

解析表达式时, 通过Visitor将参数设置到上下文中.

  • SpELHelper 见 附录 部分
/*** @author hp*/
@Slf4j
@Getter
@Setter
public abstract class AbstractExcelSelectModel<T> {protected int headLayerCount;protected T options;protected String columnName;protected int columnIndex;protected String parentColumnName;protected int parentColumnIndex;protected int firstRow;protected int lastRow;public AbstractExcelSelectModel(@Nonnull Field field, @Nonnull ExcelSelect excelSelect, @Nullable ExcelProperty excelProperty, int defaultSort, @Nullable Map<String, Object> parameters) {final Optional<ExcelProperty> excelPropertyOpt = Optional.ofNullable(excelProperty);this.headLayerCount = excelPropertyOpt.map(property -> property.value().length).orElse(1);this.firstRow = Math.max(excelSelect.firstRow(), this.headLayerCount);this.lastRow = excelSelect.lastRow();this.parentColumnName = excelSelect.parentColumnName();this.columnName = excelPropertyOpt.map(property -> property.value()[this.headLayerCount - 1]).orElse(field.getName());this.columnIndex = excelPropertyOpt.map(property -> property.index() > -1 ? property.index() : defaultSort).orElse(defaultSort);this.options = resolveOptions(excelSelect, parameters);}public boolean hasParentColumn() {return StrUtil.isNotEmpty(this.parentColumnName);}@SuppressWarnings("unchecked")@Nullableprotected T resolveOptions(@Nonnull ExcelSelect excelSelect, @Nullable Map<String, Object> parameters) {final ExcelOptions excelOptions = excelSelect.options();if (StrUtil.isEmpty(excelOptions.expression())) {log.warn("The ExcelSelect on {} has no options whatsoever.", this.columnName);return null;}final SpELHelper spELHelper = SpringUtil.getBean(SpELHelper.class);return (T) spELHelper.newGetterInstance(excelOptions.expression()).apply(null,// 在这里向上下文中设置自定义变量.(evaluationContext -> Optional.ofNullable(parameters).ifPresent(map -> map.forEach(evaluationContext::setVariable))));}
}

1.2 构建下拉元数据的映射关系

Convention: @Nullable Map<String, Object> parameters 参数定义为Map集合类型

提交动态数据的入口, 同时根据实际列注解情况构建下拉元数据的映射关系


/*** @author hp*/
@Slf4j
@UtilityClass
public class ExcelSelectHelper {@Nullablepublic static <T> Map<Integer, ? extends AbstractExcelSelectModel<?>> createSelectionMapping(@Nonnull Class<T> dataClass) {return createSelectionMapping(dataClass, null);}@Nullablepublic static <T> Map<Integer, ? extends AbstractExcelSelectModel<?>> createSelectionMapping(@Nonnull Class<T> dataClass, @Nullable Map<String, Object> parameters) {final Field[] fields = ReflectUtil.getFields(dataClass);final AtomicInteger fieldIndex = new AtomicInteger(0);final Map<Integer, ? extends AbstractExcelSelectModel<?>> selectionMapping = Arrays.stream(fields).map(field -> {final ExcelSelect excelSelect = AnnotatedElementUtils.getMergedAnnotation(field, ExcelSelect.class);if (Objects.isNull(excelSelect)) {log.debug("No ExcelSelect annotated on {}, skip processing", field.getName());fieldIndex.getAndIncrement();return null;}final ExcelProperty excelProperty = AnnotatedElementUtils.getMergedAnnotation(field, ExcelProperty.class);AbstractExcelSelectModel<?> excelSelectModel;if (StrUtil.isNotEmpty(excelSelect.parentColumnName())) {excelSelectModel = new ExcelCascadeModel(field, excelSelect, excelProperty, fieldIndex.getAndIncrement(), parameters);} else {excelSelectModel = new ExcelSelectModel(field, excelSelect, excelProperty, fieldIndex.getAndIncrement(), parameters);}return excelSelectModel;}).filter(Objects::nonNull).collect(Collectors.toMap(AbstractExcelSelectModel::getColumnIndex, Function.identity(), (a, b) -> a));if (MapUtil.isEmpty(selectionMapping)) {return null;}// 设置父列索引final Map<String, Integer> columnNamedMapping = selectionMapping.values().stream().collect(Collectors.toMap(AbstractExcelSelectModel::getColumnName, AbstractExcelSelectModel::getColumnIndex));selectionMapping.forEach((k, v) -> {if (v.hasParentColumn() && columnNamedMapping.containsKey(v.getParentColumnName())) {v.setParentColumnIndex(columnNamedMapping.get(v.getParentColumnName()));}});return selectionMapping;}
}

1.3 框架方式

框架方式基于SpringAOP能力, 纯静态配置方式导出模版或数据, 所以无法通过人为调用的方式提交动态参数.

1.3.1 框架实现

同动态指定导出文件名称的方式相同, 在导出时, 通过向HttpServletRequest对象中设置指定Key(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY)的Attributes, 根据约定, 框架将通过该指定Key查询是否存在参数, 有则使用.

框架通过Enhance类配置ExcelWriterBuilderExcelWriterSheetBuilder, 并且框架考虑自动导出多sheet情况, 所以在enhanceExcel()中获取动态参数并处理.

/*** @author hp*/
@Slf4j
public class ExcelSelectExcelWriterBuilderEnhance implements ExcelWriterBuilderEnhance {protected final AtomicInteger selectionColumnIndex = new AtomicInteger(0);protected Map<Class<?>, Map<Integer, ? extends AbstractExcelSelectModel<?>>> selectionMapMapping = Maps.newHashMap();@SuppressWarnings("unchecked")@Overridepublic ExcelWriterBuilder enhanceExcel(ExcelWriterBuilder writerBuilder,ResponseExcel responseExcel,Collection<? extends Class<?>> dataClasses,HttpServletRequest request,HttpServletResponse response) {final Object attribute = Objects.requireNonNull(request).getAttribute(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY);final Map<String, Object> parameters = Optional.ofNullable(attribute).map(attr -> {Preconditions.checkArgument(attr instanceof Map<?, ?>);return (Map<String, Object>) attribute;}).orElse(null);dataClasses.forEach(dataClass -> selectionMapMapping.put(dataClass, ExcelSelectHelper.createSelectionMapping(dataClass, parameters)));return writerBuilder.registerWriteHandler(new SelectDataWorkbookWriteHandler());}@Overridepublic ExcelWriterSheetBuilder enhanceSheet(ExcelWriterSheetBuilder writerSheetBuilder,Integer sheetNo,String sheetName,Class<?> dataClass,Class<? extends HeadGenerator> headEnhancerClass,String templatePath) {if (selectionMapMapping.containsKey(dataClass)) {final Map<Integer, ? extends AbstractExcelSelectModel<?>> selectionMapping = selectionMapMapping.get(dataClass);writerSheetBuilder.registerWriteHandler(new SelectDataSheetWriteHandler(selectionColumnIndex, selectionMapping));}return writerSheetBuilder;}
}

1.3.2 框架用例

模版类
/*** @author hp*/
@EqualsAndHashCode(callSuper = true)
@Data
public class DynamicParametersExcelTemplate extends ExcelTemplate {@ExcelSelect(options = @ExcelOptions(expression = "@excelSelectDynamicDataHandler.findAll()"))@ExcelProperty("动态单列下拉列表")private LocalDate dynamicSelectColumn;@ExcelProperty("非下拉列No3")private String noneDropdownNo3;@ExcelSelect(options = @ExcelOptions(expression = "@excelSelectDynamicParameterDataHandler.findAllForParentByType(#type)"))@ExcelProperty("动态参数父列")private Integer dynamicParameterParentColumn;@ExcelProperty("非下拉列No1")private String noneDropdownNo1;@ExcelProperty("非下拉列No2")private String noneDropdownNo2;@ExcelSelect(parentColumnName = "动态参数父列",options = @ExcelOptions(expression = "@excelSelectDynamicParameterDataHandler.findAllForChildrenByValueGt(#value)"))@ExcelProperty("动态参数子列")private Integer dynamicParameterChildColumn;
}
加载下拉业务
/*** @author hp*/
@Component
public class ExcelSelectDynamicParameterDataHandler {public List<String> findAllForParentByType(String type) {final Map<String, List<String>> map = Maps.newHashMap();map.put("TYPE-A", List.of("1", "2", "3", "4"));map.put("TYPE-B", List.of("一", "二", "三", "四"));return map.getOrDefault(type, Collections.emptyList());}public Map<String, List<Integer>> findAllForChildrenByValueGt(Integer limitation) {final Map<String, List<Integer>> map = Maps.newHashMap();map.put("1", Stream.of(10, 20, 30, 40, 50, 60).filter(i-> i> limitation).toList());map.put("2",  Stream.of(30, 40, 50, 60, 70, 80, 90).filter(i-> i> limitation).toList());return map;}
}
导出接口
@ResponseExcel(name = "DynamicParametersExcelTemplate",sheets = {@Sheet(sheetName = "sheet", sheetNo = 0),},enhancement = {ExcelSelectExcelWriterBuilderEnhance.class}
)
@PostMapping("template/sheet/single/DynamicParametersExcelTemplate")
public List<DynamicParametersExcelTemplate> singleSheet5(HttpServletRequest request) {final Map<String, Object> map = Maps.newHashMap();map.put("type","TYPE-A");map.put("value",40);request.setAttribute(ExcelConstants.DROPDOWN_QUERY_PARAMS_ATTRIBUTE_KEY, map);return Collections.singletonList(new DynamicParametersExcelTemplate());
}

1.4 EasyExcel方式

原生方式相当于很多框架做的配置工作都给人来完成, 那自然很容易拿到提交动态参数的入口, 可以直接提交参数.

1.4.1 EasyExcel用例

导出接口

在设置handler时, 直接将下拉元数据映射提交给 SelectDataSheetWriteHandler 即可, 此时可以提交动态参数, 用例里偷懒写了null.

  • new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo1ExcelTemplate.class, null))
@PostMapping("/easyexcel/template")
public void template(HttpServletRequest request, HttpServletResponse response) {String filename = "文件名称";final AtomicInteger index = new AtomicInteger(0);String userAgent = request.getHeader("User-Agent");if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {// 针对IE或者以IE为内核的浏览器:filename = java.net.URLEncoder.encode(filename, StandardCharsets.UTF_8);} else {// 非IE浏览器的处理:filename = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);}response.setContentType("application/vnd.ms-excel");response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", filename + ".xlsx"));response.setHeader("Cache-Control", "no-cache");response.setHeader("Pragma", "no-cache");response.setDateHeader("Expires", -1);response.setCharacterEncoding("UTF-8");final ExcelWriterBuilder excelWriterBuilder;try {excelWriterBuilder = EasyExcel.write(response.getOutputStream());} catch (IOException e) {throw new RuntimeException(e);}try (ExcelWriter excelWriter = excelWriterBuilder.registerWriteHandler(new SelectDataWorkbookWriteHandler()).build()) {WriteSheet writeSheet = EasyExcel.writerSheet(0, "sheet名称").head(MultipleSheetNo1ExcelTemplate.class).registerWriteHandler(new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo1ExcelTemplate.class, null))).build();excelWriter.write(new ArrayList<String>(), writeSheet);WriteSheet writeSheet2 = EasyExcel.writerSheet(1, "sheet名称2").head(MultipleSheetNo2ExcelTemplate.class).registerWriteHandler(new SelectDataSheetWriteHandler(index, ExcelSelectHelper.createSelectionMapping(MultipleSheetNo2ExcelTemplate.class, null))).build();excelWriter.write(new ArrayList<String>(), writeSheet2);excelWriter.finish();} catch (Exception e) {log.error("导出Excel文件异常", e);}
}

二、效果

2.1 下拉sheet

可见根据框架用例的条件和查询方法, TYPE-A 类型的数据和 大于40的数据被用于创建下拉选项
在这里插入图片描述

2.2 业务sheet

在这里插入图片描述


附录

SpELHelper

package com.luban.common.base.utils;import cn.hutool.core.util.StrUtil;
import com.luban.common.base.visitor.Visitor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;/*** 很多判断是Groovy语法* <p>* Tips:* <ul>*   <li>字符串单引号. 可以调用方法或访问属性</li>*   <li>属性首字母大小写不敏感</li>*   <li>集合元素: Map用 {@code map['key']} 获取元素, Array/List用 {@code 集合名称[index]} 获取元素</li>*   <li>定义List: {@code {1,2,3,4} 或 {{'a','b'},{'x','y'}} }</li>*   <li>instance of: {@code 'xyz' instanceof T(int)}</li>*   <li>正则: {@code '字符串' matches '正则表达式'}</li>*   <li>逻辑运算符: {@code !非 and与 or或}</li>*   <li>类型: {@code java.lang包下直接用, 其他的要用T(全类名)}</li>*   <li>构造器: {@code new 全类名(构造参数)}</li>*   <li>变量: StandardEvaluationContext当中的变量 {@code  #变量名称 }</li>*   <li>#this: 当前解析的对象</li>*   <li>#root: 上下文的根对象</li>*   <li>Spring Bean引用: {@code @beanName} </li>*   <li>三元表达式和Java一样</li>*   <li>Elvis Operator: {@code Names?:'Unknown'} Names为空提供默认值</li>*   <li>防NPE操作符: {@code PlaceOfBirth?.City} 如果为NULL 防止出现NPE</li>*   <li>筛选集合元素: {@code 集合.?[筛选条件]} 如果是Map集合,Map.Entry为当前判断对象</li>*   <li>筛选第一个满足集合元素: {@code 集合.^[筛选条件]}</li>*   <li>筛选第一个满足集合元素: {@code 集合.$[筛选条件]}</li>*   <li>集合映射,类似StreamAPI的map()再collect(): 使用语法 {@code 集合.![映射规则]}, Map集合类似上述说明</li>*   <li>表达式模版: 默认{@code #{} }, 指定解析模版内部的内容</li>* </ul>** @author hp*/@Slf4j
@Configuration
public class SpELHelper implements ApplicationContextAware {private BeanResolver beanResolver;private final ExpressionParser expressionParser = new SpelExpressionParser();private final ParserContext parserContext = ParserContext.TEMPLATE_EXPRESSION;public <T, R> StandardSpELGetter<T, R> newGetterInstance(String expression) {return new StandardSpELGetter<>(expression, new StandardEvaluationContext());}public <T, R> StandardSpELSetter<T, R> newSetterInstance(Field field) {return new StandardSpELSetter<>(field);}@Overridepublic void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {this.beanResolver = new BeanFactoryResolver(applicationContext);}public class StandardSpELGetter<T, R> implements BiFunction<T, Visitor<EvaluationContext>, R> {private final Expression expression;private final EvaluationContext evaluationContext;private StandardSpELGetter(String expression, EvaluationContext evaluationContext) {if (StrUtil.isNotEmpty(expression) && expression.startsWith(parserContext.getExpressionPrefix())) {this.expression = expressionParser.parseExpression(expression, parserContext);} else {this.expression = expressionParser.parseExpression(expression);}this.evaluationContext = Objects.requireNonNull(evaluationContext);if (this.evaluationContext instanceof StandardEvaluationContext standardEvaluationContext) {standardEvaluationContext.setBeanResolver(beanResolver);}}@SuppressWarnings("unchecked")@Overridepublic R apply(T data, Visitor<EvaluationContext> visitor) {Optional.ofNullable(visitor).ifPresent(v -> v.visit(evaluationContext));return (R) expression.getValue(evaluationContext, data);}public R apply(T data) {return apply(data, Visitor.defaultVisitor());}}public class StandardSpELSetter<T, R> implements BiConsumer<T, Collection<R>> {private final String fieldName;private final boolean isCollection;private final Expression expression;private StandardSpELSetter(Field field) {this.fieldName = Objects.requireNonNull(field).getName();this.expression = expressionParser.parseExpression(fieldName);this.isCollection = Collection.class.isAssignableFrom(Objects.requireNonNull(field).getType());}@Overridepublic void accept(T data, Collection<R> result) {if (isCollection) {this.expression.setValue(data, result);} else {int size = result.size();if (size == 1) {this.expression.setValue(data, result.stream().findFirst().get());} else {log.error("write join result to {} error: Too many results, field is {}, data is {}", data, fieldName, result);}}}}
}

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

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

相关文章

灵活QinQ

拓扑图 配置 sysname AR1 # interface GigabitEthernet0/0/0.10dot1q termination vid 10ip address 12.1.1.1 255.255.255.0 arp broadcast enable # interface GigabitEthernet0/0/0.20dot1q termination vid 20ip address 21.1.1.1 255.255.255.0 arp broadcast enable # …

重学SpringBoot3-SPI机制

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-SPI机制 什么是 SPI&#xff1f;Spring Boot 中的 SPI 机制spring.factories 文件自动配置的实现启动流程中的作用 SPI实际应用步骤 1: 新建模块步骤 2:…

(超详细讲解)实现将idea的java程序打包成exe (新版,可以在没有java的电脑下运行,即可以发给好朋友一起玩)

目录 实现打包到exe大概步骤 工具准备 1.将java程序文件打包成jar文件 2.准备好jre文件 3.使用exe4j软件打包好 4.最终打包 实现打包到exe大概步骤 1.打包需要满足的条件&#xff1a;将java文件转成jar文件的工具exe4j、 以及需要满足jdk1.8以上&#xff08;因安装exe4…

Embedding技术学习

可能很多人并没有关注Embedding技术&#xff0c;但实际上它是GPT非常重要的基础&#xff0c;准备的说&#xff0c;它是GPT模型中理解语言/语义的基础。 【解释什么是Embedding】 对于客观世界&#xff0c;人类通过各种文化产品来表达&#xff0c;比如&#xff1a;语言&#x…

如何快速提取出一个文件里面全部指定类型的文件的全部路径

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 打开工具&#xff0c;切换到第五个模块&#xff0c;文件批量复制模块&#xff08;快捷键&#xff1a;Ctrl5&#xff09; 点击右边的“搜索添加”按钮&#…

unordered系列关联式容器底层哈希结构的介绍,哈希表的模拟实现(哈希冲突的解决方法采用闭散列线性探测)

目录 前言 unordered系列关联式容器之所以处理数据的效率比较高&#xff0c;是因为底层使用了哈希结构&#xff0c;哈希结构的优点是&#xff1a;不经过任何比较&#xff0c;一次直接从表中得到要搜索的元素&#xff0c;通过某种函数(hashFunc)使元素的存储位置与它的关键码之…

mapreduce | 自定义Partition分区(案例2)

1.需求 统计每个手机号消费总金额&#xff0c;按照消费金额降序排序&#xff0c;最终联通、电信、移动分别写入不同的文件。 130、131、132&#xff08;联通&#xff09; 133&#xff08;电信&#xff09; 135、136、137、138、139 &#xff08;移动&#xff09; 手机号,消费记…

全局变量在函数中的使用:Python 编程技巧解析

在Python编程中&#xff0c;全局变量是一种在程序的多个部分中共享数据的机制。全局变量在函数中使用时&#xff0c;需要特别注意其作用域和访问方式。本文将详细讲解如何在函数中使用全局变量&#xff0c;并提供示例代码&#xff0c;帮助初学者深入理解这一概念。 基本原理 …

AI作画涉及的深度学习算法

AI作画中使用的深度学习算法多种多样&#xff0c;这些算法主要基于神经网络的结构和训练方式&#xff0c;以生成和改进艺术作品。以下是一些在AI作画中常用的深度学习算法&#xff1a; 生成对抗网络&#xff08;GANs, Generative Adversarial Networks&#xff09;&#xff1a…

Linux —— 线程

Linux —— 线程 什么是线程Linux如何实现线程Winodws如何实现线程使用一下线程pthread_create函数原型参数说明返回值 如何解决 ps -aL 查看线程线程为什么轻量 我们今天进入线程的学习&#xff1a; 什么是线程 我们先来了解一个笼统的概念&#xff1a;简单来说&#xff0c;…

计算机发展史故事【12】

芯片计算机 众所周知&#xff0c;所谓286、386、486 个人电脑等名称的起源&#xff0c;在于它们采用了英特尔公司研制的微处理器X86 系列芯片286、386 和486。然而&#xff0c;这种以数字为电脑命名的奇特现象&#xff0c;却来源于霍夫博士等人发明的世界上第一个微处理器芯片…

动态路由-链路状态路由协议ospf案例

实验拓扑和要求如图 ospf实验 1.设置各个接口地址 2.测试ar5到ar6的连通性 3.配置ospf协议&#xff0c;routerid&#xff0c;area&#xff0c; 详细的网络信息&#xff0c;等待网络收敛后&#xff0c; 查看ospf信息&#xff0c;路由表信息&#xff0c;再次测试连通性 注意区域…

防火墙技术基础篇:网络地址转换(NAT):防火墙技术的核心机制

防火墙技术基础篇&#xff1a;网络地址转换&#xff08;NAT&#xff09;&#xff1a;防火墙技术的核心机制 网络地址转换&#xff08;NAT&#xff09;是现代网络架构中不可或缺的一个组成部分&#xff0c;尤其在防火墙技术的实现中扮演着重要角色。本文旨在全面解读NAT的工作机…

AI算法-高数5.1-线性代数-向量定义、表示和向量间的关系

看线性代数这篇文章&#xff08;AI算法-高数5-线性代数1-基本概念、向量-CSDN博客&#xff09;理解有些吃力的朋友们&#xff0c;可以先学下宋浩老师的这些课程。 宋浩老师&#xff1a; 3.1 n维向量及其运算_哔哩哔哩_bilibili 3.2 向量间的线性关系&#xff08;一&#xff…

MFC编程之设计美丽的对话框

目录 写在前面&#xff1a; Part 1&#xff1a;美美的设计一下计算器的布局 1.描述文字&#xff1a; ​编辑 2.ID&#xff1a; Part 2&#xff1a;美美熟悉一下计算器的工作流程 Part 3&#xff1a;美美设计一下控件功能 1.edit control&#xff1a; 2.相关变量初始化&…

Agilent MSO9404A、Keysight MSO9404A示波器,4 GHz,4 通道,20 GSa/s

Agilent MSO9404A、Keysight MSO9404A、HP MSO9404A 示波器&#xff0c;4 GHz&#xff0c;4 通道&#xff0c;20 GSa/s Keysight MSO9404A 示波器配备 15 英寸 XGA 显示屏&#xff0c;封装深度仅为 9 英寸&#xff08;23 厘米&#xff09;&#xff0c;重量仅为 26 磅&#xff…

AI地名故事:笔岗村

笔岗村&#xff0c;实际上是由笔村和宏岗村两个古老的村落合并而成的。南宋度宗元年&#xff0c;也就是公元1265年&#xff0c;笔村开始建立。随着时间的推移&#xff0c;到了宋代后期&#xff0c;宏岗村也相继建立。这两个村落各自承载着丰富的历史和文化&#xff0c;最终在历…

铁山靠之数学建模 - Matlab入门

Matlab基础 1. Matlab界面与基本操作1.1 matlab帮助系统1.2 matlab命令1.3 matlab功能符号1.4 matlab的数据类型1.5 函数计算1.6 matlab向量1.7 matlab多项式1.8 M文件1.9 函数文件1.10 matlab的程序结构1.11 echo、warning和error函数1.12 交互输入1.13 程序调试1.14 设置断点…

‌‍‬⁣⁡​⁤⁢​⁢⁡⁣‬‍‌​​‬ ​‍⁤‬ ‬⁡⁡⁡‍‌‬⁡⁡⁢‬⁤⁢⁢⁤​‍‌​​‬ ​⁣‌绘唐3一键追爆款反推软件哪家好

‌‍‬⁣⁡​⁤⁢​⁢⁡⁣‬‍‌​​‬ ​‍⁤‬ ‬⁡⁡⁡‍‌‬⁡⁡⁢‬⁤⁢⁢⁤​‍‌​​‬ ​⁣‌绘唐3一键追爆款反推软件哪家好 支持单镜精绘&#xff0c;可以根据故事情节进行单镜头绘图调整&#xff0c;并支持对SD各类模型的自定义调整。 支持一键完成图文视频合成&am…

Windows11“重置此电脑”后,Edge浏览器在微软应用商店显示“已安装”,但是开始菜单搜索不到的解决办法

Windows11“重置此电脑”后&#xff0c;Edge浏览器在微软应用商店显示“已安装”&#xff0c;但是开始菜单搜索不到的解决办法 为什么重新使用Edge&#xff1f;问题描述不该更新可用更新问过AI&#xff08;通义千问&#xff09;&#xff0c;并且AI提供方法全都无效。现象 操作步…