开源AGV调度系统OpenTCS中的路由器(router)详解
OpenTCS中的任务分派器router详解
- 1. 引言
- 2. 路由器(router)
- 2.1 代价计算函数(Cost functions)
- 2.2 2.1 Routing groups
- 2.1 默认的停车位置选择
- 2.2 可选停车位置属性
- 2.3 默认的充电位置选择
- 2.4 即时运输订单分配
- 3. 默认任务分派器的配置项
- 4. 参考资料与源码
1. 引言
openTCS是一项著名的开源运输控制系统,我在之前的文章中对该系统也做了初步介绍:
- 开源AGV调度系统 OpenTCS 5.4 开发环境配置与编译运行
- 开源AGV调度系统OpenTCS中的任务分派器(dispatcher)详解
2. 路由器(router)
openTCS中默认的任务路由器(Default router)是openTCS内置的重要策略模块,当然也是允许用户自定义和替换的。
默认路由器会在行驶路线上找到从一点到另一点消耗成本最低的路线。通过使用Dijkstra算法的实现来实现。它考虑已经被锁定的路径,但不考虑其他车辆的位置或被假定的未来行为。因此它不会绕过速度较慢或停在路上的车辆。
2.1 代价计算函数(Cost functions)
可用于评估驾驶过程中路径的成本函数可以通过配置进行选择。以下成本函数/配置选项可用:
DISTANCE (default):路由成本等同于路径长度;
TRAVELTIME:路由成本的计算是以在路径上行驶的预期时间,即路径长度/车辆允许的最大速度;
EXPLICIT_PROPERTIES:路径上行驶车辆的路由成本通过以下两个带键的路径属性中获取 tcs:routingCostForward and tcs:routingCostReverse。
HOPS:模型中每条路径的路由成本为1,得到的路由代价最小路径/点被选中。
2.2 2.1 Routing groups
计算车辆的路径时,对工厂中的不同车辆以不同的方式处理是可以的,如果车辆有不同的特点并且实际上路径行驶有不同的最佳路线,那么这是可取的。
为了实现这一点,模型中的路径或所用的成本函数需要反映出来这种差异。默认情况下不会这样做,因为默认路由器为所有的车辆计算路径同样的方式,除非另有指示。
要让路由器知道它应该单独计算车辆的路由,可以将键tcs:routingGroup的属性设置为任意字符串。(具有相同值集的车辆共享相同的路由表,空字符串为所有车辆的默认值。)
2.1 默认的停车位置选择
当一辆小车被派往停车点时,默认选择最接近(依据路由)且未被占用的停车点。可以通过设置以下关键属性来给车辆分配固定的位置。
tcs:preferredParkingPosition
:模型中的点名。如果此停车点已被占用,则车辆选择附近距离最近的停车点代替。tcs:assignedParkingPosition
:模型中的点名。如果此停车点已被占用,则车辆不会前往到其他停车点,而是保持原地不动。
assignedParkingPosition
优先级高于preferredParkingPosition
。
2.2 可选停车位置属性
停车位置的优先级是可以明确的,车辆也可以按照一种新的停车序列进行重新停车操作。例如将车辆停放在运输订单频繁的第一目的地附近的位置。
要给停车点设置一个优先级,可以用tcs:parkingPositionPriority
键设置一个属性在点上。该属性的值应为十进制整数,值越小,则会导致停车位的优先级更高。
1.3. Default recharging location selection
2.3 默认的充电位置选择
当车辆被派往充电位置时,默认选择最接近(依据路由)且未被占用的充电位置。也可通过为以下键设置属性来给车辆分配固定位置:
preferredRechargeLocation
:如果此充电位置已被占用,则选择附近距离最近的充电位置。assignedRechargeLocation
:如果此充电位置已被选择,则车辆不会被派往到其他充电位置。
assignedRechargeLocation
优先级高于preferredRechargeLocation
。
2.4 即时运输订单分配
系统除了根据默认的流程和规则分配运输订单外,还可以显式分配运输订单(即时)。运输订单的即时分配支持具有预期车辆的运输订单。在这样的情况下,运输订单及其预期车辆通常处于可能进行分配的状态,但在常规调度程序流中被某些过滤条件阻止,因此采取这种方法将会很有用。
Although the immediate assignment of transport orders bypasses some of the filter criteria in the regular dispatcher flow, it works only in specific situations. Regarding the transport order’s state:
尽管传输订单的即时分配绕过了常规调度流程中的一些过滤条件,但它只在特定情况下起作用。考虑运输订单的状态:
- 运输订单的状态必须是可指派的(
DISPATCHABLE
)。 - 运输订单不能是订单序列的一部分。
- 必须设置运输订单的预定车辆。
至于(预定)车辆的状态:
- 车辆的处理状态必须为
IDLE
。 - 车辆状态必须为
IDLE
或CHARGING
。 - 车辆的集成级别必须是
TO_BE_UTILIZED
。 - 车辆必须被报告在已知位置。
- 车辆不得处理订单序列。
除了运输订单和预定车辆的各自状态之外,分派器可能还有其他特定的原因来拒绝即时分配。
3. 默认任务分派器的配置项
默认任务分派器提供以下配置项实现可配置.
defaultdispatcher.orderCandidatePriorities
- Type: Comma-separated list of strings 逗号分隔的字符串列表
- Trigger for changes to be applied: on application start 触发要应用的更改:在应用程序启动时
- Description: Keys by which to prioritize transport order candidates for assignment.
Possible values: - BY_AGE: Sort by transport order age, oldest first.
- BY_DEADLINE: Sort by transport order deadline, most urgent first.
- DEADLINE_AT_RISK_FIRST: Sort orders with deadlines at risk first.
- BY_COMPLETE_ROUTING_COSTS: Sort by complete routing costs, lowest first.
- BY_INITIAL_ROUTING_COSTS: Sort by routing costs for the first destination.
- BY_ORDER_NAME: Sort by transport order name, lexicographically.
defaultdispatcher.orderPriorities
- Type: Comma-separated list of strings
- Trigger for changes to be applied: on application start
- Description: Keys by which to prioritize transport orders for assignment.
Possible values:
BY_AGE: Sort by age, oldest first.
BY_DEADLINE: Sort by deadline, most urgent first.
DEADLINE_AT_RISK_FIRST: Sort orders with deadlines at risk first.
BY_NAME: Sort by name, lexicographically.
defaultdispatcher.vehicleCandidatePriorities
- Type: Comma-separated list of strings
- Trigger for changes to be applied: on application start
- Description: Keys by which to prioritize vehicle candidates for assignment.
Possible values:
BY_ENERGY_LEVEL: Sort by energy level of the vehicle, highest first.
IDLE_FIRST: Sort vehicles with state IDLE first.
BY_COMPLETE_ROUTING_COSTS: Sort by complete routing costs, lowest first.
BY_INITIAL_ROUTING_COSTS: Sort by routing costs for the first destination.
BY_VEHICLE_NAME: Sort by vehicle name, lexicographically.
defaultdispatcher.vehiclePriorities
- Type: Comma-separated list of strings
- Trigger for changes to be applied: on application start
- Description: Keys by which to prioritize vehicles for assignment.
Possible values:
BY_ENERGY_LEVEL: Sort by energy level, highest first.
IDLE_FIRST: Sort vehicles with state IDLE first.
BY_NAME: Sort by name, lexicographically.
defaultdispatcher.deadlineAtRiskPeriod
Type: Integer
Trigger for changes to be applied: on application start
Description: The time window (in ms) before its deadline in which an order becomes urgent.
defaultdispatcher.assignRedundantOrders
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether orders to the current position with no operation should be assigned.
defaultdispatcher.dismissUnroutableTransportOrders
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether unroutable incoming transport orders should be marked as UNROUTABLE.
defaultdispatcher.reroutingImpossibleStrategy
- Type: String
- Trigger for changes to be applied: instantly
- Description: The strategy to use when rerouting of a vehicle results in no route at all.
The vehicle then continues to use the previous route in the configured way.
Possible values:
IGNORE_PATH_LOCKS: Stick to the previous route, ignoring path locks.
PAUSE_IMMEDIATELY: Do not send further orders to the vehicle; wait for another rerouting opportunity.
PAUSE_AT_PATH_LOCK: Send further orders to the vehicle only until it reaches a locked path; then wait for another rerouting opportunity.
defaultdispatcher.parkIdleVehicles
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether to automatically create parking orders for idle vehicles.
defaultdispatcher.considerParkingPositionPriorities
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether to consider parking position priorities when creating parking orders.
defaultdispatcher.reparkVehiclesToHigherPriorityPositions
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether to repark vehicles to parking positions with higher priorities.
defaultdispatcher.rechargeIdleVehicles
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether to automatically create recharge orders for idle vehicles.
defaultdispatcher.keepRechargingUntilFullyCharged
- Type: Boolean
- Trigger for changes to be applied: instantly
- Description: Whether vehicles must be recharged until they are fully charged.
If false, vehicle must only be recharged until sufficiently charged.
defaultdispatcher.idleVehicleRedispatchingInterval
- Type: Integer
- Trigger for changes to be applied: when/after plant model is loaded
- Description: The interval between redispatching of vehicles.
4. 参考资料与源码
本文内容参考:官方文档
该模块源码位于:
openTCS-Strategies-Default/src/main/java/org/opentcs/strategies/basic/dispatching/DefaultDispatcher.java,代码如下:
public DefaultDispatcher(OrderReservationPool orderReservationPool,TransportOrderUtil transportOrderUtil,InternalVehicleService vehicleService,@ApplicationEventBus EventSource eventSource,@KernelExecutor ScheduledExecutorService kernelExecutor,FullDispatchTask fullDispatchTask,Provider<PeriodicVehicleRedispatchingTask> periodicDispatchTaskProvider,DefaultDispatcherConfiguration configuration,RerouteUtil rerouteUtil,OrderAssigner orderAssigner,TransportOrderAssignmentChecker transportOrderAssignmentChecker) {this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");this.vehicleService = requireNonNull(vehicleService, "vehicleService");this.eventSource = requireNonNull(eventSource, "eventSource");this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");this.fullDispatchTask = requireNonNull(fullDispatchTask, "fullDispatchTask");this.periodicDispatchTaskProvider = requireNonNull(periodicDispatchTaskProvider,"periodicDispatchTaskProvider");this.configuration = requireNonNull(configuration, "configuration");this.rerouteUtil = requireNonNull(rerouteUtil, "rerouteUtil");this.orderAssigner = requireNonNull(orderAssigner, "orderAssigner");this.transportOrderAssignmentChecker = requireNonNull(transportOrderAssignmentChecker,"transportOrderAssignmentChecker");}@Overridepublic void initialize() {if (isInitialized()) {return;}LOG.debug("Initializing...");transportOrderUtil.initialize();orderReservationPool.clear();fullDispatchTask.initialize();implicitDispatchTrigger = new ImplicitDispatchTrigger(this);eventSource.subscribe(implicitDispatchTrigger);LOG.debug("Scheduling periodic dispatch task with interval of {} ms...",configuration.idleVehicleRedispatchingInterval());periodicDispatchTaskFuture = kernelExecutor.scheduleAtFixedRate(periodicDispatchTaskProvider.get(),configuration.idleVehicleRedispatchingInterval(),configuration.idleVehicleRedispatchingInterval(),TimeUnit.MILLISECONDS);initialized = true;}@Overridepublic void terminate() {if (!isInitialized()) {return;}LOG.debug("Terminating...");periodicDispatchTaskFuture.cancel(false);periodicDispatchTaskFuture = null;eventSource.unsubscribe(implicitDispatchTrigger);implicitDispatchTrigger = null;fullDispatchTask.terminate();initialized = false;}@Overridepublic boolean isInitialized() {return initialized;}@Overridepublic void dispatch() {LOG.debug("Scheduling dispatch task...");// Schedule this to be executed by the kernel executor.kernelExecutor.submit(fullDispatchTask);}@Overridepublic void withdrawOrder(TransportOrder order, boolean immediateAbort) {requireNonNull(order, "order");checkState(isInitialized(), "Not initialized");// Schedule this to be executed by the kernel executor.kernelExecutor.submit(() -> {LOG.debug("Scheduling withdrawal for transport order '{}' (immediate={})...",order.getName(),immediateAbort);transportOrderUtil.abortOrder(order, immediateAbort);});}@Overridepublic void withdrawOrder(Vehicle vehicle, boolean immediateAbort) {requireNonNull(vehicle, "vehicle");checkState(isInitialized(), "Not initialized");// Schedule this to be executed by the kernel executor.kernelExecutor.submit(() -> {LOG.debug("Scheduling withdrawal for vehicle '{}' (immediate={})...",vehicle.getName(),immediateAbort);transportOrderUtil.abortOrder(vehicle, immediateAbort);});}@Overridepublic void topologyChanged() {if (configuration.rerouteOnTopologyChanges()) {LOG.debug("Scheduling reroute task...");kernelExecutor.submit(() -> {LOG.info("Rerouting all vehicles due to topology change...");rerouteUtil.reroute(vehicleService.fetchObjects(Vehicle.class), ReroutingType.REGULAR);});}}@Overridepublic void reroute(Vehicle vehicle, ReroutingType reroutingType) {LOG.debug("Scheduling reroute task...");kernelExecutor.submit(() -> {LOG.info("Rerouting vehicle due to explicit request: {} ({}, current position {})...",vehicle.getName(),reroutingType,vehicle.getCurrentPosition() == null ? null : vehicle.getCurrentPosition().getName());rerouteUtil.reroute(vehicle, reroutingType);});}@Overridepublic void assignNow(TransportOrder transportOrder)throws TransportOrderAssignmentException {requireNonNull(transportOrder, "transportOrder");TransportOrderAssignmentVeto assignmentVeto= transportOrderAssignmentChecker.checkTransportOrderAssignment(transportOrder);if (assignmentVeto != TransportOrderAssignmentVeto.NO_VETO) {throw new TransportOrderAssignmentException(transportOrder.getReference(),transportOrder.getIntendedVehicle(),assignmentVeto);}orderAssigner.tryAssignments(List.of(vehicleService.fetchObject(Vehicle.class, transportOrder.getIntendedVehicle())),List.of(transportOrder));}
}
相关文章:

