openTCS二次开发简要事项(详细版)

2024-03-07 03:40

本文主要是介绍openTCS二次开发简要事项(详细版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.juice guice 如何使用注入

在opentcs中使用注入非常的简单,只需要在类的构造方法中指定参数即可。例如需要用到TCSObjectService这个服务,在参数中指定,然后接收此参数,需要检测非空。

我们可以注意到一个有意思的现象,即打了@Inject标记的类它的参数个数是可变的,类型随意指定。这样的类无法被简单的调用,因为java再编译的过程中就会判断我们新建或调用的类传递的参数是否合法,但是通过反射我们可以获取到类的一些详细信息,比如需要哪些参数,这就可以根据需要来初始化调用它。

但是这么麻烦有什么用处呢,而且反射是比较慢的。其中的一个用处就是开发插件,插件的编译是独立于主体软件的,软件要加载什么插件也是不知道的,可以把一些信息写在插件里面或储存在文本文件里,主体启动的时候可以主动去初始化插件里的类。

  public OrderHandler(TransportOrderService orderService,VehicleService vehicleService,DispatcherService dispatcherService,@KernelExecutor ExecutorService kernelExecutor,
//                      @ServiceCallWrapper CallWrapper callWrapper,@Nonnull TCSObjectService objectService) {this.orderService = requireNonNull(orderService, "orderService");this.vehicleService = requireNonNull(vehicleService, "vehicleService");this.dispatcherService = requireNonNull(dispatcherService, "dispatcherService");this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
//    this.callWrapper = requireNonNull(callWrapper, "callWrapper");this.objectService = requireNonNull(objectService, "objectService");}

注入的原理是这些类的实例化都是通过专门的构造器来负责,它会读取类所需要的参数,把已经实例化的参数依次传递减去。可达到一个服务被不同的类所使用,但是不用负责创建(实例化),因为某些服务只能创建一次,比如opentcs的订单服务,但是需要用到的场合确是很多的。

openTCS-Kernel/src/guiceConfig/java/org/opentcs/kernel/RunKernel.java
public static void main(String[] args)throws Exception {System.setSecurityManager(new SecurityManager());Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(false));System.setProperty(org.opentcs.util.configuration.Configuration.PROPKEY_IMPL_CLASS,org.opentcs.util.configuration.XMLConfiguration.class.getName());Environment.logSystemInfo();LOG.debug("Setting up openTCS kernel {}...", Environment.getBaselineVersion());Injector injector = Guice.createInjector(customConfigurationModule());injector.getInstance(KernelStarter.class).startKernel();
}private static Module customConfigurationModule() {List<KernelInjectionModule> defaultModules= Arrays.asList(new DefaultKernelInjectionModule(),new DefaultDispatcherModule(),new DefaultRouterModule(),new DefaultSchedulerModule(),new DefaultRecoveryEvaluatorModule());ConfigurationBindingProvider bindingProvider = configurationBindingProvider();for (KernelInjectionModule defaultModule : defaultModules) {defaultModule.setConfigBindingProvider(bindingProvider);}return Modules.override(defaultModules).with(findRegisteredModules(bindingProvider));
}private static List<KernelInjectionModule> findRegisteredModules(ConfigurationBindingProvider bindingProvider) {List<KernelInjectionModule> registeredModules = new LinkedList<>();for (KernelInjectionModule module : ServiceLoader.load(KernelInjectionModule.class)) {LOG.info("Integrating injection module {}", module.getClass().getName());module.setConfigBindingProvider(bindingProvider);registeredModules.add(module);}return registeredModules;
}private static ConfigurationBindingProvider configurationBindingProvider() {return new Cfg4jConfigurationBindingProvider(Paths.get(System.getProperty("opentcs.base", "."),"config","opentcs-kernel-defaults-baseline.properties").toAbsolutePath(),Paths.get(System.getProperty("opentcs.base", "."),"config","opentcs-kernel-defaults-custom.properties").toAbsolutePath(),Paths.get(System.getProperty("opentcs.home", "."),"config","opentcs-kernel.properties").toAbsolutePath());
}

可以看到入口函数main在内核源码目录的guiceConfig下面,所有模块的源码目录下都有这样一个目录,它控制着将我们写的类跟接口做绑定。
上面比较关键的地方是创建一个injector,然后再用它实例化kernelstarter。

⚠️opentcs这种在guiceconfig目录下面创建文件记录接口绑定的做法实际上是java自带的一套标准(SPI),跟guice这个框架没有什么关系,取这个名字纯粹是因为用了guice,并不是guice规定要写这么个配置文件。

1.3 createInjector vs getInstance 有何区别

创建注入器是前提,使用注入器实例化目标类是最终目的。前一步创建的东西是基础组建,后一步是启动最终的目标类。

具体的启动过程

customConfigurationModule()

我们先看findRegisteredModules

for (KernelInjectionModule module : ServiceLoader.load(KernelInjectionModule.class))

这句话比较关键,很容易让人摸不着头脑。debug后仔细看了module的名字才知道是所有基于KernelInjectionModule扩展类的名字,那么这个ServiceLoader load做了什么事情才可以达到如此效果呢。查询了解到这是java自在的功能,我们暂且不管它是什么。只有知道可以通过它获取到哪些类扩展了这个内核注入模块,会想一下扩展车辆适配器熟悉需要扩展内核注入模块,然后还需要提供一个文本文件,这个文件文件的名字就是内核注入模块的全称内容自然就是扩展类的全称。可以发现所有扩展模块HTTP接口、TCP接口、RMI接口都有这样一个文件,而这个MERA-INF.services路径是标准规定,ServiceLoader会根据load方法提供的类名去各模块下的标准目录寻找此类名的文本文件,再把文件里的内容读取出来,这样我们就可以知道有哪些类扩展了目标类。

查看ServiceLoader的源代码,可以发现有一个路径前缀,正是上述的标准路径。

public final class ServiceLoader<S>implements Iterable<S>
{private static final String PREFIX = "META-INF/services/";

请添加图片描述

⚠️ServiceLoader是SPI中的关键函数,用于查找哪些jar包里面有META-INF/services/这个文件夹。

  module.setConfigBindingProvider(bindingProvider);registeredModules.add(module

接着设置模块的privider,这个又是什么东西呢,其实就是一些路径和环境变量

ConfigurableInjectionModule->KernelInjectionModule

这个setConfigBindingProvider方法在ConfiguragleInjectionModule中定义,KernelInjectionModule是扩展了此类。

public class Cfg4jConfigurationBindingProviderimplements ConfigurationBindingProvider {/*** This class's logger.*/private static final Logger LOG = LoggerFactory.getLogger(Cfg4jConfigurationBindingProvider.class);/*** The key of the (system) property containing the reload interval.*/private static final String PROPKEY_RELOAD_INTERVAL = "opentcs.cfg4j.reload.interval";/*** Default configuration file name.*/private final Path defaultsPath;/*** Supplementary configuration files.*/private final Path[] supplementaryPaths;/*** The (cfg4j) configuration provider.*/private final ConfigurationProvider provider;

下面是比较关键的一步,一开始创建的四个类其实是空的,现在需要把它们替换成继承了这些类的类,说起来比较绕口。我们注意到创建的不是四个基础类吗,但是实际上却远不止这些类。还记得KernelInjectionModule被扩展了很多次吗,每个车辆适配器加API接口就有四五个了,而后面的四个类跟调度相关只有一个唯一的扩展类,这个涉及到注入的另外一个概念了(singleton 单、多绑定)。

( new DefaultKernelInjectionModule(),new DefaultDispatcherModule(),new DefaultRouterModule(),new DefaultSchedulerModule(),new DefaultRecoveryEvaluatorModule())
return Modules.override(defaultModules)
.with(findRegisteredModules(bindingProvider))

经过这一步内核只是找到了注册的模块,从名字上我们可以看出,此时还没有开始实例化。

直到createInjector这一步模块开始实例化了,这里面发生了什么呢。

Injector injector = Guice.createInjector(customConfigurationModule());public static Injector createInjector(Stage stage, Iterable<? extends Module> modules) {
return new InternalInjectorCreator().stage(stage).addModules(modules).build();
}

更加具体的过程需要了解juice注入的原理,我们目前先了解大概。这个createInjector帮我们把所有模块给实例化了,然后得到一个注入器injector,使用它再去启动KernelStarter。

现在还有一个疑惑,juice帮我们初始化的这些模块彼此之间又是有依赖关系的,如何保证没有依赖关系的模块先启动,有依赖关系的模块按照顺序启动呢。

⚠️分析每个类的初始化参数就能找出哪些类是没有依赖关系的。

[20210323-20:13:48-312] INFO main o.o.c.cfg4j.Cfg4jConfigurationBindingProvider.buildSource(): Using default configuration file /Users/touchmii/IntelliJProjects/OpenTCS-4.17/openTCS-Kernel/build/install/openTCS-Kernel/./config/opentcs-kernel-defaults-baseline.properties...
[20210323-20:13:48-344] WARNING main o.o.c.cfg4j.Cfg4jConfigurationBindingProvider.buildSource(): Supplementary configuration file /Users/touchmii/IntelliJProjects/OpenTCS-4.17/openTCS-Kernel/build/install/openTCS-Kernel/./config/opentcs-kernel-defaults-custom.properties not found, skipped.
[20210323-20:13:48-347] INFO main o.o.c.cfg4j.Cfg4jConfigurationBindingProvider.buildSource(): Using overrides from supplementary configuration file /Users/touchmii/IntelliJProjects/OpenTCS-4.17/openTCS-Kernel/build/install/openTCS-Kernel/./config/opentcs-kernel.properties...
[20210323-20:13:48-368] INFO main o.o.c.cfg4j.Cfg4jConfigurationBindingProvider.reloadInterval(): Using configuration reload interval of 10000 ms.
[20210323-20:13:48-371] INFO main o.c.provider.ConfigurationProviderBuilder.build() : Initializing ConfigurationProvider with org.opentcs.configuration.cfg4j.CachedConfigurationSource source, org.opentcs.configuration.cfg4j.PeriodicalReloadStrategy reload strategy and org.cfg4j.source.context.environment.DefaultEnvironment environment
[20210323-20:13:48-414] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.virtualvehicle.LoopbackCommAdapterModule
[20210323-20:13:48-419] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.adminwebapi.AdminWebApiModule
[20210323-20:13:48-424] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.servicewebapi.ServiceWebApiModule
[20210323-20:13:48-457] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.rmi.RmiServicesModule
[20210323-20:13:48-462] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.statistics.StatisticsModule
[20210323-20:13:48-471] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.xmlhost.TcpHostInterfaceModule
[20210323-20:13:48-474] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.sockethost.TcpMESInterfaceModule
[20210323-20:13:48-482] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.websockets.WebSocketsHostInterfaceModule
[20210323-20:13:48-496] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.zjw.vehicle.ExampleKernelInjectionModule
[20210323-20:13:48-500] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.vehicle.ModbusAdapterKernelInjectionModule
[20210323-20:13:48-507] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.http.HTTPAdapterKernelInjectionModule
[20210323-20:13:48-511] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.vehicleqian.QianAdapterKernelInjectionModule
[20210323-20:13:48-519] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.vehiclejbh.JbhAdapterKernelInjectionModule
[20210323-20:13:48-522] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.vehicletcp.TCPAdapterKernelInjectionModule
[20210323-20:13:48-535] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.vrep.ExampleKernelInjectionModule
[20210323-20:13:48-556] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.serial.SerialAdapterKernelInjectionModule
[20210323-20:13:48-560] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module com.lvsrobot.apollo.ApolloAdapterKernelInjectionModule
[20210323-20:13:48-570] INFO main o.o.kernel.RunKernel.findRegisteredModules() : Integrating injection module org.opentcs.kernel.extensions.controlcenter.ControlCenterModule
以上为模块查找阶段,以下为模块初始化
[20210323-20:13:50-166] WARNING main 
o.o.k.e.rmi.RmiServicesModule.configure() : SSL encryption disabled, connections will not be secured!
[20210323-20:13:55-527] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: org.opentcs.virtualvehicle.LoopbackCommunicationAdapterFactory
[20210323-20:13:55-537] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.zjw.vehicle.ExampleCommAdapterFactory
[20210323-20:13:55-538] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.vehicle.ExampleCommAdapterFactory
[20210323-20:13:55-572] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.http.ExampleCommAdapterFactory
[20210323-20:13:55-579] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.vehicleqian.ExampleCommAdapterFactory
[20210323-20:13:55-599] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.vehiclejbh.ExampleCommAdapterFactory
[20210323-20:13:55-607] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.vehicletcp.ExampleCommAdapterFactory
[20210323-20:13:55-613] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.vrep.ExampleCommAdapterFactory
[20210323-20:13:55-628] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.serial.ExampleCommAdapterFactory
[20210323-20:13:55-635] INFO main o.o.k.vehicles.VehicleCommAdapterRegistry.<init>() : Setting up communication adapter factory: com.lvsrobot.apollo.ExampleCommAdapterFactory
[20210323-20:14:00-184] INFO main o.o.k.e.rmi.XMLFileUserAccountPersister.loadUserAccounts(): Account data file does not exist, no user accounts available.
[UPTIME: 0s] Connected to MES : /127.0.0.1:8735
[20210323-20:14:01-143] INFO main o.o.kernel.StandardKernel.setState() : Switching kernel to state 'MODELLING'
[20210323-20:14:01-320] INFO Thread-2 o.e.j.u.log.Log.initialized() : Logging initialized @13814ms to org.eclipse.jetty.util.log.Slf4jLog
[20210323-20:14:01-647] INFO Thread-2 s.e.jetty.EmbeddedJettyServer.ignite() : == Spark has ignited ...
[20210323-20:14:01-648] INFO Thread-2 s.e.jetty.EmbeddedJettyServer.ignite() : >> Listening on 127.0.0.1:55100
[20210323-20:14:01-660] INFO Thread-2 o.e.j.server.Server.doStart() : jetty-9.4.18.v20190429; built: 2019-04-29T20:42:08.989Z; git: e1bc35120a6617ee3df052294e433f3a25ce7097; jvm 1.8.0_281-b09
[20210323-20:14:01-868] INFO Thread-2 o.e.j.s.session.DefaultSessionIdManager.doStart() : DefaultSessionIdManager workerName=node0
[20210323-20:14:01-869] INFO Thread-2 o.e.j.s.session.DefaultSessionIdManager.doStart() : No SessionScavenger set, using defaults
[20210323-20:14:01-876] INFO Thread-2 o.e.j.s.session.HouseKeeper.startScavenging() : node0 Scavenging every 600000ms
[20210323-20:14:01-926] INFO Thread-2 o.e.j.server.AbstractConnector.doStart() : Started ServerConnector@7d23cecc{HTTP/1.1,[http/1.1]}{127.0.0.1:55100}
[20210323-20:14:01-927] INFO Thread-2 o.e.j.server.Server.doStart()
public class DefaultKernelInjectionModuleextends KernelInjectionModule {@Overrideprotected void configure() {configureEventHub();configureKernelExecutor();// Ensure that the application's home directory can be used everywhere.File applicationHome = new File(System.getProperty("opentcs.home", "."));bind(File.class).annotatedWith(ApplicationHome.class).toInstance(applicationHome);// A single global synchronization object for the kernel.bind(Object.class).annotatedWith(GlobalSyncObject.class).to(Object.class).in(Singleton.class);// The kernel's data pool structures.bind(TCSObjectPool.class).in(Singleton.class);bind(Model.class).in(Singleton.class);bind(TransportOrderPool.class).in(Singleton.class);bind(NotificationBuffer.class).in(Singleton.class);bind(ObjectNameProvider.class).to(PrefixedUlidObjectNameProvider.class).in(Singleton.class);configurePersistence();bind(VehicleCommAdapterRegistry.class).in(Singleton.class);configureVehicleControllers();bind(AttachmentManager.class).in(Singleton.class);bind(VehicleEntryPool.class).in(Singleton.class);bind(StandardKernel.class).in(Singleton.class);bind(LocalKernel.class).to(StandardKernel.class);configureKernelStatesDependencies();configureKernelStarterDependencies();configureSslParameters();configureKernelServicesDependencies();// Ensure all of these binders are initialized.extensionsBinderAllModes();extensionsBinderModelling();extensionsBinderOperating();vehicleCommAdaptersBinder();}private void configureKernelServicesDependencies() {bind(StandardPlantModelService.class).in(Singleton.class);bind(PlantModelService.class).to(StandardPlantModelService.class);bind(InternalPlantModelService.class).to(StandardPlantModelService.class);bind(StandardTransportOrderService.class).in(Singleton.class);bind(TransportOrderService.class).to(StandardTransportOrderService.class);bind(InternalTransportOrderService.class).to(StandardTransportOrderService.class);bind(StandardVehicleService.class).in(Singleton.class);bind(VehicleService.class).to(StandardVehicleService.class);bind(InternalVehicleService.class).to(StandardVehicleService.class);bind(StandardTCSObjectService.class).in(Singleton.class);bind(TCSObjectService.class).to(StandardTCSObjectService.class);bind(StandardNotificationService.class).in(Singleton.class);bind(NotificationService.class).to(StandardNotificationService.class);bind(StandardRouterService.class).in(Singleton.class);bind(RouterService.class).to(StandardRouterService.class);bind(StandardDispatcherService.class).in(Singleton.class);bind(DispatcherService.class).to(StandardDispatcherService.class);bind(StandardSchedulerService.class).in(Singleton.class);bind(SchedulerService.class).to(StandardSchedulerService.class);}private void configureVehicleControllers() {install(new FactoryModuleBuilder().build(VehicleControllerFactory.class));bind(DefaultVehicleControllerPool.class).in(Singleton.class);bind(VehicleControllerPool.class).to(DefaultVehicleControllerPool.class);bind(LocalVehicleControllerPool.class).to(DefaultVehicleControllerPool.class);}private void configurePersistence() {bind(ModelPersister.class).to(XMLFileModelPersister.class);}@SuppressWarnings("deprecation")private void configureEventHub() {EventBus newEventBus = new SimpleEventBus();bind(EventHandler.class).annotatedWith(ApplicationEventBus.class).toInstance(newEventBus);bind(org.opentcs.util.event.EventSource.class).annotatedWith(ApplicationEventBus.class).toInstance(newEventBus);bind(EventBus.class).annotatedWith(ApplicationEventBus.class).toInstance(newEventBus);// A binding for the kernel's one and only central event hub.BusBackedEventHub<org.opentcs.util.eventsystem.TCSEvent> busBackedHub= new BusBackedEventHub<>(newEventBus, org.opentcs.util.eventsystem.TCSEvent.class);busBackedHub.initialize();bind(new TypeLiteral<org.opentcs.util.eventsystem.EventListener<org.opentcs.util.eventsystem.TCSEvent>>() {}).annotatedWith(org.opentcs.customizations.kernel.CentralEventHub.class).toInstance(busBackedHub);bind(new TypeLiteral<org.opentcs.util.eventsystem.EventSource<org.opentcs.util.eventsystem.TCSEvent>>() {}).annotatedWith(org.opentcs.customizations.kernel.CentralEventHub.class).toInstance(busBackedHub);bind(new TypeLiteral<org.opentcs.util.eventsystem.EventHub<org.opentcs.util.eventsystem.TCSEvent>>() {}).annotatedWith(org.opentcs.customizations.kernel.CentralEventHub.class).toInstance(busBackedHub);}private void configureKernelStatesDependencies() {// A map for KernelState instances to be provided at runtime.MapBinder<Kernel.State, KernelState> stateMapBinder= MapBinder.newMapBinder(binder(), Kernel.State.class, KernelState.class);stateMapBinder.addBinding(Kernel.State.SHUTDOWN).to(KernelStateShutdown.class);stateMapBinder.addBinding(Kernel.State.MODELLING).to(KernelStateModelling.class);stateMapBinder.addBinding(Kernel.State.OPERATING).to(KernelStateOperating.class);bind(OrderPoolConfiguration.class).toInstance(getConfigBindingProvider().get(OrderPoolConfiguration.PREFIX,OrderPoolConfiguration.class));transportOrderCleanupApprovalBinder();orderSequenceCleanupApprovalBinder();}private void configureKernelStarterDependencies() {bind(KernelApplicationConfiguration.class).toInstance(getConfigBindingProvider().get(KernelApplicationConfiguration.PREFIX,KernelApplicationConfiguration.class));}private void configureSslParameters() {SslConfiguration configuration= getConfigBindingProvider().get(SslConfiguration.PREFIX,SslConfiguration.class);SslParameterSet sslParamSet = new SslParameterSet(SslParameterSet.DEFAULT_KEYSTORE_TYPE,new File(configuration.keystoreFile()),configuration.keystorePassword(),new File(configuration.truststoreFile()),configuration.truststorePassword());bind(SslParameterSet.class).toInstance(sslParamSet);}private void configureKernelExecutor() {ScheduledExecutorService executor= new LoggingScheduledThreadPoolExecutor(1,(runnable) -> {Thread thread = new Thread(runnable, "kernelExecutor");thread.setUncaughtExceptionHandler(new UncaughtExceptionLogger(false));return thread;});bind(ScheduledExecutorService.class).annotatedWith(KernelExecutor.class).toInstance(executor);bind(ExecutorService.class).annotatedWith(KernelExecutor.class).toInstance(executor);bind(Executor.class).annotatedWith(KernelExecutor.class).toInstance(executor);}
}

2.扩展HTTP接口

2.1通过HTTP接口发送指令给指定车辆

发送指令给适配器需要获取车辆名称的引用,再调用VehicleService的sendCommAdapter方法即可。在适配器中重写BasicVehicleCommAdapter中的

excute方法,接收VehicleCommAdapterEvent事件。

public String sendCommand(String name, Command command) {try {TCSObjectReference<Vehicle> vehicleReference = vehicleService.fetchObject(Vehicle.class, name).getReference();VehicleCommAdapterEvent event = new VehicleCommAdapterEvent(name, command.getCommand());try {
//      callWrapper.call(() -> vehicleService.sendCommAdapterCommand(vehicleReference, new PublishEventCommand(event)));vehicleService.sendCommAdapterCommand(vehicleReference, new PublishEventCommand(event));} catch (Exception e) {LOG.warn("Can't send command to vehicle");e.getMessage();throw new ObjectUnknownException(("Can't send command to vehicle"));}} catch (Exception e) {e.getMessage();LOG.warn("Can't found vechile name: {}", name);throw new ObjectUnknownException("Unknow Vehicle name: " + name);}return String.format("Send command: %s to Vehicle: %s success.", command.getCommand(),name);}@Override
public void execute(AdapterCommand command) {PublishEventCommand publishCommand = (PublishEventCommand) command;

3.适配器任务

基础适配器有两个列表分别是移动指令列表和以发送指令列表,当判断适配器可以发送命令则从移动指令列表取出添加到已发送列表

BasicVehicleCommAdapter
/*** This adapter's command queue.*/private final Queue<MovementCommand> commandQueue = new LinkedBlockingQueue<>();/*** Contains the orders which have been sent to the vehicle but which haven't* been executed by it, yet.*/private final Queue<MovementCommand> sentQueue = new LinkedBlockingQueue<>();if (getSentQueue().size() < sentQueueCapacity) && !getCommandQueue().isEmpty()curCmd = getCommandQueue().poll();if (curCmd != null) {try {sendCommand(curCmd);//send driver order,adapter implement sendCommand,receive curCmdgetSentQueue().add(curCmd);//add driving order to the queue of sent ordersgetProcessModel().commandSent(curCmd);//Notify the kernel that the drive order has been sent to the vehicle

跟车辆通行的适配器只操作已发送指令列表,使用peek获取要发送的命令,车辆到达预期地点则使用poll删除已发送指令头部,再使用ProcessModel通知内核,代表车辆执行此命令成功。然后获取下一个指令按照上面的步骤重复执行,直到将所有指令执行情况都上报给内核,此时才会判断路径执行成功。

ExampleCommAdapter
curCommand = getSentQueue().peek();
MovementCommand sentcmd = getSentQueue().poll();
getProcessModel().commandExecuted(curCommand);
3.2车辆执行完的移动命令如何通知到内核
/*** Notifies observers that the given command has been executed by the comm adapter/vehicle.** @param executedCommand The command that has been executed.*/public void commandExecuted(@Nonnull MovementCommand executedCommand) {getPropertyChangeSupport().firePropertyChange(Attribute.COMMAND_EXECUTED.name(),null,executedCommand);}/*** Notifies observers that the given command could not be executed by the comm adapter/vehicle.** @param failedCommand The command that could not be executed.*/public void commandFailed(@Nonnull MovementCommand failedCommand) {getPropertyChangeSupport().firePropertyChange(Attribute.COMMAND_FAILED.name(),null,failedCommand);}

DefaultVehicleController 重写PropertyChangeListener的方法实现监听适配器发送过来的事件,如果执行命令失败会取消此车辆的订单

DefaultVehicleController

//属性变更回调函数,使用getProcessModel发送车辆消息监听到车辆属性变更时调用@Overridepublic void propertyChange(PropertyChangeEvent evt) {if (evt.getSource() != commAdapter.getProcessModel()) {return;}handleProcessModelEvent(evt);}
//处理驱动器消息类型,调用不同的处理函数,如指令发送成功或位置变更private void handleProcessModelEvent(PropertyChangeEvent evt) {eventBus.onEvent(new ProcessModelEvent(evt.getPropertyName(),commAdapter.createTransferableProcessModel()));if (Objects.equals(evt.getPropertyName(), VehicleProcessModel.Attribute.POSITION.name())) {updateVehiclePosition((String) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.PRECISE_POSITION.name())) {updateVehiclePrecisePosition((Triple) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.ORIENTATION_ANGLE.name())) {vehicleService.updateVehicleOrientationAngle(vehicle.getReference(),(Double) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.ENERGY_LEVEL.name())) {vehicleService.updateVehicleEnergyLevel(vehicle.getReference(), (Integer) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.LOAD_HANDLING_DEVICES.name())) {vehicleService.updateVehicleLoadHandlingDevices(vehicle.getReference(),(List<LoadHandlingDevice>) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(), VehicleProcessModel.Attribute.STATE.name())) {updateVehicleState((Vehicle.State) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.COMM_ADAPTER_STATE.name())) {updateCommAdapterState((VehicleCommAdapter.State) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.COMMAND_EXECUTED.name())) {commandExecuted((MovementCommand) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.COMMAND_FAILED.name())) {dispatcherService.withdrawByVehicle(vehicle.getReference(), true, false);}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.USER_NOTIFICATION.name())) {notificationService.publishUserNotification((UserNotification) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.COMM_ADAPTER_EVENT.name())) {eventBus.onEvent((VehicleCommAdapterEvent) evt.getNewValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.VEHICLE_PROPERTY.name())) {VehicleProcessModel.VehiclePropertyUpdate propUpdate= (VehicleProcessModel.VehiclePropertyUpdate) evt.getNewValue();vehicleService.updateObjectProperty(vehicle.getReference(),propUpdate.getKey(),propUpdate.getValue());}else if (Objects.equals(evt.getPropertyName(),VehicleProcessModel.Attribute.TRANSPORT_ORDER_PROPERTY.name())) {VehicleProcessModel.TransportOrderPropertyUpdate propUpdate= (VehicleProcessModel.TransportOrderPropertyUpdate) evt.getNewValue();if (currentDriveOrder != null) {vehicleService.updateObjectProperty(currentDriveOrder.getTransportOrder(),propUpdate.getKey(),propUpdate.getValue());}}}
3.4commandExcuted 判断移动指令是否真的执行成功
private void commandExecuted(MovementCommand executedCommand) {requireNonNull(executedCommand, "executedCommand");synchronized (commAdapter) {// Check if the executed command is the one we expect at this point.MovementCommand expectedCommand = commandsSent.peek();if (!Objects.equals(expectedCommand, executedCommand)) {LOG.warn("{}: Communication adapter executed unexpected command: {} != {}",vehicle.getName(),executedCommand,expectedCommand);// XXX The communication adapter executed an unexpected command. Do something!}// Remove the command from the queue, since it has been processed successfully.lastCommandExecuted = commandsSent.remove();// Free resources allocated for the command before the one now executed.Set<TCSResource<?>> oldResources = allocatedResources.poll();if (oldResources != null) {LOG.debug("{}: Freeing resources: {}", vehicle.getName(), oldResources);scheduler.free(this, oldResources);}else {LOG.debug("{}: Nothing to free.", vehicle.getName());}// Check if there are more commands to be processed for the current drive order.if (pendingCommand == null && futureCommands.isEmpty()) {LOG.debug("{}: No more commands in current drive order", vehicle.getName());// Check if there are still commands that have been sent to the communication adapter but// not yet executed. If not, the whole order has been executed completely - let the kernel// know about that so it can give us the next drive order.if (commandsSent.isEmpty() && !waitingForAllocation) {LOG.debug("{}: Current drive order processed", vehicle.getName());currentDriveOrder = null;// Let the kernel/dispatcher know that the drive order has been processed completely (by// setting its state to AWAITING_ORDER).vehicleService.updateVehicleRouteProgressIndex(vehicle.getReference(),Vehicle.ROUTE_INDEX_DEFAULT);vehicleService.updateVehicleProcState(vehicle.getReference(),Vehicle.ProcState.AWAITING_ORDER);}}// There are more commands to be processed.// Check if we can send another command to the comm adapter.else if (canSendNextCommand()) {allocateForNextCommand();}}}/*** Sets the point which a vehicle is expected to occupy next.** @param vehicleRef A reference to the vehicle to be modified.* @param pointRef A reference to the point which the vehicle is expected to* occupy next.* @throws ObjectUnknownException If the referenced vehicle does not exist.* @deprecated Use{@link InternalVehicleService#updateVehicleNextPosition(* org.opentcs.data.TCSObjectReference, org.opentcs.data.TCSObjectReference)} instead.*/@Deprecatedvoid setVehicleNextPosition(TCSObjectReference<Vehicle> vehicleRef,TCSObjectReference<Point> pointRef)throws ObjectUnknownException;/*** Sets a vehicle's index of the last route step travelled for the current* drive order of its current transport order.** @param vehicleRef A reference to the vehicle to be modified.* @param index The new index.* @throws ObjectUnknownException If the referenced vehicle does not exist.* @deprecated Use{@link InternalVehicleService#updateVehicleRouteProgressIndex(* org.opentcs.data.TCSObjectReference, int)} instead.*/@Deprecatedvoid setVehicleRouteProgressIndex(TCSObjectReference<Vehicle> vehicleRef,int index)throws ObjectUnknownException;/*** Sets a transport order's state.* Note that transport order states are intended to be manipulated by the* dispatcher only. Calling this method from any other parts of the kernel may* result in undefined behaviour.** @param ref A reference to the transport order to be modified.* @param newState The transport order's new state.* @throws ObjectUnknownException If the referenced transport order does not* exist.* @deprecated Use {@link InternalTransportOrderService#updateTransportOrderState(* org.opentcs.data.TCSObjectReference, org.opentcs.data.order.TransportOrder.State)} instead.*/@Deprecatedvoid setTransportOrderState(TCSObjectReference<TransportOrder> ref,TransportOrder.State newState)throws ObjectUnknownException;     
@Override
public void run() {transportOrderService.fetchObjects(Vehicle.class).stream().filter(vehicle -> vehicle.hasProcState(Vehicle.ProcState.AWAITING_ORDER)).forEach(vehicle -> checkForNextDriveOrder(vehicle));
}private void checkForNextDriveOrder(Vehicle vehicle) {LOG.debug("Vehicle '{}' finished a drive order.", vehicle.getName());// The vehicle is processing a transport order and has finished a drive order.// See if there's another drive order to be processed.transportOrderService.updateTransportOrderNextDriveOrder(vehicle.getTransportOrder());TransportOrder vehicleOrder = transportOrderService.fetchObject(TransportOrder.class,vehicle.getTransportOrder());if (vehicleOrder.getCurrentDriveOrder() == null) {LOG.debug("Vehicle '{}' finished transport order '{}'",vehicle.getName(),vehicleOrder.getName());// The current transport order has been finished - update its state and that of the vehicle.transportOrderUtil.updateTransportOrderState(vehicle.getTransportOrder(),TransportOrder.State.FINISHED);// Update the vehicle's procState, implicitly dispatching it again.vehicleService.updateVehicleProcState(vehicle.getReference(), Vehicle.ProcState.IDLE);vehicleService.updateVehicleTransportOrder(vehicle.getReference(), null);// Let the router know that the vehicle doesn't have a route any more.router.selectRoute(vehicle, null);// Update transport orders that are dispatchable now that this one has been finished.transportOrderUtil.markNewDispatchableOrders();}else {LOG.debug("Assigning next drive order to vehicle '{}'...", vehicle.getName());// Get the next drive order to be processed.DriveOrder currentDriveOrder = vehicleOrder.getCurrentDriveOrder();if (transportOrderUtil.mustAssign(currentDriveOrder, vehicle)) {if (configuration.rerouteTrigger() == DRIVE_ORDER_FINISHED) {LOG.debug("Trying to reroute vehicle '{}' before assigning the next drive order...",vehicle.getName());rerouteUtil.reroute(vehicle);}// Get an up-to-date copy of the transport order in case the route changedvehicleOrder = transportOrderService.fetchObject(TransportOrder.class,vehicle.getTransportOrder());currentDriveOrder = vehicleOrder.getCurrentDriveOrder();// Let the vehicle controller know about the new drive order.vehicleControllerPool.getVehicleController(vehicle.getName()).setDriveOrder(currentDriveOrder, vehicleOrder.getProperties());// The vehicle is still processing a transport order.vehicleService.updateVehicleProcState(vehicle.getReference(),Vehicle.ProcState.PROCESSING_ORDER);}// If the drive order need not be assigned, immediately check for another one.else {vehicleService.updateVehicleProcState(vehicle.getReference(),Vehicle.ProcState.AWAITING_ORDER);checkForNextDriveOrder(vehicle);}}
}

4.如何监听内核事件

@ApplicationEventBus EventSource eventSourceimplicitDispatchTrigger = new ImplicitDispatchTrigger(this);
eventSource.subscribe(implicitDispatchTrigger);/*** A handler for events emitted by an {@link EventSource}.** @author Stefan Walter (Fraunhofer IML)*/
public interface EventHandler {/*** Processes the event object.** @param event The event object.*/void onEvent(Object event);
}

实现EventHandler接口,重写onEvent方法,再注入EventSource,使用EventSource订阅EventHandler接口的实现类。

当产生事件时会调用onEvent方法,判断event事件的类型即可。

例子,此例子为tcp状态接口,端口号默认为44444,连接后不会自动断开,有新的事件都会发给客户端。

创建一个继承EventHandler接口的类ConnectionHandler,重写onEvent方法

/*** The task handling client connections.*/
class ConnectionHandlerimplements Runnable,EventHandler {/*** The source of status events.*/private final EventSource eventSource;/*** Creates a new ConnectionHandler.** @param clientSocket The socket for communication with the client.* @param evtSource The source of the status events with which the handler* is supposed to register.*/ConnectionHandler(Socket clientSocket,EventSource evtSource,String messageSeparator) {this.socket = requireNonNull(clientSocket, "clientSocket");this.eventSource = requireNonNull(evtSource, "evtSource");this.messageSeparator = requireNonNull(messageSeparator, "messageSeparator");checkArgument(clientSocket.isConnected(), "clientSocket is not connected");}/*** Adds an event to this handler's queue.** @param event The event to be processed.*/@Overridepublic void onEvent(Object event) {requireNonNull(event, "event");if (event instanceof TCSObjectEvent) {commands.offer(new ConnectionCommand.ProcessObjectEvent((TCSObjectEvent) event));}}}

使用,使用eventSource订阅ConnectionHandler实例,即可监听event事件

ConnectionHandler newHandler = new ConnectionHandler(clientSocket);

eventSource.subscribe(newHandler);

4.3题外话

那么这个方法时怎么找到的呢,首先我并不清楚tcp状态接口的机制,以为是轮询式的查询获取状态。所以我把目光锁定在了订单服务的类上面,

猜测,订单状态改变,或车辆被分配订单肯定会有事件之类的方法调用。否则不同的类要想知道订单和车辆状态发生了什么变化则很难实现,只能走通信的方式。

在tranportorder tool里面有个分配订单给车辆的方法中有调用emitObjectEvent方法,猜猜此方法的作用就是发送事情,必定有相应的接收事件方法。一路往上找,发现了

EventHandle接口,在查找此接口的实现发现在各处都有,基本可以判定就是通过它实现事件的监听,果不其然在tcp的状态接口中找到了简单的应用。

public TransportOrder setTransportOrderProcessingVehicle(TCSObjectReference<TransportOrder> orderRef,TCSObjectReference<Vehicle> vehicleRef)throws ObjectUnknownException {LOG.debug("method entry");TransportOrder order = objectPool.getObject(TransportOrder.class, orderRef);TransportOrder previousState = order.clone();if (vehicleRef == null) {order = objectPool.replaceObject(order.withProcessingVehicle(null));}else {Vehicle vehicle = objectPool.getObject(Vehicle.class, vehicleRef);order = objectPool.replaceObject(order.withProcessingVehicle(vehicle.getReference()));}objectPool.emitObjectEvent(order.clone(),previousState,TCSObjectEvent.Type.OBJECT_MODIFIED);return order;}

5.如何保存订单

当提交一个订单时,内核通过TransportOrderPool(TCSObjectPool)来储存订单,当订单被分配到具体车辆时则会添加到OrderReservationPool(strategies里)。我们应该要保存的订单是内核中未分配给车辆和执行完成的订单,

如需要实时保存可以写一个内核扩展,类似http服务扩展那样,监听订单的变化,将结果写入到数据库中。内核启动时读取数据库中的订单再写入到内核中。

如保存在文本文件中则可以使用json的格式在内核关闭时统一写入文件中,但是这样需要清空之前的纪录才可保证不会出现重复但不同状态的订单。

综上使用sqlite保存订单记录是不错的方式,后期如有需要跟换其它数据库也比较方便。

6.关于自动充电

@Inject
public ExampleCommAdapter(@Assisted Vehicle vehicle, ExampleAdapterComponentsFactory componentsFactory, @KernelExecutor ExecutorService kernelExecutor, TransportOrderService orderService, @Nonnull TCSObjectService objectService) {//父类BasicVehicleCommAdapter实例需要的参数super(new ExampleProcessModel(vehicle), 30, 30, "Charge");

新建适配器的时候需要提供充电的动作名称,在充电类型的点中添加此动作。如果对应不上的话会提示找不到合适的充电点,通过下面的源码可以看到其实就是判断车辆的充电动作和地图上所有位置允许的动作是否匹配。

那么这样做有什么用意呢,我们不妨这样想,如果调度系统连接了多种类型的车辆,而不同车辆的充电方式可能不一样,那么它们需要去到不同的点才能充电,这样调度系统就可以区分出来了,不得不说openTCS考虑的还是挺周到的。

openTCS-Strategies-Default/src/main/java/org/opentcs/strategies/basic/dispatching/phase/recharging/DefaultRechargePositionSupplier.java
@Override
public List<DriveOrder.Destination> findRechargeSequence(Vehicle vehicle) {requireNonNull(vehicle, "vehicle");if (vehicle.getCurrentPosition() == null) {return new ArrayList<>();}Map<Location, Set<Point>> rechargeLocations= findLocationsForOperation(vehicle.getRechargeOperation(),vehicle,router.getTargetedPoints());String assignedRechargeLocationName = vehicle.getProperty(PROPKEY_ASSIGNED_RECHARGE_LOCATION);if (assignedRechargeLocationName != null) {Location location = pickLocationWithName(assignedRechargeLocationName,rechargeLocations.keySet());if (location == null) {return new ArrayList<>();}// XXX Strictly, we should check whether there is a viable route to the location.return Arrays.asList(createDestination(location, vehicle.getRechargeOperation()));}String preferredRechargeLocationName = vehicle.getProperty(PROPKEY_PREFERRED_RECHARGE_LOCATION);if (assignedRechargeLocationName != null) {Location location = pickLocationWithName(preferredRechargeLocationName,rechargeLocations.keySet());if (location != null) {// XXX Strictly, we should check whether there is a viable route to the location.return Arrays.asList(createDestination(location, vehicle.getRechargeOperation()));}}Location bestLocation = findCheapestLocation(rechargeLocations, vehicle);if (bestLocation != null) {return Arrays.asList(createDestination(bestLocation, vehicle.getRechargeOperation()));}return new ArrayList<>();
}
private Map<Location, Set<Point>> findLocationsForOperation(String operation,Vehicle vehicle,Set<Point> targetedPoints) {Map<Location, Set<Point>> result = new HashMap<>();for (Location curLoc : plantModelService.fetchObjects(Location.class)) {LocationType lType = plantModelService.fetchObject(LocationType.class, curLoc.getType());if (lType.isAllowedOperation(operation)) {Set<Point> points = findUnoccupiedAccessPointsForOperation(curLoc,operation,vehicle,targetedPoints);if (!points.isEmpty()) {result.put(curLoc, points);}}}
6.2 相关配置

临界电量

7.控制控制中心界面扩展

8.上位机界面扩展

9.调度优化

关于默认策略

再api base包里面定义了内核的所有接口,如下图
在这里插入图片描述

在这里插入图片描述

默认策略实现的是Dispatcher, Scheduler, Router, Recovery这四个服务里面的方法, 实际的服务是再Kernel这个包里面运行的上面的四个服务具体就是调用了默认策略里面的类.

这篇关于openTCS二次开发简要事项(详细版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/782272

相关文章

DeepSeek模型本地部署的详细教程

《DeepSeek模型本地部署的详细教程》DeepSeek作为一款开源且性能强大的大语言模型,提供了灵活的本地部署方案,让用户能够在本地环境中高效运行模型,同时保护数据隐私,在本地成功部署DeepSe... 目录一、环境准备(一)硬件需求(二)软件依赖二、安装Ollama三、下载并部署DeepSeek模型选

电脑密码怎么设置? 一文读懂电脑密码的详细指南

《电脑密码怎么设置?一文读懂电脑密码的详细指南》为了保护个人隐私和数据安全,设置电脑密码显得尤为重要,那么,如何在电脑上设置密码呢?详细请看下文介绍... 设置电脑密码是保护个人隐私、数据安全以及系统安全的重要措施,下面以Windows 11系统为例,跟大家分享一下设置电脑密码的具体办php法。Windo

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

springboot整合gateway的详细过程

《springboot整合gateway的详细过程》本文介绍了如何配置和使用SpringCloudGateway构建一个API网关,通过实例代码介绍了springboot整合gateway的过程,需要... 目录1. 添加依赖2. 配置网关路由3. 启用Eureka客户端(可选)4. 创建主应用类5. 自定

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to