miki艾比利个人博客

愿居于一城,与卿所见美好......

推荐文章

Springcloud配置启动一工程多实例

    Springcloud配置启动一工程多实例

    前言

    最近在学习微服务,要学习微服务的负载均衡,涉及到一个服务要启动多个实例,这个时候又不想重复的创建工程,网上查了一下都是关于idea设置单一工程多实例使用的,对于这个不能忍啊,于是我就琢磨了一下使用myeclipse怎样启动多实例,教程如下:

     

    1. 创建springcloud工程,并启动

    1.设置好相关application的名称,和端口号,启动方式如下:

    1. 利用spring tool工具Boot Dashboard启动:

    右键(ReDebugger

    这样就完成了一个实例的启动方式,接下来启动第二个,第三个哟:

    2.创建第二个实例并启动

     

    1.如图:就创建出第三个实例了,这个时候接下来只需要更改一下端口即可:

     

    2.选中创建的实例,点击Open Config

    3.进入如下配置:

    4.更改端口并启动

    利用工具启动项目:

    右键(ReDebugger,以debugger模式启动:

    如下图:启动成功!!一个启动了三个实例。

    阅读全文>>

作者:miki艾比利分类:【springcloud浏览(211评论(0

2020-04-15 11:49:47

记录一次硬盘崩盘事故带来的灾难

    2019.12.19  miki

    2018年6月购置的神舟战神Z7-kp7gc笔记本,原价7600多买的,后来想着这个牌子的摸具比较难拆,就让店家帮忙把原装的126的固态升级为一个256的固态系统盘,注意,此时已经掉入了一个坑,由于加大固态盘,店家肯定会收取你好几百的升级费用,但是,虽然收了你的钱,但是不一定为你办事情,它为你升级的某个不知名的sata接口的achive协议的杂牌固态盘,虽然是256G,但是价钱同你的原装126的固态盘价位差不多,原装的126的固态盘则是三星的PCI-E接口的MVMe协议的,速度相对sata接口的要快上好几倍。

    坑1:

     

    PCIe和SATA的区别如下:

    1、接口类型不同:

    SATA(串行ATA)是SSD使用的一种连接接口,用于与系统进行数据通信。它创建于2003年,这意味着它有很多时间将自己巩固为当今最广泛使用的连接类型之一。

    PCIe(Peripheral Component Interconnect Express)可以视为与主板更直接的数据连接。它通常用于显卡等设备,这些设备也需要极快的数据连接。

    2、速率不同:

    SATA 3.0是最流行的SSD形式,其理论传输速度为6 Gb / s(750 MB / s)。但是由于在编码传输数据时出现了一些物理偏差,它实际上具有4.8 Gb / s(600 MB / s)的实际传输速度。

    PCIe 3.0的有效传输速度为每通道985 MB / s,由于PCIe设备可支持1x,4x,8x或16x通道,因此您可以将潜在的传输速度提高到15.76 GB / s。

    3、接口协议不同:

    接口有AHCI协议与NVMe协议,AHCI较旧,专为HDD和SATA而设计,这意味着使用AHCI的PCIe SSD可能无法发挥其最大潜力。NVMe专为与PCIe配合使用而设计,因此性能更佳。

    坑2:

    机械硬盘崩盘,刚买来的时候就是个小小白,虽然现在也还是个小白,但是刚买过来的时候,机械盘读取的特别慢,我i以为是常态,就没在意,就这样浑浑噩噩的用了一年半,以至于后面的崩盘。最最可惜的是,丢失的数据无价,这几年总结,积累的东西全没了,对此我心疼了好久,好久。所以,大家一定要记得资料定时备份,时刻注意硬盘的状态,防止最后一场空。

    坑3:

    数据恢复,硬盘崩了,找数据恢复一定要谨慎,不要随随便便找一家不知名的数据恢复门店,对方极有可能会将你的硬盘资料加密,或者造成二次伤害,以此为胁迫,漫天要价,让你无法去其他的专业机构去恢复,我这边恢复数据就找了一家公司,叫什么雷超科技有限公司,能力是真烂,让我自己买介质,(就是自己买原装硬盘型号的二手的,磁头一定要好,硬盘坏道一定要少),然后给我恢复了半个月,到头来,数据重要的还是丢失了一半,剩下一部分即使恢复出来了,文件也是损坏的,无法使用。最后跟我要了1000块钱,真是心疼。最后只能根据文件名自己慢慢恢复资料。

    总结

    硬盘保养一定要仔细。

    硬盘是电脑中较容易损坏的配件,但其中有相当一部分的原因是用户操作不当所致。其实,只要在日常使用中注意一些小技巧,便可以减少硬盘出故障的可能性,从而延长其正常的使用寿命。

    1.读写忌断电
    现时硬盘的转速大都是7200转,在进行读写时,整个盘片处于高速旋转状态中,如果忽然切断电源,将使得磁头与盘片猛烈磨擦,从而导致硬盘出现坏道甚至损坏。所以在关机时,一定要注意机箱面板上的硬盘指示灯是否没有闪烁,即硬盘已经完成读写操作之后才可以按照正常的程序关闭电脑。硬盘指示灯闪烁时,一定不可切断电源。

    2.防止受震动
    硬盘是十分精密的存储设备,进行读写操作时,磁头在盘片表面的浮动高度只有几微米;即使在不工作的时候,磁头与盘片也是接触的。硬盘在工作时,一旦发生较大的震动,就容易造成磁头与资料区相撞击,导致盘片资料区损坏或刮伤磁盘,丢失硬盘内所储存的文件数据。因此,在工作时或关机后主轴电机尚未停顿之前,千万不要搬动电脑或移动硬盘,以免磁头与盘片产生撞击而擦伤盘片表面的磁层。此外,在硬盘的安装、拆卸过程中也要加倍小心,防止过分摇晃或与机箱铁板剧烈碰撞。

    3.远离磁场
    磁场是损毁硬盘数据的隐形杀手,因此要尽可能地使硬盘不靠近强磁场,如音箱、手机、电台等,以免硬盘里所记录的资料因磁化而受到破坏。机有信号时会产生强烈磁场,可能磁化附近的硬盘

    4.减少频繁操作
    如果长时间运行一个程序(如大型软件或玩游戏),这时就要注意了,这样磁头会长时间频繁读写同一个硬盘位置(即程序所在的扇区),而使硬盘产生坏道。
    另外,如果长时间使用一个操作系统,也会使系统文件所在的硬盘扇区(不可移动)处于长期读取状态,从而加快该扇区的损坏速度。当然,最好是安装有两个或以上的操作系统交替使用,以避免对硬盘某个扇区做长期的读写操作。

    5.恰当的使用时间
    在一天中,最好不要让硬盘的工作时间超过10个小时,而且不要连续工作超过8个小时,应该在使用一段时间之后就关闭电脑,让硬盘有足够的休息时间。

    6.定期整理碎片
    硬盘工作时会频繁地进行读写操作,同时程序的增加、删除也会产生大量的不连续的磁盘空间与磁盘碎片。当不连续磁盘空间与磁盘碎片数量不断增多时,就会影响到硬盘的读取效能。如果数据的增删操作较为频繁或经常更换软件,则应该每隔一定的时间(如一个月)就运行Windows系统自带的磁盘碎片整理工具,进行磁盘碎片和不连续空间的重组工作,将硬盘的性能发挥至最佳。

    7.增加内存与良好供电
    如果经常使用一些内存需求很大的软件(如图像处理、模型设计等软件),就应该增加内存,来减少大量的文件交换时在硬盘上进行的读写操作,从而延长硬盘的使用寿命。
    另外,一定要使用性能稳定的电源,如果电源的供电不纯或功率不足,很容易就会造成资料丢失甚至硬盘损坏。

    阅读全文>>

作者:miki艾比利分类:【日记浏览(161评论(0

2019-12-19 19:46:54

用Kettle在数据集成中进行分区

    让我们假设,我们用简单的数据集进行了一次转换,该数据集具有印度城市明智的州人口数。我们需要获取总人口数。我们使用“ 内存按步骤分组” 构建一个简单的转换文件,并根据State对源数据进行分组以获得总人口数。

    KTR找到明智的人口计数国家

    现在,假设你有少量的源数据,而不是源中的数百万条记录。因此,逐步进行内存分组将花费你巨大的成本。我们不是简单地通过,而是利用多核计算机的功能。

    Pentaho将为你提供一个定义步骤中数据副本数的选项。在Pentaho中按步骤定义内存组的多行是使用多核计算机的一种方法。因此,请按以下步骤对“内存组”进行以下操作:

    >>  右键单击 “内存分组依据
     ” >>选择“更改份数以开始
     ” >>将值设置为 3或大于1的任何数字

    将份数输入到3

    但是,一旦执行了上面的转换,我们就会看到结果有很大的不同,这并不是预期的。这是因为源步骤是按循环方式将数据逐步发送到“内存组”的3个副本。这些内存组中的每一个都以自己的方式逐步处理数据,最后发送输出。这是PDI中的并行性,但是在这种情况下,我们没有得到正确的输出。

    因此,我们在这里要做的就是对数据进行分区。这里的想法是将类似种类的数据发送到每个分区,然后通过分组而不是根据上述数据进行分组。它像3 Memory Step by Step,每个分区都有分区数据,例如分区1中的西孟加拉邦,分区2中的喀拉拉邦,等等。

    在PDI中创建分区

    创建分区要遵循的步骤:

    >>转到转换的
    设计”选项卡>>在“分区模式”选项卡中,创建一个“新模式
     ” >>输入“分区名称”。它可以是任何可读的名称。
    >>输入分区ID或所需的分区数。
    到目前为止,对分区没有命名约束。 
    只需给出分区1,分区2等。

    完成上述步骤后。现在继续进行转换并执行以下操作:

    >>右键单击“内存组”,一步步
     >>选择“分区”。
    >>选择“分区”方法。选择除法余数>>选择分区架构。
    这是上面在“设计”选项卡中创建的所有分区架构的列表。
    >>接下来,你需要选择要分区的字段。
    选择一个合适的。

    最后,你将看到类似以下的内容:

    带有分区架构和数据的KTR

    执行后,返回的结果是正是我们需要的:

    最终分区输出

    阅读全文>>

作者:miki艾比利分类:【ETL浏览(215评论(0

2019-11-14 09:38:16

【转载】kettle集群原理分析

    本文转自:http://blog.csdn.net/dqswuyundong/article/details/5952009

    Kettle集群

    Kettle是一款开源的ETL工具,以其高效和可扩展性而闻名于业内。其高效的一个重要原因就是其多线程和集群功能。

    Kettle的多线程采用的是一种流水线并发的机制,我们在另外的文章中专门有介绍。这里主要介绍的是kettle的集群。

    集群允许转换以及转换中的步骤在多个服务器上并发执行。在使用kettle集群时,首先需要定义的是Cluster schema。所谓的Cluster schema就是一系列的子服务器的集合。在一个集群中,它包含一个主服务器(Master)和多个从属服务器服务器(slave)。如下图所示。

    子服务器(Slave servers)允许你在远程服务器上执行转换。建立一个子服务器需要你在远程服务器上建立一个叫做“Carte”的 web 服务器,该服务器可以从Spoon(远程或者集群执行)或者转换任务中接受输入。

    在以后的描述中,如果我们提到的是子服务器,则包括集群中的主服务器和从属服务器;否则我们会以主服务器和从属服务器来进行特别指定。

    设计

    要让转换是以集群方式执行,首先需要在Spoon中进行图形化的设计工作。定义一个以集群方式运行的转换,主要包括定义cluster schema和定义转换两个步骤。

    定义cluster schema

    创建子服务器

    服务tab 选项

    选项

    描述

    服务器名称

    子服务器的名称

    主机名称或IP地址

    用作子服务器的机器的地址

    端口号

    与远程服务通信的端口号

    用户名

    获取远程服务器的用户名

    密码

    获取远程服务器的密码

    是主服务器吗

    在转换以集群形式执行时,该子服务器将作为主服务器

    注意: 在集群环境下执行转化时,你必须有一个子服务器作为主服务器(master server)而其余所有的子服务器都作为从属服务器(slave)

    Proxy tab options

    选项

    描述

    代理服务器主机名

    设置你要通过代理进行连接的主机名

    代理服务器端口

    设置与代理进行连接时所需的端口号

       

    Ignore proxy for hosts: regexp|separated

    指定哪些服务器不需要通过代理来进行连接。该选项支持你使用正则表达式来制定多个服务器,多个服务器之间以' | ' 字符来进行分割

    创建cluster schema

    选项描述

    选项

    描述

    Schema 名称

    集群schema的名称

    端口号

    这里定义的端口号是指从哪一个端口号开始分配给子服务器。每一个在子服务器中执行的步骤都要消耗一个端口号。

    注意: 确保没有别的网络协议会使用你定义的范围之类的端口,否则会引起问题

    Sockets缓存大小

    TCP内部缓存的大小

    Sockets刷新间隔(rows)

    当TCP的内部缓存通过网络完全发送出去并且被清空时处理的行数

    Sockets数据是否压缩

    如果该选项被选中,则所有的数据都会使用Gzip压缩算法进行压缩以减轻网络传输量

    Dynamic Cluster

    动态集群指的是在运行的时候才能获知从属服务器的信息。这种情形适用于主机可以自动增加或者去除的情形,例如云计算。

    主服务器的设置不变,但是它可以接受从属服务器的注册。一旦接受了某个从属服务器的注册,则每隔30秒去监视该从属服务器是否还处于有效状态

    子服务器

    这里是一个要在集群中使用的服务器列表。这个列表中包含一个主服务器和任意数目的从属服务器。

    在dynamic Cluster的情况下,只需要选择主服务器即可

    定义转换

    定义完了cluster schema后,下一步就是定义在集群环境下执行的转换。我们这里展现的只是一个最简单的例子,完全是为了演示而用。现实情况中的集群有可能非常复杂。

    首先你像平时一样创建转换,以hop连接连个两个步骤。然后你指定第二个步骤将在集群下执行

    然后选择需要使用的集群。转换如图一样显示在GUI中。

    注意 Cx4显示这个步骤将在集群中运行,而这个集群中有4个从属服务器。假设我们将计算结果再次存入到数据表中

    这个转换虽然定义了集群,但是我们同样可以让它在单机环境下执行,而且可以得到相同的结果。这意味着你可以使用普通的本地模式来测试它。

    执行转换

    要想以集群方式来运行转换或者作业,首先需要启动在Cluster  schema中定义的主服务器和从属服务器,然后再运行转换或者作业。

    启动子服务器

    子服务器其实是一个嵌入式的名为Carte的小web server。要进行集群转换,首先需要启动cluster schema中的子服务器

    脚本启动

    kettle提供了carte.bat和carte.sh(linux)批处理脚本来启动子服务器,这种启动方式分为两种

    使用主机号和端口号

    Carte 127.0.0.1 8080

    Carte 192.168.1.221 8081

    使用配置文件

    Carte  /foo/bar/carte-config.xml

    Carte http://www.example.com/carte-config.xml

    如果cluster schema中定义了Dynamic cluster选项,则必须使用配置文件来进行启动,当这个子服务器启动时,它需要向配置文件中“masters”中列出的主服务器列表中汇报其运行状态(通过调用主服务器的registerSlave服务),已达到动态地设置子服务器的目的。配置文件格式

    <slave_config>
       <masters>
       <slaveserver>
         <name>master1</name>
         <hostname>localhost</hostname>
         <port>8080</port>
         <username>cluster</username>
         <password>cluster</password>
         <master>Y</master>
       </slaveserver>
      </masters>
      <report_to_masters>Y</report_to_masters>
    <slaveserver>
      <name>slave4-8084</name>
      <hostname>localhost</hostname>
      <port>8084</port>
      <username>cluster</username>
      <password>cluster</password>
      <master>N</master>
    </slaveserver>
    </slave_config>

    这个配置文件主要包括以下几个节点

    Ø       masters: 这里列出来的服务器是当前子服务器需要向其汇报状态的主服务器。如果当前这个子服务器是主服务器,则它将连接其它的主服务器来获得这个集群中的所有子服务器。

    Ø       report_to_masters : 如果为Y,则表示需要向定义的主服务器发送消息以表明该从属服务器存在

    Ø       slaveserver : 这里定义的就是当前carte实例运行时需要的子服务器的配置情况

    这里定义的username和password在向主服务器调用Register服务时连接主服务器时提供的安全设置。在 <slaveserver>部分,你可以使用<network_interface> 参数,这个参数的优先级高于<hostname>参数 ,如果你的机器中安装有多个网卡,这个设置可以起作用。

    程序启动

    Kettle提供了org.pentaho.di.www.Carte类,你可以通过该类提供的函数来启动或者停止子服务器。

    Ø         启动子服务器

    SlaveServerConfig config = new SlaveServerConfig(hostname, port, false);

    Carte. runCarte(config);

    Ø         停止子服务器

    carte.getWebServer().stopServer();

    子服务器内幕

    我们前面提到过子服务器实际上就是一个web server,该web server是基于Jetty这个嵌入式的开源servlet容器。

    这个web server主要是提供转换运行的环境,另外一个重要的功能通过提供servlet来在客户端、主服务器和从属服务器之间进行通讯和控制。主服务器和从属服务器之间是通过httpClient来进行通讯的,通讯时传递的数据是xml格式。通过提供的servlet,可以实现启动、停止、暂停转换或者作业、获得转换或者作业的状态、注册子服务器、获得子服务器的列表等等

    Kettle主要提供了以下的几种基于servlet的服务

    Ø         GetRootServlet:获得Carte的根目录

    Ø         GetStatusServlet:获得在服务器上运行的所有的转换和作业的状态

    Ø         GetTransStatusServlet:获得在服务器上运行的某个指定的转换的每个步骤的运行状态。

    Ø         PrepareExecutionTransServlet:让服务器上的某个指定的转换做好运行的准备。

    Ø         StartTransServlet:执行服务器上的某个指定的转换

    Ø         PauseTransServlet:暂停或者重新运行某一个转换

    Ø         StopTransServlet:停止正在运行的转换

    Ø         CleanupTransServlet:清理运行转换时的环境

    Ø         AddTransServlet:向子服务器中增加某个转换。如果服务器中有正在运行或者准备运行的相同名字的转换,则抛出异常。

    Ø         AllocateServerSocketServlet:分配一个新的socket端口号。这个端口号是基于你在定义cluster schema中设置的端口号,依次加1

    Ø         StartJobServlet:执行服务器上某个指定的作业

    Ø         StopJobServlet:停止正在运行的作业

    Ø         GetJobStatusServlet:获得某个指定作业的状态

    Ø         AddJobServlet:向当前的子服务器中添加某个作业。

    Ø         RegisterSlaveServlet:注册某个服务器的信息。服务器信息包括子服务器是否活动、最新活动的时间、最新不活动的时间。这个在dynamic cluster中需要用到,由从属服务器向主服务器汇报当前状态。

    Ø         GetSlavesServlet:获得集群中子服务器的信息

    Ø         AddExportServlet:以zip文件的形式向caret服务器传递作业或者转换信息,并将信息加入到服务器中。

    运行转换

    在spoon中运行

    在kettle的集成设计环境spoon中,你可以选择转换中的“运行”菜单项,或者按F9快捷键,弹出以下的窗口

    这里有三个选项来决定转换是以什么方式来执行

    Ø       本地执行: 转换或者作业将在你现在使用的JVM中运行。

    Ø       远程执行: 允许你指定一个想运行转换的远程服务器。这需要你在远程服务器上安装Pentaho Data Integration(Kettle)并且运行Carte子服务器。

    Ø       集群方式执行: 允许你在集群环境下执行作业或者转换

     

    当你选择“集群方式执行”选项是,你可以选择以下的选项

    Ø       提交转换: 分解转换并且将转换提交到不同的主服务器和从属服务器。

    Ø       准备执行: 它将在主服务器和从属服务器上执行转换的初始化阶段。

    Ø       开始执行: 它将在主服务器和从属服务器中执行实际的转换任务。

    Ø       显示转换: 显示将要在集群上执行的生成的转换(可以参看下面的分析).

    编程运行

    你也可以通过使用Kettle提供的API通过编程来以集群的方式运行转换。

    TransMeta transMeta = new TransMeta("cluster.ktr");

    //设置执行模式

    TransExecutionConfiguration config = new TransExecutionConfiguration();

    config.setExecutingClustered(true);

    config.setExecutingLocally(false);

    config.setExecutingRemotely(false);

    config.setClusterPosting(true);

    config.setClusterPreparing(true);

    config.setClusterStarting(true);

    config.setLogLevel(LogWriter.LOG_LEVEL_BASIC);

    TransSplitter transSplitter = Trans.executeClustered(transMeta, config);

    long nrErrors = Trans.monitorClusteredTransformation("cluster  test", transSplitter, null, 1);

    需要注意的是这段代码可以在一个独立的JVM中执行,而不必要在主服务器中执行。

    运行内幕

    当以集群方式来运行转换时,Kettle主要执行以下几个步骤来执行分布式的处理

    Ø 分解转换

    在定义转换的时候,如果在某个步骤中定义使用集群,那么这个步骤其实是在从属服务器(slave server)上执行的,例如我们在前面定义转换的modify javascript value步骤中,我们定义了使用集群,那么这个步骤将在从属服务器中执行;而Cx4表示这个步骤是在4个从属服务器上执行。如果步骤中没有定义集群,则表示该步骤是在主服务器(master server)上执行。如果前一步骤在主服务器上执行,而后一步骤需要在从属服务器上执行,或者相反,则这时需要分别在前一步骤和后一步骤之间建立一个remoteStep步骤,前面的remoteStep建立socketWriter进程,它负责从上一步骤中取出数据然后通过socket传输到对应的子服务器的remoteStep中。而后一步骤所在的子服务器的remoteStep步骤则建立一个socketReader,负责从socket中获取数据,并将数据将数据传输到后一步骤中,以供后一步骤来进行后续处理。

    所以在以集群方式执行转换时,首要的任务是将转换分解成可以在各个子服务器上执行的转换。

    我们还是以上面建立的转换来进行分析描述:

    上图是在主服务器上建立的转换

    And 4 slaves transformations:

    上图是在4个从属服务器上建立的转换,我们可以注意到这四个从属服务器上的转换是一样的,除了端口号不一样。另外我们还注意到在前述Cluster Schema 定义中我们指定了端口号为4000,则为每一个建立的socket连接就是端口号4000开始,依次加1。另外,还可以看到数据是通过使用socket  Writer和socket Reader的remoteStep步骤通过TCP/IP的socket来传递数据的。

    Ø         提交转换

    对于第一步骤生成的子转换,将调用每个子服务器提供的AddTransServlet服务将转换的信息增加到每个子服务器中(包括主服务器和从属服务器)。

    Ø         准备转换

    调用每个子服务器的PrepareExecutionTransServlet服务来准备转换

    Ø         启动转换

    调用每个子服务器的StartExecutionTransServlet服务来启动转换。

    Ø         监控转换

    在各服务器的转换都启动后,调用Trans.monitorClusteredTransformation来监控各个服务器的运行状态(使用各子服务器提供的GetTransStatusServlet服务来获得每个子服务器的状态)。

    例子

    目的

    做一个转换(表输入---à排序--à表输出)

    然后在两台pc机器上实验。把集群放到排序插件上。

    配置两台子服务器

    创建子服务器

    在主对象下的转换下的子服务器右键单击新建。

    右键单击子服务器新建

    填写相关的配置,用户名和密码为cluster,如果要修改得修改kettle默认路径下的pwd下的kettle.pwd文件里的用户名密码。

    这个是从属服务器。

    配置schemas

    新建schemas

    在选择子服务器中选择这两个服务器。

    开启两台机器的carte服务

    在10.2.4.81机器和10.2.4.188机器的控制台开启carte服务。

    C:/pdi-open-3.1.0-826是保存kettle的文件夹

    188机器也和他一样。

    在转换中添加集群

    右击字段选择选择集群。

    点击确定。

    出现cx1代表成功。

    然后运行,就OK了。

    ---------------------------------以下内容是我自己添加的----------------------------------------

    更多资料:

    http://forums.pentaho.com/showthread.php?60500-clustering-in-Kettle
    http://forums.pentaho.com/archive/index.php/t-54398.html

    阅读全文>>

作者:miki艾比利分类:【ETL浏览(155评论(0

2019-10-30 10:51:20

【转载】myeclipse和idea阿里代码质量检测插件安装及使用

    本文参考 阿里巴巴代码规范插件安装说明

    一、插件安装

    1. 环境: JDK1.8,Eclipse4+。

    本文使用的JDK版本为:jdk1.8.0_152;Eclipse版本是: Oxygen.3 (4.7.3)

    2. 安装

    Help -> Install New Software...

    输入Update Site地址:https://p3c.alibaba.com/plugin/eclipse/update 回车,

     

    然后勾选Ali-CodeAnalysis,再一直点Next ...按提示走下去就好。
    选择接受证书点击finish完成操作。

     

     

    如下图界面提示选择install anyway


    然后就是提示重启了,点击restart now,安装完毕。

     


    出现如下图所示图标则,安装成功。

     

    至此插件安装完毕。


    注意:有同学反映插件扫描会触发很多 "JPA Java Change Event Handler (Waiting)" 的任务,这个是Eclipse的一个bug

    因为插件在扫描的时候会对文件进行标记,所以触发了JPA的任务。卸载JPA插件,

    或者尝试升级到最新版的Eclipse。附:JPA project Change Event Handler问题解决

    二、插件使用

    目前插件实现了开发手册中的53条规则,大部分基于PMD实现,其中有4条规则基于Eclipse实现,支持4条规则的QuickFix功能。

    * 所有的覆写方法,必须加@Override注解,

    * if/for/while/switch/do等保留字与左右括号之间都必须加空格,

    * long或者Long初始赋值时,必须使用大写的L,不能是小写的l

    * Objectequals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals

    目前不支持代码实时检测,需要手动触发。

    代码扫描

    可以通过右键菜单、Toolbar按钮两种方式手动触发代码检测。

     

    触发扫描

    在当前编辑的文件中点击右键,可以在弹出的菜单中触发对该文件的检测。

     

    在左侧的Project目录树种点击右键,可以触发对整个工程或者选择的某个目录、文件进行检测。

     

    也可以通过Toolbar中的按钮来触发检测,目前Toolbar的按钮触发的检测范围与您IDE当时的焦点有关,如当前编辑的文件或者是Project目录树选中的项,是不是感觉与右键菜单的检测范围类似呢。

    扫描结果

    简洁的结果面板,按规则等级分类,等级->规则->文件->违规项。同时还提供一个查看规则详情的界面。

    清除结果标记更方便,支持上面提到的4条规则QuickFix。

    查看所有规则

     

     

    image

    国际化

     

    虽然是抄的,但是有用就好

    阅读全文>>

作者:miki艾比利分类:【编程浏览(273评论(0

2019-10-29 09:18:02

【转载】ExecutorService的正确关闭方法

    ExecutorService的正确关闭方法

    转自:https://blog.csdn.net/xueyepiaoling/article/details/61200270

    虽然使用ExecutorService可以让线程处理变的很简单,可是有没有人觉得在结束线程运行时候只调用shutdown方法就可以了?实际上,只调用shutdown方法的是不够的。我们用学校的老师和学生的关系来说明这个问题。

    shutdown只是起到通知的作用

    我们来假设如下场景:
    学校里在课上老师出了一些问题安排全班同学进行解答并对学生说“开问题解答完毕后请举手示意!”
    如果有学生解答完毕后会举手对老师说“老师我做完了!”,如果大家都解题完毕后上课结束。

    上面的场景对应于ExecutorService里的方法的话是下面的样子。

    • 老师: ExecutorService
    • 学生: ExecutorService里的线程
    • 问题: 通过参数传递给ExecutorService.execute的任务(Runnable)
    • 授课: main线程
    • 学校: Java进程
    • “问题解答完毕后请举手示意!”是shutdown方法。
    • “老师我做完了!”是各个任务(Runnable)的运行结束。

    所有的任务(Runnable)都结束了的话main线程(授课)也结束了。

    在这里,我们假设试卷中有难度较大的问题,当然学生解答较难的问题也会比较花时间。在上面的场景中老师除了shutdown方法之外什么也做不了,只能呆呆得等着学生们说,“老师我做完了!”之后才可以有下一步动作。这都是因为shutdown方法只是用来通知的方法。

    这时如果即使授课时间结束(main线程结束),学校也不能放学(Java进程结束),因为学生们还在解题中呢。这个时候如果你是老师你会怎么做?

    一般的情况肯定是经过一定的时间在授课快要结束的时候,如果还有人没有解答出来的话,或者公布给大家解题方法,或者作为课后习题让学生回去继续思考,然后结束上课对不对!

    定好下课时间后等待结束

    如果经过了一定的时间任务(Runnable)还不结束的时候我们可以通过中止任务(Runnable)的执行,以防止一直等待任务的结束。awaitTermination方法正是可以实现这个中止作用的角色。

    具体的使用方法是,在shutdown方法调用后,接着调用awaitTermination方法。这时只需要等待awaitTermination方法里第一个参数指定的时间。

    如果在指定的时间内所有的任务都结束的时候,返回true,反之返回false。返回false意味着课程结束的时候还有题目没有解答出来的学生。

    通过shutdownNow方法,我们可以作为老师向同学发出“没有解答出来的同学明天给出解答”的命令后结束授课。

    shutdownNow方法的作用是向所有执行中的线程发出interrupted以中止线程的运行。这时,各个线程会抛出InterruptedException异常(前提是
    线程中运行了sleep等会抛出异常的方法)

    所以正确的中止线程的方法如下:

    public static void main(String[] args) {  
       
        ExecutorService pool = Executors.newFixedThreadPool(5);  
        final long waitTime = 8 * 1000;  
        final long awaitTime = 5 * 1000;  
       
        Runnable task1 = new Runnable(){  
            public void run(){  
                try {  
                    System.out.println("task1 start");  
                    Thread.sleep(waitTime);  
                    System.out.println("task1 end");  
                } catch (InterruptedException e) {  
                    System.out.println("task1 interrupted: " + e);  
                }  
            }  
        };  
       
        Runnable task2 = new Runnable(){  
            public void run(){  
                try {  
                    System.out.println("task2 start");  
                    Thread.sleep(1000);  
                    System.out.println("task2 end");  
                } catch (InterruptedException e) {  
                    System.out.println("task2 interrupted: " + e);  
                }  
            }  
        };  
        // 让学生解答某个很难的问题  
        pool.execute(task1);  
       
        // 让学生解答很多问题  
        for(int i=0; i<1000; ++i){  
            pool.execute(task2);  
        }  
       
        try {  
            // 向学生传达“问题解答完毕后请举手示意!”  
            pool.shutdown();  
       
            // 向学生传达“XX分之内解答不完的问题全部带回去作为课后作业!”后老师等待学生答题  
            // (所有的任务都结束的时候,返回TRUE)  
            if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){  
                // 超时的时候向线程池中所有的线程发出中断(interrupted)。  
                pool.shutdownNow();  
            }  
        } catch (InterruptedException e) {  
            // awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。  
            System.out.println("awaitTermination interrupted: " + e);  
            pool.shutdownNow();  
        }  
       
        System.out.println("end");  
    }  
    

    可以看出上面程序中waitTime的值比awaitTime大的情况下,发生Timeout然后执行中的线程会中止执行而结束。

    反过来如果缩小waitTime的值,增大awaitTime的值的的话,各个线程就会不被中止的正常运行至结束。

    在这里,如果我们把awaitTime和shutdownNow方法全部屏蔽掉的只留下shutdown方法的话会怎样呢? 会变成表示main方法结束的「end」显示出来之后,会打印出很多的task2的start和end。这就是虽然课程结束了,但是学校仍然不能放学的不正常状态。最恶劣的情况会导致JAVA进程一直残留在OS中。

    所以我们一定不要忘记使用awaitTermination和shutdownNow。

    shutdown也是很重要的

    看了上面的描述后可能有些人会认为,只需要执行awaitTermination和shutdownNow就可以正常结束线程池中的线程了。其实不然。
    shutdown方法还有「大家只解答我要求的问题,其它的不用多做」的意思在里面。

    shutdown方法调用后,就不能再继续使用ExecutorService来追加新的任务了,如果继续调用execute方法执行新的任务的话,就会抛出RejectedExecutionException异常。(submit方法也会抛出上述异常)

    而且,awaitTermination方法也不是在它被调用的时间点上简单得等待任务结束而是在awaitTermination方法调用后,持续监视各个任务的状态以或者是否线程已经运行结束。所以不调用shutdown方法执行调用awaitTermination的话由于追加出来的任务可能会导致任务状态监视出现偏差而发生预料之外的awaitTermination的Timeout异常。

    所以正确的调用顺序是:

    shutdown方法
    awaitTermination方法
    shutdownNow方法(发生异常或者是Timeout的时候)
    

    实际开发的系统可能会有不能强制线程中止执行的场景出现,所以虽然推荐使用上面说的调用顺序但也并不是绝对一成不变的。

    阅读全文>>

作者:miki艾比利分类:【javaEE浏览(197评论(0

2019-10-25 19:33:30

解决getConnection或者java.sql.rs.next() 时,线程hang住,导致代码卡顿问题

    1. 问题复现

    当你兴高采烈的写完一堆获取数据库连接的代码准备测试时,由于服务器性能,或者网络问题,导致线程阻塞,页面卡死。大多数数据库可能会具备连接超时的设置,你可以设置一下连接超时,自动退出即可。但是,有的数据库就是那么尿性,又或者是本身提供的jdbc驱动不支持连接超时设置,这个时候就很头疼了,每次页面查询数据或者点击测试连接的时候,让前台写一堆ajax超时设置吗,好了,废话不多说,此处教你一个方法,使用java中的ExecutorService来实现任务的中止。

    ExecutorService简介:

    查看java的api可知,

    继承于Executor,提供了管理终止的方法和可以生成Future,用来跟踪一个或多个异步任务进度的方法。

    可以关闭Service,之后会拒绝执行新的任务。提供了两种终止方法,shutdown()会终止任务,但是shutdown()之前提交的任务会继续执行,而shutdownNow()会终止掉没启动的任务并且终止正在运行的任务。终止的时候如果没有提交的任务,没有等待的任务,没有正在执行的任务时,应该释放executor资源。

    总的来说,实现ExecutorService,就有终止任务,和生成Future跟踪任务的能力。

    3.代码

    下面上代码:

    public static void main(String[] args) {
    		
    		boolean hasNext = false;
    		
    		Runnable runnable = new Runnable() {
                public void run() {
                    try {
                       Thread.sleep(5000);
                       System.out.println("乐乐");
                    } catch (Throwable e) {
                        
                    }
                }
            };
    //      Thread thread = new Thread(runnable);
    //      thread.start();
            
            System.out.println("tomcat初始化..");
            System.out.println("开始时间:"+System.currentTimeMillis());
            
            //首先构造Executor接口,下面的例子是创建一个单线程的执行者,你也可以根据需要创建基于线程池的执行者
            ExecutorService executor = Executors.newSingleThreadExecutor();
            
            //将rs.next()或者getcConnection方法委托给Callable接口去调用,通过Future接口获取返回结果
    		FutureTask future = new FutureTask(new Callable() {
    			public Boolean call() throws IOException {
    				try {
    					//模拟超时
    					Thread.sleep(5000);
    				} catch (InterruptedException e) {
    					e.printStackTrace();return false;
    				}
    				System.out.println("获取连接成功..");
    				return true;
    			}
    		});
    		
    		try {
    			executor.execute(future);
    			hasNext = future.get(2, TimeUnit.SECONDS);
    		} catch (InterruptedException | ExecutionException | TimeoutException e) {
    			e.printStackTrace();
    			System.out.println("超时啦...");
    			//捕获异常后,关闭当前任务!!!重要  2019.10.24  miki
    			executor.shutdown();
    		}       
            if(hasNext) {
            	//System.out.println("到达时间:"+System.currentTimeMillis());
            	System.out.println("到达时间:"+System.currentTimeMillis());
            }      
            
    	}

    执行结果如下:

    注意如下:

    shutdown()

    不能添加新任务,否则抛异常。但是已存在的任务会继续执行完成。(所以才会打印出来获取链接成功

    shutdownNow()

    不接收新任务,不处理等待任务,尝试中断已存在线程(通过Thread.interrupt(),这个方法作用有限,你的任务里没有抛出InterruptException的话,就中断不了了)

    Future submit(Callable task);

    Future submit(Runnable task, T result);

    谢谢观看,记得掉个赞哦

     

    阅读全文>>

作者:miki艾比利分类:【javaEE浏览(186评论(0

2019-10-24 21:19:07

【转载】Java:new一个对象的过程中发生了什么?

    作者:沉默哥

    cnblogs.com/JackPn/p/9386182.html

    java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载。加载并初始化类完成后,再进行对象的创建工作。

    我们先假设是第一次使用该类,这样的话new一个对象就可以分为两个过程:加载并初始化类和创建对象。

    一、类加载过程(第一次使用该类)

    java是使用双亲委派模型来进行类的加载的,所以在描述类加载过程前,我们先看一下它的工作过程:

    双亲委托模型的工作过程是:如果一个类加载器(ClassLoader)收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时,子加载器才会尝试自己去加载。

    使用双亲委托机制的好处是:能够有效确保一个类的全局唯一性,当程序中出现多个限定名相同的类时,类加载器在执行加载时,始终只会加载其中的某一个类。

    1、加载

    由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例

    2、验证

    格式验证:验证是否符合class文件规范

    语义验证:检查一个被标记为final的类型是否包含子类;检查一个类中的final方法是否被子类进行重写;确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同)

    操作验证:在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否可以通过符号引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)

    3、准备

    为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量不在此操作范围内)

    被final修饰的static变量(常量),会直接赋值;

    4、解析

    将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。
    解析需要静态绑定的内容。 // 所有不会被重写的方法和域都会被静态绑定

    以上2、3、4三个阶段又合称为链接阶段,链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。

    5、初始化(先父后子)

    • 4.1 为静态变量赋值

    • 4.2 执行static代码块

    注意:static代码块只有jvm能够调用


    如果是多线程需要同时初始化一个类,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。

    因为子类存在对父类的依赖,所以类的加载顺序是先加载父类后加载子类,初始化也一样。不过,父类初始化时,子类静态变量的值也有有的,是默认值。

    最终,方法区会存储当前类类信息,包括类的静态变量、类初始化代码(定义静态变量时的赋值语句 和 静态初始化代码块)、实例变量定义、实例初始化代码(定义实例变量时的赋值语句实例代码块和构造方法)和实例方法,还有父类的类信息引用。

    二、创建对象

    1、在堆区分配对象需要的内存

    分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量

    2、对所有实例变量赋默认值

    将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值

    3、执行实例初始化代码

    初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法

    4、如果有类似于Child c = new Child()形式的c引用的话,在栈区定义Child类型引用变量c,然后将堆区对象的地址赋值给它

    需要注意的是,每个子类对象持有父类对象的引用,可在内部通过super关键字来调用父类对象,但在外部不可访问

    补充:

    通过实例引用调用实例方法的时候,先从方法区中对象的实际类型信息找,找不到的话再去父类类型信息中找。

    如果继承的层次比较深,要调用的方法位于比较上层的父类,则调用的效率是比较低的,因为每次调用都要经过很多次查找。这时候大多系统会采用一种称为虚方法表的方法来优化调用的效率。

    所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及其地址,包括父类的方法,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。当通过对象动态绑定方法的时候,只需要查找这个表就可以了,而不需要挨个查找每个父类。

    本文系转载文章,来源注明出处https://mp.weixin.qq.com/s?__biz=MzU3MjY3NTA4Mw==&mid=2247484670&idx=1&sn=0aa6c48864938e67ec240f172881b96d&chksm=fccc019acbbb888c419beb4366a98a94a30be829b11b51e1e00fbd24deaaefbccc27ce4c8b48&mpshare=1&scene=23&srcid=&sharer_sharetime=1571197826923&sharer_shareid=740d3594a7d56bac2db24d531aeafff1#rd

    阅读全文>>

作者:miki艾比利分类:【jvm浏览(122评论(0

2019-10-16 15:36:15

javascript常用正则表达式(-)

    Javascript 与正则表达式

     

    一、正则表达式(regular expression简称res)

    1、定义:

    一个正则表达式就是由普通字符以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。

    正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

     

    2、作用:

    正则表达式提供了功能强大、灵活而又高效的方法来处理文本。正则表达式的全面模式匹配表示法可以快速地分析大量的文本以找到特定的字符模式;

    提取、编辑、替换或删除文本子字符串;或将提取的字符串添加到集合以生成报告。

    3、主要用途:

    正则表达式被用来匹配一组文字。

    通常,它有两类用途:

    1. 数据有效性验证
    2. 查找和替换 

     

    4、如何来构造正则表达式:

    构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与操作符将小的表达式结合在一起来创建更大的表达式

    可以通过在一对分隔符之间放入表达式模式的各种组件来构造一个正则表达式。对 JScript 而言,分隔符为一对正斜杠 (/) 字符。

    1. 构造器函数方法使用方法如下:
      new RegExp("pattern"[, "flags"])

     

    1. 文本格式: /pattern/flags

    参数说明:

    pattern :一个正则表达式文本

    flags : 如果存在,将是以下值: 

    g global match(全局匹配)

    i ignore case(忽略大小写)

    gi both global match and ignore case(匹配所有可能的值,也忽略大小写)

    注意:文本格式中的参数不要使用引号标记,而构造器函数的参数则要使用引号标记。所以下面的
    表达式建立同样的正则表达式:/ab+c/i   等价于   new RegExp("ab+c", "i")

    使用 文本格式 文本的长度最大支持128个字符,

    描述:
    当使用构造函数的时候,必须使用正常的字符串避开规则(在字符串中加入前导字符\ )是必须的。
    例如,下面的两条语句是等价的:
    re = new RegExp("\\w+")
    re = /\w+/

     

    三、正则表达式的常用方法:

     

    regexp.test(string)

    用来测试一个字符串是否能够被匹配。它返回turefalse两个值。

    regexp.exec(string)

    在指定的字符串中执行搜寻一个匹配,匹配的结果是通过一个数组返回。

     

    四、与正则表达式有关的字符串对象的方法:

     

    string.replace(pattern,string)

    替换在正则表达式查找中找到的文本。

    string.search(pattern)

    通过正则表达式查找相应的字符串,只是判断有无匹配的字符串。如果查找成功,search返回匹配串的位置, 否则返回-1 

    string.match(pattern)

    match方法执行全局查找,查找结果存放在一个数组里。 

     

    五、常用的正则表达式的操作符

     

    Symbol

    Function

    \

    转义符

    (), (?:), (?=), []

    括号

    *, +, ?, {n}, {n,}, {n,m}

    限定符

    ^, $, \anymetacharacter

    定位符

    |

     

     

    八、一些常用的正则表达式示例:

     

    1、匹配所有的正数:^[0-9]+$

    2、匹配所有的小数:^\-?[0-9]*\.?[0-9]*$

    3匹配所有的整数:^\-?[0-9]+$

    4、提取信息中的中文字符串: [\u4e00-\u9fa5]*

    5、提取信息中的邮件地址:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
    6
    、提取信息中的中国手机号码:(86)*0*13\d{9}
    7
    、提取信息中的中国固定电话号码:(\(\d{3,4}\)|\d{3,4}-|\s)?\d{8}
    8
    、提取信息中的中国邮政编码:[1-9]{1}(\d+){5}
    9
    、提取信息中的中国身份证号码:\d{18}|\d{15}
    10
    、提取信息中的任何数字:(-?\d*)(\.\d+)?
    11
    匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/

     

    则表达式用于字符串处理、表单验证等场合,实用高效。
    现将一些常用的表达式收集于此,以备不时之需。

    匹配中文字符的正则表达式: [\u4e00-\u9fa5]
    评注:匹配中文还真是个头疼的事,有了这个表达式就好办了

    匹配双字节字符(包括汉字在 )[^\x00-\xff]
    评注:可以用来计算字符串的长度(一个双字节字符长度计2ASCII字符计1

    匹配空白行的正 则表达式:\n\s*\r
    评注:可以用来删除空白行

    匹配HTML标记的正则表达式:<(\S*?) [^>]*>.*?</\1>|<.*? />
    评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对 于复杂的嵌套标记依旧无能为力

    匹配首尾空白字符的正则表达式:^\s*|\s*$
    评注:可以用来删除行首行尾的空白字符(包括空 格、制表符、换页符等等),非常有用的表达式

    匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.] \w+)*\.\w+([-.]\w+)*
    评注:表单验证时很实用

    匹配网址URL的正则表达式:[a-zA- z]+://[^\s]*
    评注:网上流传的版本功能很有限,上面这个基本可以满足需求

    匹配帐号是否合法(字母开头,允许5-16 字节,允许字母数字下划线)^[a-zA-Z][a-zA-Z0-9_]{4,15}$
    评注:表单验证时很实用

    匹配国内电话号 码:\d{3}-\d{8}|\d{4}-\d{7}
    评注:匹配形式如 0511-4405222  021-87888822

    配腾讯QQ号:[1-9][0-9]{4,}
    评注:腾讯QQ号从10000开始

    匹配中国邮政编码:[1-9]\d{5}(?! \d)
    评注:中国邮政编码为6位数字

    匹配身份证:\d{15}|\d{18}
    评注:中国的身份证为15位或18

    ip地址:\d+\.\d+\.\d+\.\d+
    评注:提取ip地址时有用

    匹配特定数字:
    ^[1-9]\d*$     //匹配正整数
    ^-[1-9]\d*$   //匹配负整数
    ^-?[1-9]\d*$   //匹配整数
    ^[1-9]\d*|0$   //匹配非负整数(正整数 + 0
    ^-[1-9]\d*|0$   //匹配非正整数(负整数 + 0
    ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$    //匹配正浮点数
    ^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  //匹配负浮点数
    ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$   //匹配浮点数
    ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   //匹配非负浮点数(正浮点  + 0
    ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  //匹配非正浮点数(负浮点  + 0
    评注:处理大量数据时有用,具体应用时注意修正

    匹配特定字符串:
    ^[A-Za-z]+$  //匹配由26 个英文字母组成的字符串
    ^[A-Z]+$  //匹配由26个英文字母的大写组成的字符串
    ^[a-z]+$  //匹配由26个英文字母 的小写组成的字符串
    ^[A-Za-z0-9]+$  //匹配由数字和26个英文字母组成的字符串
    ^\w+$  //匹配由数字、26 英文字母或者下划线组成的字符串
    评注:最基本也是最常用的一些表达式

     

    加了时间验证的

    ^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-)) (20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$

    阅读全文>>

作者:miki艾比利分类:【web前端浏览(158评论(0

2019-10-15 17:02:40