开源AGV调度系统OpenTCS中的路由器(router)详解
OpenTCS中的任务分派器router详解 1. 引言2. 路由器(router)2.1 代价计算函数(Cost functions)2.2 2.1 Routing groups2.1 默认的停车位置选择2.2 可选停车位置属性2.3 默认的充电位置选择2.4 即时运输订单分配 3. 默认任务分派器的配置项4. 参考资料与源…...
关于下载 IDEA、WebStorm 的一些心得感想
背景 实习第一天的时候,睿哥便吩咐我下载一些软件,这些软件以后在写项目的时候会用到,他叫我先装IDEA,WebStorm,微信开发者工具,git,还有Navicat。 这些软件能够被我们正常使用,无非就通过三步…...

C#使用Scoket实现服务器和客户端互发信息
20240616 By wdhuag 目录 前言: 参考: 一、服务器端: 1、服务器端口绑定: 2、服务器关闭: 二、客户端: 1、客户端连接: 2、客户端断开: 三、通讯: 1、接收信…...
【经验分享】SpringCloud + MyBatis Plus 配置 MySQL,TDengine 双数据源
概述 因为项目中采集工厂中的设备码点的数据量比较大,需要集成TDengine时序数据库,所以需要设置双数据源 操作步骤 导入依赖 <!-- 多数据源支持 --><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-s…...
Pycharm 忽略文件
安装 .ignore插件 规则示例 罗列一些常遇到.getignore忽略规则的使用示例: 1. 在已忽略文件夹中不忽略指定文件夹: /libs/* !/libs/extend/ 2. 在已忽略文件夹中不忽略指定文件 /libs/* !/libs/extend/fastjson.jar 3.只忽略libs目录…...

爬虫学习。。。。
爬虫的概念: 爬虫是一种自动化信息采集程序或脚本,用于从互联网上抓取信息。 它通过模拟浏览器请求站点的行为,获取资源后分析并提取有用数据,这些数据可以是HTML代码、JSON数据或二进制数据(如图片、视频)…...

美国铁路客运巨头Amtrak泄漏旅客数据,数据销毁 硬盘销毁 文件销毁
旅客的Guest Rewards常旅客积分账户的个人信息被大量窃取。 美国国家客运铁路公司(Amtrak)近日披露了一起数据泄露事件,旅客的Guest Rewards常旅客积分账户的个人信息被大量窃取。 根据Amtrak向马萨诸塞州提交的泄露通知,5月15日…...

LabVIEW与Matlab联合编程的途径及比较
LabVIEW和Matlab联合编程可以通过多种途径实现,包括调用Matlab脚本节点、使用LabVIEW MathScript RT模块、利用ActiveX和COM接口,以及通过文件读写实现数据交换。每种方法都有其独特的优势和适用场景。本文将详细比较这些方法,帮助开发者…...

秋招突击——6/16——复习{(单调队列优化DP)——最大子序和,背包模型——宠物小精灵收服问题}——新作{二叉树的后序遍历}
文章目录 引言复习(单调队列优化DP)——最大子序和单调队列的基本实现思路——求可移动窗口中的最值总结 背包模型——宠物小精灵收服问题思路分析参考思路分析 新作二叉树的后续遍历加指针调换 总结 引言 复习 (单调队列优化DP)…...

SAR动目标检测系列:【4】动目标二维速度估计
在三大类杂波抑制技术(ATI、DPCA和STAP)中,STAP技术利用杂波与动目标在二维空时谱的差异,以信噪比最优为准则,对地杂波抑制的同时有效保留动目标后向散射能量,有效提高运动目标的检测概率和动目标信号输出信杂比,提供理…...

JavaEE多线程(2)
文章目录 1..多线程的安全1.1出现多线程不安全的原因1.2解决多线程不安全的⽅法1.3三种典型死锁场景1.4如何避免死锁问题2.线程等待通知机制2.1等待通知的作用2.2等待通知的方法——wait2.3唤醒wait的方法——notify 1…多线程的安全 1.1出现多线程不安全的原因 线程在系统中…...

中新赛克两款数据安全产品成功获得“可信数安”评估测试证书
6月19日,2024数据智能大会在北京盛大召开。 会上,中国2024年上半年度“可信数安”评估测试证书正式颁发。中新赛克两款参评产品凭借过硬的技术水准和卓越的应用效果,成功获得专项测试证书。 2024年上半年度“可信数安”评估测试通过名单 中新…...

代码随想录——分割回文串(Leetcode 131)
题目链接 回溯 class Solution {List<List<String>> res new ArrayList<List<String>>();List<String> list new ArrayList<String>();public List<List<String>> partition(String s) {backtracking(s, 0);return res;}p…...
Rust 学习方法及学习路线汇总
Rust 学习方法及学习路线汇总 Rust 是一种系统编程语言,旨在提供安全性、并发性和高性能。它是由 Mozilla 公司开发的,于 2010 年首次发布。Rust 能够帮助开发者编写可靠和高效的软件,因此受到了广泛的关注和认可。 如果你有兴趣学习 Rust&…...

一名女DBA的感谢信,到底发生了什么?
昨日我们收到这样一通来电 “早上九点刚上班便收到业务投诉电话,系统卡顿,接口失败率大增,怀疑数据库问题。打开运维平台发现是国产库,生无可恋,第一次生产环境遇到国产库性能问题,没什么排查经验…...

群晖NAS本地部署并运行一个基于大语言模型Llama2的个人本地聊天机器人
前言 本文主要分享如何在群晖 NAS 本地部署并运行一个基于大语言模型 Llama 2 的个人本地聊天机器人并结合内网穿透工具发布到公网远程访问。本地部署对设备配置要求高一些,如果想要拥有比较好的体验,可以使用高配置的服务器设备. 目前大部分大语言模型的产品都是基于网络线上…...

HarmonyOS模拟器(phone-x86-api9)一直卡顿的解决方法
在DevEco Studio 3.1.1 Release版本中的Device Manager中创建本地的模拟器,创建phone-x86-api9模拟器成功,但是启动该新建的模拟器一直显示"HarmonyOS"logo图片,然后一直卡在这里,运行结果如下所示: 检查模…...
排序题目:有序数组的平方
文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题:有序数组的平方 出处:977. 有序数组的平方 难度 2 级 题目描述 要求 给定按非递减顺序排序的整…...

PPT可以转换成Word吗?归纳了三种转换方式
PPT可以转换成Word吗?在当今快节奏的工作和学习环境中,不同格式文件之间的转换变得日益重要。PPT作为演示文稿制作的首选工具,广泛应用于会议演讲、教育培训等多个场景,而Word则是文档编辑与编排的基石。为了便于进一步编辑、分享…...

分布式锁三种方案
基于数据库的分布式锁(基于主键id和唯一索引) 1基于主键实现分布式锁 2基于唯一索引实现分布式锁 其实原理一致,都是采用一个唯一的标识进行判断是否加锁。 原理:通过主键或者唯一索性两者都是唯一的特性,如果多个…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...