暂时未有相关云产品技术能力~
引言今天给大家介绍一个比较好玩的库,今天在看别人写的代码的时候发现在代码中用到了govaluate库,这个库可以让我们在golang代码中计算一个表达式的结果,这让我们在实现一些条件判断业务场景的时候非常便利。govaluate与 JavaScript 中的eval功能类似,用于计算任意表达式的值。此类功能函数在 JavaScript/Python 等动态语言中比较常见。govaluate让 Go 这个编译型语言也有了这个能力!快速使用安装1. 2. $ go get github.com/Knetic/govaluate使用package main import ( "fmt" "github.com/Knetic/govaluate" "log" ) func main() { // 简单表达式 无参数 expr, err := govaluate.NewEvaluableExpression("10>0") if err != nil { log.Fatal("syntax error:",err) } result, err := expr.Evaluate(nil) if err != nil { log.Fatal("evaluate error:err") } fmt.Println(result) }使用govaluate计算表达式只需要两步:调用NewEvaluableExpression()将表达式转为一个表达式对象;调用表达式对象的Evaluate方法,传入参数,返回表达式的值。上面演示了一个很简单的例子,我们使用govaluate计算10 > 0的值,该表达式不需要参数,故传给Evaluate()方法nil值。当然,这个例子并不实用,显然我们直接在代码中计算10 > 0更简单。但问题是,有些时候我们并不知道需要计算的表达式的所有信息,甚至我们都不知道表达式的结构。这时govaluate的作用就体现出来了。参数govaluate支持在表达式中使用参数,调用表达式对象的Evaluate()方法时通过map[string]interface{}类型将参数传入计算。其中map的键为参数名,值为参数值。例如:package main import ( "fmt" "github.com/Knetic/govaluate" ) func main() { expr1, _ := govaluate.NewEvaluableExpression("foo >0") param:= make(map[string]interface{}) param["foo"] = -1 r1, _ := expr1.Evaluate(param) fmt.Printf("r1:%+v\n",r1) expr2, _ := govaluate.NewEvaluableExpression("a * b") param1 := make(map[string]interface{}) param1["a"] = 2 param1["b"] =4 r2,_:= expr2.Evaluate(param1) fmt.Printf("r2:%+v",r2) expr3, _ := govaluate.NewEvaluableExpression("(a / b) * 100") param3 := make(map[string]interface{}) param3["a"] = 1024 param3["b"] = 512 r3,_:= expr3.Evaluate(param3) fmt.Printf("r3:%+v",r3) }一次编译,多次运行使用带参数的表达式,我们可以实现一个表达式的一次“编译”,多次运行。只需要使用编译返回的表达式对象即可,可多次调用其Evaluate()方法:package main import ( "fmt" "github.com/Knetic/govaluate" ) func main() { expr2, _ := govaluate.NewEvaluableExpression("a * b") param1 := make(map[string]interface{}) param1["a"] = 2 param1["b"] =4 r2,_:= expr2.Evaluate(param1) fmt.Printf("r2:%+v",r2) param2 := make(map[string]interface{}) param2["a"] = 5 param2["b"] =6 r23,_:= expr2.Evaluate(param2) fmt.Printf("r223:%+v",r23) }上面的代码的运行结果,第一次运行 根据传递的参数得到的结果是8,第二次运行,由于我们更换了参数,但是还是复用了【a*b】的表达式,所以结果是30。函数如果仅仅能进行常规的算数和逻辑运算,govaluate的功能会大打折扣。govaluate提供了自定义函数的功能。所有自定义函数需要先定义好,存入一个map[string]govaluate.ExpressionFunction变量中,然后调用govaluate.NewEvaluableExpressionWithFunctions()生成表达式,此表达式中就可以使用这些函数了。自定义函数类型为func (args ...interface{}) (interface{}, error),如果函数返回错误,则这个表达式求值返回错误。package main import ( "fmt" "github.com/Knetic/govaluate" ) func main() { funcs:= map[string]govaluate.ExpressionFunction{ "strlen":func(argus ...interface{})(interface{},error) { len:= len(argus[0].(string)) return len,nil }, } exprString:="strlen('testing')" expr, _ := govaluate.NewEvaluableExpressionWithFunctions(exprString, funcs) r, _ := expr.Evaluate(nil) fmt.Println(r) }上面例子中,我们定义一个函数strlen计算第一个参数的字符串长度。表达式strlen('teststring')调用strlen函数返回字符串teststring的长度。函数可以接受任意数量的参数,而且可以处理嵌套函数调用的问题。所以可以写出类似下面这种复杂的表达式:sqrt(x1 ** y1, x2 ** y2) max(someValue, abs(anotherValue), 10 * lastValue)访问器在 Go 语言中,访问器(Accessors)就是通过.操作访问结构中的字段。如果传入的参数中有结构体类型,govaluate也支持使用.访问其内部字段或调用它们的方法:package main import ( "fmt" "github.com/Knetic/govaluate" ) type User struct { FirstName string LastName string Age int } func (u User) FullName()string { return u.FirstName + u.LastName } func main() { u := User{FirstName: "go", LastName: "jack", Age: 18} parameters := make(map[string]interface{}) parameters["u"] = u expr, _ := govaluate.NewEvaluableExpression("u.FullName()") result, _ := expr.Evaluate(parameters) fmt.Println("user", result) expr, _ = govaluate.NewEvaluableExpression("u.Age > 18") result, _ = expr.Evaluate(parameters) fmt.Println("age > 18?", result) }在上面代码中,我们定义了一个User结构,并为它编写了一个Fullname()方法。第一个表达式中,我们调用u.Fullname()返回全名,第二个表达式比较年龄是否大于 18。需要注意的一点是,我们不能使用foo.SomeMap['key']的方式访问map的值。由于访问器涉及到很多反射,所以它一般比直接使用参数慢 4 倍左右。如果能使用参数的形式,尽量使用参数。在上面的例子中,我们可以直接调用u.Fullname(),将结果作为参数传给表达式求值。涉及到复杂的计算可以通过自定义函数来解决。我们还可以实现govaluate.Parameter接口,对于表达式中使用的未知参数,govaluate会自动调用其Get()方法获取:// src/github.com/Knetic/govaluate/parameters.go type Parameters interface { Get(name string) (interface{}, error) }例如,我们可以让User实现Parameter接口:package main import ( "errors" "fmt" "github.com/Knetic/govaluate" ) type User struct { FirstName string LastName string Age int } func (u User) FullName()string { return u.FirstName + u.LastName } func (u User)Get(name string) (interface{},error) { if name =="FullName" { return u.FirstName + " " + u.LastName,nil } return nil,errors.New("unsupported field " + name) } func main() { u := User{FirstName: "go", LastName: "jack", Age: 18} expr1,_:=govaluate.NewEvaluableExpression("FullName") rr,_:=expr1.Eval(u) fmt.Println("user",rr) }表达式对象实际上有两个方法,一个是我们前面用的Evaluate(),这个方法接受一个map[string]interface{}参数。另一个就是我们在这个例子中使用的Eval()方法,该方法接受一个Parameter接口。实际上,在Evaluate()实现内部也是调用的Eval()方法:/ src/github.com/Knetic/govaluate/EvaluableExpression.go func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) { if parameters == nil { return this.Eval(nil) } return this.Eval(MapParameters(parameters)) }在表达式计算时,未知的参数都需要调用Parameter的Get()方法获取。上面的例子中我们直接使用FullName就可以调用u.Get()方法返回全名。支持的操作和类型govaluate支持的操作和类型与 Go 语言有些不同。一方面govaluate中的类型和操作不如 Go 丰富,另一方面govaluate也对一些操作进行了扩展。算数、比较和逻辑运算:+ - / * & | ^ ** % >> <<:加减乘除,按位与,按位或,异或,乘方,取模,左移和右移;> >= < <= == != =~ !~:=~为正则匹配,!~为正则不匹配;|| &&:逻辑或和逻辑与。常量:数字常量,govaluate中将数字都作为 64 位浮点数处理;字符串常量,注意在govaluate中,字符串用单引号';日期时间常量,格式与字符串相同,govaluate会尝试自动解析字符串是否是日期,只支持 RFC3339、ISO8601等有限的格式;布尔常量:true、false。其他:圆括号可以改变计算优先级;数组定义在()中,每个元素之间用,分隔,可以支持任意的元素类型,如(1, 2, 'foo')。实际上在govaluate中数组是用[]interface{}来表示的;三目运算符:? :。在下面代码中,govaluate会先将2014-01-02和2014-01-01 23:59:59转为time.Time类型,然后再比较大小:func main() { expr, _ := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'") result, _ := expr.Evaluate(nil) fmt.Println(result) }错误处理在上面的例子中,我们刻意忽略了错误处理。实际上,govaluate在创建表达式对象和表达式求值这两个操作中都可能产生错误。在生成表达式对象时,如果表达式有语法错误,则返回错误。表达式求值,如果传入的参数不合法,或者某些参数缺失,或者访问结构体中不存在的字段都会报错总结govaluate虽然支持的类型和操作优先,但是对于一些需要通过前端页面传递参数生成判断表达式的场景还是能非常好的实现,所以多掌握一些golang的库,可以让我们在业务实现的时候更加的灵活。
IO模型IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIOBIO(Blocking IO )同步阻塞模型,一个客户端连接对应一个处理线程BIO代码示例: import java.net.ServerSocket; import java.net.Socket; import java.util.logging.Handler; public class SocketServer { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(9000); while (true){ System.out.println("等待连接"); //阻塞连接 Socket clientSocket = serverSocket.accept(); System.out.println("有客户端连接。。。"); // handle(clientSocket); new Thread(new Runnable() { @Override public void run() { try { handle(clientSocket); } catch (Exception e) { e.printStackTrace(); } } }).start(); } } public static void handle(Socket clientSocket) throws Exception{ byte[] bytes = new byte[1024]; System.out.println("准备read。。"); //接收客户端的数据,阻塞方法,没有数据可读时就阻塞 int read = clientSocket.getInputStream().read(bytes); System.out.println("read 完毕。"); if (read !=-1){ System.out.println("接收到客户端数据:" + new String(bytes,0,read)); } clientSocket.getOutputStream().write("helloClient".getBytes()); clientSocket.getOutputStream().flush(); } }//客户端代码 import java.io.IOException; import java.net.Socket; public class SocketClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 9000); //向服务端发送数据 socket.getOutputStream().write("HelloServer".getBytes()); socket.getOutputStream().flush(); System.out.println("向服务端发送数据结束"); byte[] bytes = new byte[1024]; //接收服务端回传的数据 socket.getInputStream().read(bytes); System.out.println("接收到服务端的数据:" + new String(bytes)); socket.close(); } }缺点:从上面的代码我们可以看出来,BIO代码中连接事件和读写数据事件都是阻塞的,所以这种模式的缺点非常的明显1、如果我们连接完成以后,不做读写数据操作会导致线程阻塞,浪费资源2、如果没来一个连接我们都需要启动一个线程处理,那么会导致服务器线程太多,压力太大,比如C10K;应用场景:BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,但是程序比较简单。NIO(Non Blocking IO)同步非阻塞模型,服务器实现模式为一个线程可以处理多个请求连接,客户端发送的连接请求都会注册到多路复用器(selector)上,多路复用器轮询到连接有IO请求就进行处理,JDK1.4开始引入。//没有引入多路复用器的代码 import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class NioServer { static List<SocketChannel> channelList = new ArrayList<>(); public static void main(String[] args) throws Exception { //创建NIO ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //设置非阻塞 serverSocket.configureBlocking(false); System.out.println("服务启动。。"); while (true) { //非阻塞模式accept方法不会阻塞,否则会阻塞 //NIO的非阻塞模式是由操作系统内部实现,底层调用了Linux内核的accept函数 SocketChannel socketChannel = serverSocket.accept(); if (socketChannel != null) { System.out.println("连接成功"); //设置socketchannel为非阻塞 socketChannel.configureBlocking(false); //保存客户端连接到list channelList.add(socketChannel); } //遍历连接读数据 Iterator<SocketChannel> iterator = channelList.iterator(); while (iterator.hasNext()) { SocketChannel sc = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); //非阻塞模式read 方式不会阻塞 否则会阻塞 int len = sc.read(byteBuffer); if (len > 0) { System.out.println("接收到消息:" + new String(byteBuffer.array())); } else if (len == -1) { // 如果客户端断开,把socket从集合中去掉 iterator.remove(); System.out.println("客户端断开连接"); } } } } }缺点:如果连接数太多的话,会有大量的无效遍历,假如有10000个连接,其中只有1000个 连接有写数据,但是 由于其他9000个连接并没有断开看我们还是每次轮询遍历一万次,其中有 十分之一的遍历都是无效的,这显然是一个非常浪费资源的做法。NIO引入多路复用器的代码示例:package com.jack.nio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.security.Key; import java.util.Iterator; import java.util.Set; public class NioSelectorServer { public static void main(String[] args) throws Exception { //创建NIO ServerSocketChannle ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(9000)); //设置ServerSocketChannel为非阻塞 serverSocket.configureBlocking(false); //打开Selector处理channel,即创建epoll Selector selector = Selector.open(); //把ServerSocketChannel注册selector上,并且select对客户端accept连接操作感兴趣 serverSocket.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务启动"); // while (true) { //阻塞等待需要处理的事件发生 selector.select(); //获取selector中注册的全部事件的SelectionKey实例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); //遍历selectionKeys对事件进行处理 while (iterator.hasNext()) { SelectionKey key = iterator.next(); //如果是accept事件,则进行连接获取和事件注册 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = server.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客户端连接成功"); } else if (key.isReadable()) { //进行数据读取 SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = socketChannel.read(byteBuffer); // 如果有数据,把数据打印出来 if (len > 0) { System.out.println("接收到消息:" + new String(byteBuffer.array())); } else if (len == -1) { // 如果客户端断开连接,关闭Socket System.out.println("客户端断开连接"); socketChannel.close(); } } //从事件集合里删除本次处理的key,防止下次select重复处理 iterator.remove(); } } } }上面代码是利用NIO一个线程处理所有请求,这种单个线程处理的方式肯定是存在问题的,例如现在有10w个请求中,有1w个连接进行读写数据,那么SelectionKey就会有1w个请求,所以我们需要循环这1w个事件进行处理,比较费时间,如果这个时候再有连接进来,只能阻塞。。NIO有三大核心组件:Channel(通道),Buffer(缓冲区)Selector(多路复用器)1、channel 类似流,每个channel对应一个buffer缓冲区,buffer底层是个数组。2、channel会注册到selector上,由selector根据channel的读写事件发生将其交由某个空闲的线程处理3、NIO的Buffer和channel都是既可以读又可以写的NIO底层在JDK1.4版本是用linux的内核函数select()或poll()来实现,跟上面的NioServer代码类似,selector每次都会轮询所有的sockchannel看下哪个channel有读写事件,有的话就处理,没有就继续遍历,JDK1.5开始引入了epoll基于事件响应机制来优化NIO。举个例子:例如我们去酒吧喝酒,在吧台坐下了20个人,中间一个服务员,select()或者poll()模式就是,服务员每次都是询问这个20个人是否需要喝酒,而epoll模型则是,20个人谁需要喝酒谁就举手,服务员每次只处理举手的那几个人即可 NioSelectorServer 代码里如下几个方法非常重要,我们从Hotspot与Linux内核函数级别来理解下Selector.open() //创建多路复用器 socketChannel.register(selector, SelectionKey.OP_READ) //将channel注册到多路复用器上 selector.select() //阻塞等待需要处理的事件发生总结:NIO整个调用流程就是Java调用了操作系统的内核函数来创建Socket,获取Socket文件描述符,再创建一个Selector对象,对应操作系统的Epoll描述符,将获取到的Socket连接的文件描述符的事件绑定到Selector对应的文件描述符上,进行事件的异步通知,这样就实现了使用一条线程,并且不需要太多的无效遍历,将事件处理交给了操作系统内核(操作系统的终端程序),大大提高了效率。Epoll函数详解int epoll_create(int size);创建一个epoll实例,并返回一个非负数作为文件描述符,用于对epoll接口的所有后续调用。参数size代表可能会容纳size个描述符,但size不是一个最大值,只是提示操作系统它的数量级,现在这个参数基本上已经弃用了。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);使用文件描述符epfd引用epoll实例,对目标文件描述符fs执行op操作。参数epfd表示epoll对应的文件描述符,参数fd表示socket对应的文件描述符。参数op有以下几个值:EPOLL_CTL_ADD:注册新的fd到epfd中,并关联事件event;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中移除fd,并且忽略掉绑定的event,这时event可以为null;参数event是一个结构体 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;events有很多可选值,这里只举例最常见的几个:EPOLLIN :表示对应的文件描述符是可读的;EPOLLOUT:表示对应的文件描述符是可写的;EPOLLERR:表示对应的文件描述符发生了错误;成功则返回0,失败返回-1int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);等待文件描述符epfd上的事件。epfd就是Epoll对应的文件描述符,events表示调用者所有可用事件的集合,maxevents表示最大等到多少个事件就返回,timeout是超时时间。I/O多路复用底层主要用Linux内核函数(select 、poll、epoll)来实现。selectpollepoll(jdk1.5及以上)操作方式遍历遍历回调底层实现数组链表哈希表O效率每次调用都进行线性遍历,时间复杂度O(n)每次调用都进行线性遍历,时间复杂度O(n)事件通知方式,每当有IO事件放生,系统注册的回调函数就会被调用,事件复杂度O(1)最大连接有上限无上限无上限AIO模型异步非阻塞模型,由操作系统完成后回调通知服务端程序启动线程去处理,一般适用于连接数比较多且连接时间比较长的应用应用场景AIO方式适用于连接数 多且连接比较长(重操作)的架构,JDK1.7开始支持package com.jack.aio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannel; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; public class AIOServer { public static void main(String[] args) throws Exception { final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { try { System.out.println("2--" + Thread.currentThread().getName()); //再此接收客户端连接,如果不写这行代码后面的客户端连接不上服务端 serverChannel.accept(attachment,this); System.out.print(socketChannel.getRemoteAddress()); ByteBuffer buffer = ByteBuffer.allocate(1024); socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { System.out.println("3--"+Thread.currentThread().getName()); buffer.flip(); System.out.println(new String(buffer.array(), 0, result)); socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes())); } @Override public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); } }); } catch (IOException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { } }); System.out.println("1--"+Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); } }//客户端 package com.jack.aio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; public class AIOClient { public static void main(String... args) throws Exception { AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get(); socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes())); ByteBuffer buffer = ByteBuffer.allocate(512); Integer len = socketChannel.read(buffer).get(); if (len != -1) { System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len)); } } }为什么Netty使用NIO而不是AIO?因为在Linux系统上,AIO的底层实现扔使用Epoll模型,没有很好的使用AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易再次进行深度优化,Linux上AIO还不够成熟。Netty是异步非阻塞框架,Netty在NIO上做了很多异步封装。
引言上一篇博客我们将tomcat源码在本地成功运行了,所以在本篇博客中我们从源码层面分析,tomcat在启动的过程中,是如何初始化servlet容器的。我们平常都是将我们的服务部署到 tomcat中,然后修改一下配置文件,启动就可以对外提供 服务了,但是我们对于其中的一些流程并不是非常的了解,例如如何加载的web.xml等。这是我们分析servlet 和 sringMVC必不可少的过程。注释源码地址:https://github.com/good-jack/tomcat_source/tree/master一、代码启动tomcat平常我们不论是Windows还是linux,我们都是通过脚本来启动tomcat,这对于我们分析源码不是很友好,所以我们 需要通过代码启动,启动代码如下:Tomcat tomcat = new Tomcat(); tomcat.setPort(8080); //new 出各层容器,并且维护各层容器的关系 tomcat.addWebapp("/","/"); tomcat.start(); //阻塞监听端口 tomcat.getServer().await();启动代码还是非常非常简单,从代码中我们就可以看出,我们本篇博客主要分析的就是 addWebapp()方法和start()方法,通过这两个方法我们就可以找到servlet容器是在什么时候被初始化的。二、tomcat框架 在我们进行分析上面两个方法之前,我们先总结一下tomcat的基础框架,其实从我们非常熟悉的 server.xml配置文件中就可以知道,tomcat就是一系列父子容器组成:Server ---> Service --> Connector Engine addChild---> context(servlet容器) ,这就是我们从配置文件中分析出来的几个容器,tomcat启动时候就是逐层启动容器。三、创建容器(addWebapp())3.1 方法 调用流程图上面的流程图就是,从源码中逐步分析出来的几个重要的方法,这对于我们分析源码非常有帮助。3.2 源码分析1)通过反射获得configContext监听器方法路径:package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase); public Context addWebapp(Host host, String contextPath, String docBase) { //通过反射获得一个监听器 ContextConfig, //通过反射得到的一定是LifecycleListener的一个实现类,进入getConfigClass得到实现类(org.apache.catalina.startup.ContextConfig) LifecycleListener listener = null; try { Class<?> clazz = Class.forName(getHost().getConfigClass()); listener = (LifecycleListener) clazz.getConstructor().newInstance(); } catch (ReflectiveOperationException e) { // Wrap in IAE since we can't easily change the method signature to // to throw the specific checked exceptions throw new IllegalArgumentException(e); } return addWebapp(host, contextPath, docBase, listener); }2) 获得一个context容器(StandardContext)在下面代码中,createContext()方法通过反射加载StandardContext容器,并且将设置监听ContextConfig, ctx.addLifecycleListener(config);public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { silence(host, contextPath); //获得一个context容器(StandardContext) Context ctx = createContext(host, contextPath); ctx.setPath(contextPath); ctx.setDocBase(docBase); if (addDefaultWebXmlToWebapp) { ctx.addLifecycleListener(getDefaultWebXmlListener()); } ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); //把监听器添加到context中去 ctx.addLifecycleListener(config); if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) { // prevent it from looking ( if it finds one - it'll have dup error ) ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath()); } if (host == null) { //getHost会逐层创建容器,并维护容器父子关系 getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }3)维护各层容器getHost()方法中得到各层容器,并且维护父亲容器关系,其中包括,server容器、Engine容器。并且将StandardContext容器通过getHost().addChild(ctx); 调用containerBase中的addChild()方法维护在 children 这个map中。 public Host getHost() { //将每一层的容器都new 出来 Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); //维护tomcat中的父子容器 getEngine().addChild(host); return host; }getEngine().addChild(host); 方法选择调用父类containerBase中的addChild方法 @Override public void addChild(Container child) { if (Globals.IS_SECURITY_ENABLED) { PrivilegedAction<Void> dp = new PrivilegedAddChild(child); AccessController.doPrivileged(dp); } else { //这里的child 参数是 context 容器 addChildInternal(child); } } addChildInternal()方法的 核心代码 private void addChildInternal(Container child) { if( log.isDebugEnabled() ) log.debug("Add child " + child + " " + this); synchronized(children) { if (children.get(child.getName()) != null) throw new IllegalArgumentException("addChild: Child name '" + child.getName() + "' is not unique"); child.setParent(this); // May throw IAE children.put(child.getName(), child); }四、启动容器(tomcat.start())4.1、方法调用流程图4.2、源码分析说明:StandardServer 、StandardService、StandardEngine等容器都是继承LifecycleBase所以这里是模板模式的经典应用1)逐层启动容器此时的server对应的是我们前面创建的StandardServer public void start() throws LifecycleException { //防止server容器没有创建 getServer(); //获得connector容器,并且将得到的connector容器设置到service容器中 getConnector(); //这里的start的实现是在 LifecycleBase类中实现 //LifecycleBase方法是一个模板方法,在tomcat启动流程中非常关键 server.start(); }2) 进入start方法进入LifecycelBase中的start方法,其中核心方法是startInternal。从上面我们知道现在我们调用的是StandardServer容器的startInternal()方法,所以我们这里选择的是StandardServer方法路径:org.apache.catalina.core.StandardServer.startInternal()protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null); setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { //启动 service容器,一个tomcat中可以配置多个service容器,每个service容器都对应这我们的一个服务应用 for (Service service : services) { //对应 StandardService.startInternal() service.start(); } } }从上面代码中我们可以看出,启动server容器的时候需要启动子容器 service容器,从这里开始就是容器 逐层向向内引爆,所以接下来就是开始依次调用各层容器的star方法。在这里就不在赘述。2)ContainerBase中的startInternal()方法 核心代码,从这开始启动StandardContext容器 // Start our child containers, if any //在addWwbapp的流程中 addChild方法中加入的,所以这里需要找出来 //这里找出来的就是 context 容器 Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { //通过线程池 异步的方式启动线程池 开始启动 context容器,进入new StartChild results.add(startStopExecutor.submit(new StartChild(child))); }new StartChild(child)) 方法开始启动StandardContext容器 private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { //开始启动context,实际调用 StandardContext.startInternal() child.start(); return null; } }StandardContext.startInternal() 方法中的核心代码: protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); //lifecycleListeners 在addwebapp方法的第一步中,设置的监听的 contextConfig对象 for (LifecycleListener listener : lifecycleListeners) { //这里调用的是 contextConfig的lifecycleEvent()方法 listener.lifecycleEvent(event); } } 进入到 contextConfig中的lifecycleEvent()方法public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with try { context = (Context) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { //完成web.xml的内容解析 configureStart(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase != null) { context.setDocBase(originalDocBase); } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } } 在上面方法中,完成对web.xml的加载和解析,同时加载xml中配置的servlet并且封装成wrapper对象。3)、启动servlet容器,StandardContext.startInternal() 中的 loadOnStartup(findChildren())方法public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { //这里的 Wrapper就是 我们前面封装的 servlet Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0) { continue; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { //通过 load 方法 最终会调用 servlet的init方法 wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()), StandardWrapper.getRootCause(e)); // NOTE: load errors (including a servlet that throws // UnavailableException from the init() method) are NOT // fatal to application startup // unless failCtxIfServletStartFails="true" is specified if(getComputedFailCtxIfServletStartFails()) { return false; } } } } return true; } 在上面方法中,完成对web.xml的加载和解析,同时加载xml中配置的servlet并且封装成wrapper对象。3)、启动servlet容器,StandardContext.startInternal() 中的 loadOnStartup(findChildren())方法public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { //这里的 Wrapper就是 我们前面封装的 servlet Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0) { continue; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { //通过 load 方法 最终会调用 servlet的init方法 wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()), StandardWrapper.getRootCause(e)); // NOTE: load errors (including a servlet that throws // UnavailableException from the init() method) are NOT // fatal to application startup // unless failCtxIfServletStartFails="true" is specified if(getComputedFailCtxIfServletStartFails()) { return false; } } } } return true; }通过 load 方法 最终会调用 servlet的init方法。五、总结上面内容就是整个tomcat是如何调用servlet初始化方法的流程,整个流程小编的理解,如果有错误,欢迎指正,小编已经在源码中重要部分进行了注释,所以如果有需要的各位读者,可以下载我的注释 源码,注释源码地址:https://github.com/good-jack/tomcat_source/tree/master
一、引言上一篇博客中介绍了GPG的基本使用,在原来的项目中我们对接gpg加密的时候,采用的方式,利用java代码执行shell命令行,但是在新对接的项目中,这种方式对方利用 命令行能解密,但是利用他们的代码不能解密,原因是他们代码生成并不是pgp后缀 的加密文件, 而是pgp格式的加密文件。所以小编也利用java生成一个加密文件,这样双方可以愉快的加解密了。二、代码实现2.1 maven 依赖 <dependencies> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpg-jdk15on</artifactId> <version>1.50</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.50</version> </dependency> </dependencies>2.2 工具类代码 import java.io.*; import java.security.GeneralSecurityException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.util.Iterator; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPOnePassSignatureList; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; /** * pgp 加解密 */ public class PgpUtils { private static PgpUtils INSTANCE = null; public static PgpUtils getInstance() { if (INSTANCE == null) INSTANCE = new PgpUtils(); return INSTANCE; } private PgpUtils() { } public PGPPublicKey readPublicKey(InputStream in) throws IOException, PGPException { in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in); PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in); // // we just loop through the collection till we find a key suitable for encryption, in the real // world you would probably want to be a bit smarter about this. // PGPPublicKey key = null; // // iterate through the key rings. // Iterator<PGPPublicKeyRing> rIt = pgpPub.getKeyRings(); while (key == null && rIt.hasNext()) { PGPPublicKeyRing kRing = rIt.next(); Iterator<PGPPublicKey> kIt = kRing.getPublicKeys(); while (key == null && kIt.hasNext()) { PGPPublicKey k = kIt.next(); if (k.isEncryptionKey()) { key = k; } } } if (key == null) { throw new IllegalArgumentException("Can't find encryption key in key ring."); } return key; } /** * Load a secret key ring collection from keyIn and find the secret key corresponding to * keyID if it exists. * * @param keyIn input stream representing a key ring collection. * @param keyID keyID we want. * @param pass passphrase to decrypt secret key with. * @return * @throws IOException * @throws PGPException * @throws NoSuchProviderException */ private PGPPrivateKey findSecretKey(InputStream keyIn, long keyID, char[] pass) throws IOException, PGPException, NoSuchProviderException { PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(org.bouncycastle.openpgp.PGPUtil.getDecoderStream(keyIn)); PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID); if (pgpSecKey == null) { return null; } PBESecretKeyDecryptor a = new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(pass); return pgpSecKey.extractPrivateKey(a); } /** * decrypt the passed in message stream */ public void decryptFile(InputStream in, OutputStream out, InputStream keyIn, char[] passwd) throws Exception { Security.addProvider(new BouncyCastleProvider()); in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in); PGPObjectFactory pgpF = new PGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); // // the first object might be a PGP marker packet. // if (o instanceof PGPEncryptedDataList) { enc = (PGPEncryptedDataList) o; } else { enc = (PGPEncryptedDataList) pgpF.nextObject(); } // // find the secret key // Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects(); PGPPrivateKey sKey = null; PGPPublicKeyEncryptedData pbe = null; while (sKey == null && it.hasNext()) { pbe = it.next(); sKey = findSecretKey(keyIn, pbe.getKeyID(), passwd); } if (sKey == null) { throw new IllegalArgumentException("Secret key for message not found."); } PublicKeyDataDecryptorFactory b = new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").setContentProvider("BC").build(sKey); InputStream clear = pbe.getDataStream(b); PGPObjectFactory plainFact = new PGPObjectFactory(clear); Object message = plainFact.nextObject(); if (message instanceof PGPCompressedData) { PGPCompressedData cData = (PGPCompressedData) message; PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream()); message = pgpFact.nextObject(); } if (message instanceof PGPLiteralData) { PGPLiteralData ld = (PGPLiteralData) message; InputStream unc = ld.getInputStream(); int ch; while ((ch = unc.read()) >= 0) { out.write(ch); } } else if (message instanceof PGPOnePassSignatureList) { throw new PGPException("Encrypted message contains a signed message - not literal data."); } else { throw new PGPException("Message is not a simple encrypted file - type unknown."); } if (pbe.isIntegrityProtected()) { if (!pbe.verify()) { throw new PGPException("Message failed integrity check"); } } } public void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey, boolean armor, boolean withIntegrityCheck) throws IOException, NoSuchProviderException, PGPException { Security.addProvider(new BouncyCastleProvider()); if (armor) { out = new ArmoredOutputStream(out); } ByteArrayOutputStream bOut = new ByteArrayOutputStream(); PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); org.bouncycastle.openpgp.PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName)); comData.close(); JcePGPDataEncryptorBuilder c = new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"); PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(c); JcePublicKeyKeyEncryptionMethodGenerator d = new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider(new BouncyCastleProvider()).setSecureRandom(new SecureRandom()); cPk.addMethod(d); byte[] bytes = bOut.toByteArray(); OutputStream cOut = cPk.open(out, bytes.length); cOut.write(bytes); cOut.close(); out.close(); } private byte[] inputStreamToByteArray(InputStream is) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[1024]; while ((nRead = is.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); return buffer.toByteArray(); } /** * verify the signature in in against the file fileName. */ private boolean verifySignature(String fileName, byte[] b, InputStream keyIn) throws GeneralSecurityException, IOException, PGPException { //in = PGPUtil.getDecoderStream(in); PGPObjectFactory pgpFact = new PGPObjectFactory(b); PGPSignatureList p3 = null; Object o = pgpFact.nextObject(); if (o instanceof PGPCompressedData) { PGPCompressedData c1 = (PGPCompressedData) o; pgpFact = new PGPObjectFactory(c1.getDataStream()); p3 = (PGPSignatureList) pgpFact.nextObject(); } else { p3 = (PGPSignatureList) o; } PGPPublicKeyRingCollection pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn)); InputStream dIn = new BufferedInputStream(new FileInputStream(fileName)); PGPSignature sig = p3.get(0); PGPPublicKey key = pgpPubRingCollection.getPublicKey(sig.getKeyID()); sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()), key); int ch; while ((ch = dIn.read()) >= 0) { sig.update((byte) ch); } dIn.close(); if (sig.verify()) { return true; } else { return false; } } private PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException { PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input)); // // we just loop through the collection till we find a key suitable for encryption, in the real // world you would probably want to be a bit smarter about this. // Iterator keyRingIter = pgpSec.getKeyRings(); while (keyRingIter.hasNext()) { PGPSecretKeyRing keyRing = (PGPSecretKeyRing) keyRingIter.next(); Iterator keyIter = keyRing.getSecretKeys(); while (keyIter.hasNext()) { PGPSecretKey key = (PGPSecretKey) keyIter.next(); if (key.isSigningKey()) { return key; } } } throw new IllegalArgumentException("Can't find signing key in key ring."); } private byte[] createSignature(String fileName, InputStream keyIn, char[] pass, boolean armor) throws GeneralSecurityException, IOException, PGPException { PGPSecretKey pgpSecKey = readSecretKey(keyIn); PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(new BouncyCastleProvider()).build(pass)); PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1).setProvider(new BouncyCastleProvider())); sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ArmoredOutputStream aOut = new ArmoredOutputStream(byteOut); BCPGOutputStream bOut = new BCPGOutputStream(byteOut); InputStream fIn = new BufferedInputStream(new FileInputStream(fileName)); int ch; while ((ch = fIn.read()) >= 0) { sGen.update((byte) ch); } aOut.endClearText(); fIn.close(); sGen.generate().encode(bOut); if (armor) { aOut.close(); } return byteOut.toByteArray(); } /** * 生成签名文件 * * @param filePath 签名文件路径 * @param privateKeyPath 私钥路径 * @param outFilePath 输出证书路径 * 证书名称必须与签名文件名称一样 多后缀: .asc * 比如: 签名文件为:di.ova 那么生成的证书必须为: di.ova.asc * @param passWord 证书密码 * @return 证书字节数组 */ public static byte[] signatureCreate(String filePath, String privateKeyPath, String outFilePath, String passWord) { try { FileInputStream privKeyIn = new FileInputStream(privateKeyPath); FileOutputStream signatureOut = new FileOutputStream(outFilePath); byte[] sig = PgpUtils.getInstance().createSignature(filePath, privKeyIn, passWord.toCharArray(), true); signatureOut.write(sig); signatureOut.flush(); signatureOut.close(); privKeyIn.close(); return sig; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 签名验证 * * @param filePath 被签名的文件路径 * @param publicKeyPath 公钥路径 * @param signFilePath 签名文件路径 * @return 是否通过 */ public static boolean verifySignature(String filePath, String publicKeyPath, String signFilePath) { try { FileInputStream pubKeyIs = new FileInputStream(publicKeyPath); FileInputStream signFile = new FileInputStream(signFilePath); byte[] signFileBytes = new byte[signFile.available()]; signFile.read(signFileBytes); final boolean verifyResult = PgpUtils.getInstance().verifySignature(filePath, signFileBytes, pubKeyIs); signFile.close(); pubKeyIs.close(); return verifyResult; } catch (Exception e) { e.printStackTrace(); return false; } } }2.3 测试代码 import org.bouncycastle.openpgp.PGPPublicKey; import java.io.*; public class PgpTest { public static void main(String[] args) throws Exception{ PgpUtils pgpUtils = PgpUtils.getInstance(); addSigunature(); verifySigunature(); encryptFile(); decryptFile(); } public static void addSigunature(){ byte[] sign= PgpUtils.signatureCreate("/Users/bijia/temp/origin_file.txt", "/Users/bijia/temp/self_gen/private-key.txt", "/Users/bijia/temp/20191014_sign_file.txt", "12345678"); System.out.println(new String(sign)); } public static void verifySigunature(){ boolean flag = PgpUtils.verifySignature("/Users/bijia/temp/origin_file.txt", "/Users/bijia/temp/self_gen/public-key.txt", "/Users/bijia/temp/20191014_sign_file.txt"); System.out.println(flag); } public static void encryptFile() throws Exception{ PgpUtils pgpUtils = PgpUtils.getInstance(); PGPPublicKey pgpPublicKey = pgpUtils.readPublicKey(new FileInputStream("/Users/bijia/temp/self_gen/public-key.txt")); OutputStream os = new FileOutputStream(new File("/Users/bijia/temp/20191014encrypt_file.txt")); pgpUtils.encryptFile(os,"/Users/bijia/temp/origin_file.txt",pgpPublicKey,false,false); } public static void decryptFile() throws Exception{ PgpUtils pgpUtils = PgpUtils.getInstance(); pgpUtils.decryptFile(new FileInputStream(new File("/Users/bijia/temp/20191014encrypt_file.txt")), new FileOutputStream(new File("/Users/bijia/temp/20191014decrypt_file.txt")), new FileInputStream(new File("/Users/bijia/temp/self_gen/private-key.txt")), "12345678".toCharArray()); } }
引言最近小编负责的一个语音质检 项目中用到了ES,下面结合实例代码分享一下java操作ES查用的查询写法。关于es的基本操作,例如新建索引、查询封装类,请参考前面博客:【ElasticSearch实战】——封装java操作es基础架构1、ES在项目中的位置2、项目查询需求 从上面查询条件来看,其中包括了精确查询,模糊查询及时间范围查询等,这些查询 都会在下面 样例代码中给出,并且还有部分统计的写法,例如机器质检不合格的数量, 人工审核不合格的数量等。3、查询代码package com.jack.search.es.service; import com.jack.common.constant.SearchConstants; import com.jack.search.es.model.CaseCallCheckListQueryInfo; import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.filter.Filter; import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.cardinality.Cardinality; import org.elasticsearch.search.aggregations.metrics.sum.Sum; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @author zhenghao * @description: 录音库查询 * @date 2019/10/1115:49 */ @Service public class CallCheckQueryService { @Autowired private QueryService queryService; @Autowired private ElasticSearchService elasticSearchService; public Map<String, Object> getCallCheckList(CaseCallCheckListQueryInfo queryInfo) { if (queryInfo == null) { return null; } //构造查询条件 BoolQueryBuilder boolQueryBuilder = this.getCallCheckQueryBuilder(queryInfo); //排序 String orderBy = queryInfo.getOrderBy(); if (StringUtils.isEmpty(orderBy)) { orderBy = String.format("%s:%s", "telTime", "desc"); } //分页 Integer pageNo = queryInfo.getPageNo(); if ((pageNo == null) || (pageNo <= 0)) { pageNo = 0; } else { pageNo -= 1; } Integer pageSize = queryInfo.getPageSize(); if ((pageSize == null) || (pageSize < 1)) { pageSize = 10; } //构造排序构造器FieldSortBuilder,设置排序参数 String[] orderStr = orderBy.split(":"); FieldSortBuilder sort = SortBuilders.fieldSort(orderStr[0]).order("desc".equalsIgnoreCase(orderStr[1])? SortOrder.DESC:SortOrder.ASC); //AggregationBuilder 相当于 mysql中的 group by //第一级机器质检不合格数据聚合 聚合名称为 hasError ,聚合数据条件为 字段 hasError>=1 FilterAggregationBuilder hasErrorAggregationBuilder = AggregationBuilders.filter("hasError", QueryBuilders.rangeQuery("hasError").gte(1)); //构造第二级子聚合 每一个质检项的值 >=1 注意:每一个checkItem 是一个字段,代表一个质检项 0代表没有命中该质检项,大于0代表表中 for (int i = SearchConstants.CHECK_ITEM_MIN; i<= SearchConstants.CHECK_ITEM_MAX; i++) { String checkItemName = String.format("checkItem%d", i); hasErrorAggregationBuilder.subAggregation(AggregationBuilders.filter(checkItemName, QueryBuilders.rangeQuery(checkItemName).gte(1))); } //第一级人工审核不合格的数据聚合 FilterAggregationBuilder auditHasErrorAggregationBuilder = AggregationBuilders.filter("auditHasError", QueryBuilders.rangeQuery("auditHasError").gte(1)); //整合查询条件,相当于组长sql cardinality 表示去重 FilterAggregationBuilder allCheckAggregationBuilder = AggregationBuilders.filter("allCheck", boolQueryBuilder) .subAggregation(hasErrorAggregationBuilder) .subAggregation(auditHasErrorAggregationBuilder) .subAggregation(AggregationBuilders.sum("totalTelLength").field("telLength")) .subAggregation(AggregationBuilders.cardinality("userId").field("userId")); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder).sort(sort).from(pageNo*pageSize).size(pageSize).aggregation(allCheckAggregationBuilder); //执行查询 SearchRequest searchRequest = new SearchRequest(); searchRequest.indices(SearchConstants.CALL_INDEX_NAME).types(SearchConstants.CALL_INDEX_TYPE).source(searchSourceBuilder); SearchResponse searchResponse = elasticSearchService.search(searchRequest); if (searchResponse == null) { return null; } //从查询结果中获得想要数据 SearchHits searchHits = searchResponse.getHits(); //获得查询结果条数 long total = searchHits.getTotalHits(); Map<String, Object> resultMap = new HashMap<>(10); resultMap.put("total", total); resultMap.put("tookInMillis", searchResponse.getTook().getMillis()); //获得查询结果并转换为map if (total > 0) { SearchHit[] searchHitsArr = searchHits.getHits(); List<Map<String, Object>> resultMapList = new ArrayList<>(searchHitsArr.length); for (int i = 0; i < searchHitsArr.length; i++) { Map<String, Object> map = searchHitsArr[i].getSourceAsMap(); resultMapList.add(map); } resultMap.put("list", resultMapList); } //获得聚合桶数据 Filter allCheckFilter = searchResponse.getAggregations().get("allCheck"); Aggregations allCheckAggregations = allCheckFilter.getAggregations(); //通话总时长 Sum sum = allCheckAggregations.get("totalTelLength"); Cardinality userIdCardinality = allCheckAggregations.get("userId"); //机器审核错误的数量 Filter hasErrorFilter = allCheckAggregations.get("hasError"); Aggregations hasErrorAggregations = hasErrorFilter.getAggregations(); //人工审核错误的数量 Filter auditHasErrorFilter = allCheckAggregations.get("auditHasError"); long errorCount = hasErrorFilter.getDocCount(); long auditErrorCount = auditHasErrorFilter.getDocCount(); resultMap.put("errorCount", errorCount); resultMap.put("auditErrorCount", auditErrorCount); resultMap.put("goodRate", total>0?1.0*(total-errorCount)/total:0); resultMap.put("auditGoodRate", total>0?1.0*(total-auditErrorCount)/total:0); resultMap.put("totalTelLength", sum.getValue()); resultMap.put("averageTelLength", total>0?sum.getValue()/total:0); resultMap.put("userCount", userIdCardinality.getValue()); for (int k = SearchConstants.CHECK_ITEM_MIN; k<= SearchConstants.CHECK_ITEM_MAX; k++) { Filter checkItemFilter = hasErrorAggregations.get(String.format("checkItem%d", k)); resultMap.put(String.format("checkItem%dCount", k), checkItemFilter.getDocCount()); } return resultMap; } /** * @Description: 构造查询条件 * @author: zhenghao * @date: 2019/10/11 15:54 */ private BoolQueryBuilder getCallCheckQueryBuilder(CaseCallCheckListQueryInfo queryInfo) { /** * BoolQueryBuilder 是es提供的一个查询接口,可以对其进行参数设置完成对应的查询 */ BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); //根据主键id查询 Long[] callRecordIds = queryInfo.getCallRecordIds(); if ((callRecordIds != null) && (callRecordIds.length > 0)) { String[] strCallRecordIds = new String[callRecordIds.length]; for (int i = 0; i < callRecordIds.length; i++) { strCallRecordIds[i] = String.valueOf(callRecordIds[i]); } //idsQuery查询多个id 相当于mysql中的 where id in(1,2,3) //filter必须匹配,但他以不评分、过滤模式来进行,这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档 boolQueryBuilder.filter(QueryBuilders.idsQuery().addIds(strCallRecordIds)); } //根据caseId精确查询 termQuery 完全匹配 Long caseId = queryInfo.getCaseId(); if (caseId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("caseId", caseId)); } //termsQuery 一次匹配多个值,查询多个公司的数据 Long[] partnerIds = queryInfo.getPartnerIds(); if ((partnerIds != null) && (partnerIds.length > 0)) { long[] pids = new long[partnerIds.length]; for (int i = 0; i < pids.length; i++) { pids[i] = partnerIds[i]; } boolQueryBuilder.filter(QueryBuilders.termsQuery("partnerId", pids)); } //注意在es中是long类型 非包装类型Long Long[] departmentIds = queryInfo.getDepartmentIds(); if ((departmentIds != null) && (departmentIds.length > 0)) { long[] dids = new long[departmentIds.length]; for (int i = 0; i < dids.length; i++) { dids[i] = departmentIds[i]; } boolQueryBuilder.filter(QueryBuilders.termsQuery("departmentId", dids)); } //根据web端传递的参数不同,区分对应的userId不同的值 Integer allotState = queryInfo.getAllotState(); if (allotState != null) { if (allotState == 0) { //精确查询 boolQueryBuilder.filter(QueryBuilders.termQuery("userId", -1)); } else { Long userId = queryInfo.getUserId(); if (userId != null) { //精确查询 boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId)); } else { //范围查询 gt 代表大于 boolQueryBuilder.filter(QueryBuilders.rangeQuery("userId").gt(0)); } } } else { Long userId = queryInfo.getUserId(); if (userId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId)); } else { Integer auditHasError = queryInfo.getAuditHasError(); // 查询已经分配质检用户的质检列表 if ((auditHasError != null) && (auditHasError == -1)) { boolQueryBuilder.filter(QueryBuilders.rangeQuery("userId").gt(0)); } } } //将string类型的字段值 去掉*匹配符及空格 String userName = queryService.trimWildcardQuery(queryInfo.getUserName()); if (!StringUtils.isEmpty(userName)) { //精确查询 boolQueryBuilder.filter(QueryBuilders.termQuery("userName", userName)); } Long auditUserId = queryInfo.getAuditUserId(); if (auditUserId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("auditUserId", auditUserId)); } String auditUserName = queryService.trimWildcardQuery(queryInfo.getAuditUserName()); if (!StringUtils.isEmpty(auditUserName)) { boolQueryBuilder.filter(QueryBuilders.termQuery("auditUserName", auditUserName)); } Long clientId = queryInfo.getClientId(); if (clientId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("clientId", clientId)); } Long batchId = queryInfo.getBatchId(); if (batchId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("batchId", batchId)); } String caseCode = queryService.trimWildcardQuery(queryInfo.getCaseCode()); if (caseCode != null) { //wildcardQuery模糊查询,相当于mysql中的like boolQueryBuilder.filter(QueryBuilders.wildcardQuery("caseCode", String.format("*%s*", caseCode))); } String borrowerName = queryService.trimWildcardQuery(queryInfo.getBorrowerName()); if (!StringUtils.isEmpty(borrowerName)) { //wildcardQuery模糊查询,相当于mysql中的like boolQueryBuilder.filter(QueryBuilders.wildcardQuery("borrowerName", String.format("*%s*", borrowerName))); } String contactName = queryService.trimWildcardQuery(queryInfo.getContactName()); if (!StringUtils.isEmpty(contactName)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("contactName", String.format("*%s*", contactName))); } String contactTel = queryService.trimWildcardQuery(queryInfo.getContactTel()); if (!StringUtils.isEmpty(contactTel)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("contactTel", String.format("*%s*", contactTel))); } String fileName = queryService.trimWildcardQuery(queryInfo.getFileName()); if (!StringUtils.isEmpty(fileName)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("fileName", String.format("*%s*", fileName))); } //常见需求范围查询 查询某个时间段范围内的数据 Long telTimeMin = queryInfo.getTelTimeMin(); Long telTimeMax = queryInfo.getTelTimeMax(); if ((telTimeMin != null) || (telTimeMax != null)) { //获得范围查询builder RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("telTime"); if (telTimeMin != null) { //gte 代表大于等于 rangeQueryBuilder.gte(telTimeMin); } if (telTimeMax != null) { //lt 代表小于某个值 lte代表小于等于某个值 rangeQueryBuilder.lt(telTimeMax); } boolQueryBuilder.filter(rangeQueryBuilder); } //查询某个时间段范围内的数据 Long telMillSecMin = queryInfo.getTelMillSecMin(); Long telMillSecMax = queryInfo.getTelMillSecMax(); if ((telMillSecMin != null) || telMillSecMax != null) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("telMillSec"); if (telMillSecMin != null) { rangeQueryBuilder.gte(telMillSecMin); } if (telMillSecMax != null) { rangeQueryBuilder.lt(telMillSecMax); } } //时间范围查询 Long auditTimeMin = queryInfo.getAuditTimeMin(); Long auditTimeMax = queryInfo.getAuditTimeMax(); if ((auditTimeMin != null) || (auditTimeMax != null)) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("auditTime"); if (auditTimeMin != null) { rangeQueryBuilder.gte(auditTimeMin); } if (auditTimeMax != null) { rangeQueryBuilder.lt(auditTimeMax); } boolQueryBuilder.filter(rangeQueryBuilder); } String contactRelationName = queryService.trimWildcardQuery(queryInfo.getContactRelationName()); if (!StringUtils.isEmpty(contactRelationName)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("contactRelationName", String.format("*%s*", contactRelationName))); } String telNumber = queryService.trimWildcardQuery(queryInfo.getTelNumber()); if (!StringUtils.isEmpty(telNumber)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("telNumber", String.format("*%s*", telNumber))); } Integer hasError = queryInfo.getHasError(); if (hasError == null) { boolQueryBuilder.filter(QueryBuilders.rangeQuery("hasError").gte(-1)); } else { if (hasError >= 0) { boolQueryBuilder.filter(QueryBuilders.termQuery("hasError", hasError)); } else { boolQueryBuilder.filter(QueryBuilders.rangeQuery("hasError").gte(0)); } } Integer auditHasError = queryInfo.getAuditHasError(); if (auditHasError == null) { boolQueryBuilder.filter(QueryBuilders.rangeQuery("auditHasError").gte(-1)); } else if (auditHasError >= -1) { boolQueryBuilder.filter(QueryBuilders.termQuery("auditHasError", auditHasError)); } String auditRemark = queryService.trimWildcardQuery(queryInfo.getAuditRemark()); if (!StringUtils.isEmpty(contactName)) { boolQueryBuilder.filter(QueryBuilders.wildcardQuery("auditRemark", String.format("*%s*", auditRemark))); } Long[] recheckDepartmentIds = queryInfo.getRecheckDepartmentIds(); if ((recheckDepartmentIds != null) && (recheckDepartmentIds.length > 0)) { long[] rdids = new long[recheckDepartmentIds.length]; for (int i = 0; i < rdids.length; i++) { rdids[i] = recheckDepartmentIds[i]; } boolQueryBuilder.filter(QueryBuilders.termsQuery("recheckDepartmentId", rdids)); } //根据数据记录中是否存在某个字段进行查询 Long recheckUserId = queryInfo.getRecheckUserId(); if (recheckUserId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("recheckUserId", recheckUserId)); } else { Integer allotRecheckState = queryInfo.getAllotRecheckState(); if (allotRecheckState != null) { if (allotRecheckState == 0) { //mustNot 档必须不匹配这些条件才能被包含进来,existsQuery 存在指定字段 BoolQueryBuilder recheckUserIdQueryBuilder = new BoolQueryBuilder(); //本条件是:返回不包含recheckUserId的字段的数据记录 recheckUserIdQueryBuilder.mustNot(QueryBuilders.existsQuery("recheckUserId")); boolQueryBuilder.filter(recheckUserIdQueryBuilder); } else { //本条件是:返回包含recheckUserId 这个字段的数据记录 boolQueryBuilder.filter(QueryBuilders.existsQuery("recheckUserId")); } } Integer recheckHasError = queryInfo.getRecheckHasError(); if (recheckHasError != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("recheckHasError", recheckHasError)); } else { Integer hasRecheck = queryInfo.getHasRecheck(); if (hasRecheck != null) { if (hasRecheck == -1) { boolQueryBuilder.filter(QueryBuilders.existsQuery("recheckUserId")); } else if (hasRecheck == 0) { //和上面不同的写法 boolQueryBuilder.mustNot(QueryBuilders.existsQuery("recheckHasError")); } else { boolQueryBuilder.filter(QueryBuilders.existsQuery("recheckHasError")); } } } Long recheckTimeMin = queryInfo.getRecheckTimeMin(); Long recheckTimeMax = queryInfo.getRecheckTimeMax(); if ((recheckTimeMin != null) || (recheckTimeMax != null)) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("recheckTime"); if (recheckTimeMin != null) { rangeQueryBuilder.gte(recheckTimeMin); } if (recheckTimeMax != null) { rangeQueryBuilder.lt(recheckTimeMax); } boolQueryBuilder.filter(rangeQueryBuilder); } } return boolQueryBuilder; } }小编在项目中将代码做了详细的 注释,通过注释可以看到所有的写法。如果 本项目对您有帮助,记得给 星星。本项目后续会继续完善java对ES的操作,包括各种复杂查询和统计。
引言最近项目中对接了几个将客户,在案件传输的时候都采用SFTP + excel 或者 csv的方式传输,下面分享一下小编在项目中对sftp操作的封装的工具类。同时分享一下在实际中的应用。1、相关包依赖<dependency> <groupId>org.apache.sshd</groupId> <artifactId>sshd-sftp</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.54</version> </dependency> <!-- https://mvnrepository.com/artifact/com.trilead/trilead-ssh2 --> <dependency> <groupId>com.trilead</groupId> <artifactId>trilead-ssh2</artifactId> <version>1.0.0-build221</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3</version> </dependency>2、SFTPutil工具类package com.jack.cmbc.tools; import com.jcraft.jsch.*; import org.apache.poi.util.IOUtils; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Vector; /** * Created by zhenghao on 2018/9/18. */ public class SFTPUtil { private ChannelSftp sftp; private Session session; /** * SFTP 登录用户名 */ private String username; /** * SFTP 登录密码 */ private String password; /** * 私钥 */ private String privateKey; /** * SFTP 服务器地址IP地址 */ private String host; /** * SFTP 端口 */ private int port; /** * 构造基于密码认证的sftp对象 */ public SFTPUtil(String username, String password, String host, int port) { this.username = username; this.password = password; this.host = host; this.port = port; } /** * 构造基于秘钥认证的sftp对象 */ public SFTPUtil(String username, String host, int port, String privateKey) { this.username = username; this.host = host; this.port = port; this.privateKey = privateKey; } public SFTPUtil() { } /** * 连接sftp服务器 */ public void login() { try { JSch jsch = new JSch(); if (privateKey != null) { jsch.addIdentity(privateKey);// 设置私钥 } session = jsch.getSession(username, host, port); if (password != null) { session.setPassword(password); } Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.connect(); Channel channel = session.openChannel("sftp"); channel.connect(); sftp = (ChannelSftp) channel; } catch (JSchException e) { e.printStackTrace(); } } /** * 关闭连接 server */ public void logout() { if (sftp != null) { if (sftp.isConnected()) { sftp.disconnect(); } } if (session != null) { if (session.isConnected()) { session.disconnect(); } } } /** * 将输入流的数据上传到sftp作为文件。文件完整路径=basePath+directory * * @param directory 上传到该目录 * @param sftpFileName sftp端文件名 */ public boolean upload(String directory, String sftpFileName, InputStream input) throws SftpException { try { if (directory != null && !"".equals(directory)) { sftp.cd(directory); } sftp.put(input, sftpFileName); //上传文件 return true; } catch (SftpException e) { return false; } } public void cd(String directory) throws SftpException { if (directory != null && !"".equals(directory) && !"/".equals(directory)) { sftp.cd(directory); } } /** * 下载文件。 * * @param directory 下载目录 * @param downloadFile 下载的文件 * @param saveFile 存在本地的路径 */ public void download(String directory, String downloadFile, String saveFile) { System.out.println("download:" + directory + " downloadFile:" + downloadFile + " saveFile:" + saveFile); File file = null; try { if (directory != null && !"".equals(directory)) { sftp.cd(directory); } file = new File(saveFile); sftp.get(downloadFile, new FileOutputStream(file)); } catch (SftpException e) { e.printStackTrace(); if (file != null) { file.delete(); } } catch (FileNotFoundException e) { e.printStackTrace(); if (file != null) { file.delete(); } } } /** * 下载文件 * * @param directory 下载目录 * @param downloadFile 下载的文件名 * @return 字节数组 */ public byte[] download(String directory, String downloadFile) throws SftpException, IOException { if (directory != null && !"".equals(directory)) { sftp.cd(directory); } InputStream is = sftp.get(downloadFile); byte[] fileData = IOUtils.toByteArray(is); return fileData; } /** * 删除文件 * * @param directory 要删除文件所在目录 * @param deleteFile 要删除的文件 */ public void delete(String directory, String deleteFile) throws SftpException { if (directory != null && !"".equals(directory)) { sftp.cd(directory); } sftp.rm(deleteFile); } /** * 列出目录下的文件 * * @param directory 要列出的目录 */ public Vector<?> listFiles(String directory) throws SftpException { return sftp.ls(directory); } public boolean isExistsFile(String directory, String fileName) { List<String> findFilelist = new ArrayList(); ChannelSftp.LsEntrySelector selector = new ChannelSftp.LsEntrySelector() { @Override public int select(ChannelSftp.LsEntry lsEntry) { if (lsEntry.getFilename().equals(fileName)) { findFilelist.add(fileName); } return 0; } }; try { sftp.ls(directory, selector); } catch (SftpException e) { e.printStackTrace(); } if (findFilelist.size() > 0) { return true; } else { return false; } } //上传文件测试 public static void main(String[] args) throws SftpException, IOException { SFTPUtil sftp = new SFTPUtil("xxx", "xxx", "xx.com.cn", 22); sftp.login(); File file = new File("/Volumes/work/new1.xlsx"); InputStream is = new FileInputStream(file); //sftp.upload("/test", "", "test_sftp.jpg", is); sftp.logout(); } }3、下载文件使用package com.jack.xxxx.service; import com.jack.xxxx.tools.FileUtils; import com.jack.xxxx.tools.GPGUtil; import com.jack.xxxx.tools.SFTPUtil; import com.jack.common.exception.TpErrorCodeGeneral; import com.jack.common.model.BaseResult; import com.jack.common.utils.DateUtils; import com.jack.common.utils.service.ErrorLogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @Description: * @author: zhenghao * @date: 2019/6/12 10:04 */ @Service public class XiaoMiFtpService { @Resource ErrorLogService errorLogService; @Autowired private XiaoMiNotifyService notifyService; @Value("${xiaomi_ftp_host}") private String xiaomiFtpHost; @Value("${xiaomi_ftp_port}") private String xiaomiFtpPort; @Value("${xiaomi_ftp_user}") private String xiaomiFtpUser; @Value("${xiaomi_prikey_file}") private String xiaomiPriKeyFile; @Value("${xiaomi_ftp_local_file_path}") private String XIAOMI_LOCAL_FilPath; private static final String SFTPDirectory = "/home"; private static String todayDownLoadFile = null; private static String todayCsvFilePath = null; private static String todayGpgFilePath = null; private boolean firstExistsFileFlag = false; /** * 为了保证服务器上文件上传结束,做一次文件保护 */ public void sync( int times) { String localFilePath = this.initLocalFilePath(); //zhiqing6.12-1.xlsx ==zhiqing月份.几号-当天第几次.xlsx String gpgFileName = "zhiqing" + DateUtils.getCurrentMonth() + "." + DateUtils.getCurrentDay() + "-" + times +".xlsx"; //判断今日是否下载成功了文件 if (gpgFileName.equals(todayDownLoadFile)) { System.out.println(DateUtils.getStrDate()+ " 今日文件已经下载:" + todayDownLoadFile); return; } //判断本地是否存在文件 if (isLocalFileExists(localFilePath + "/" + gpgFileName)) { System.out.println(DateUtils.getStrDate()+" 本地文件已经存在:" + gpgFileName); return; } //初始化服务器链接 SFTPUtil sftpUtil = this.initFtp(); if (sftpUtil == null) { writeErrorMsg("initFtp failed"); return; } //判断服务器上 if (!sftpUtil.isExistsFile(SFTPDirectory, gpgFileName)) { System.out.println(DateUtils.getStrDate()+" 服务器上不存在文件:" + gpgFileName); closeFtp(sftpUtil); return; } if (!firstExistsFileFlag) { System.out.println(DateUtils.getStrDate()+" 第一次发现文件存在:" + gpgFileName); firstExistsFileFlag = true; closeFtp(sftpUtil); return; } //下载文件 String downloadFile = downloadFtpFiles(sftpUtil, localFilePath, gpgFileName); System.out.println(DateUtils.getStrDate()+" 下载文件:" + downloadFile); if (downloadFile != null) { todayDownLoadFile = gpgFileName; todayGpgFilePath = localFilePath + "/" + gpgFileName; todayCsvFilePath = downloadFile; writeErrorMsg("今日文件下载完成:" + todayDownLoadFile); firstExistsFileFlag = false; //通知业务系统进行文件处理 参数为文件全路径 notifyService.notifyFilesChanged(downloadFile); } closeFtp(sftpUtil); } private String downloadFtpFiles(SFTPUtil sftpUtil, String localFilePath, String fileName) { String downloadFileName = null; try { sftpUtil.cd(SFTPDirectory); sftpUtil.download(SFTPDirectory, fileName, localFilePath + "/" + fileName); if (fileName.endsWith(".xlsx")) { downloadFileName = localFilePath + "/" + fileName; } if (downloadFileName != null) { File file = new File(downloadFileName); if (!file.exists()) { System.out.println("本地不存在" + downloadFileName); downloadFileName = null; } } } catch (Exception e) { e.printStackTrace(); } return downloadFileName; } private SFTPUtil initFtp() { //基于秘钥链接sftp SFTPUtil sftp = new SFTPUtil(xiaomiFtpUser, xiaomiFtpHost, Integer.valueOf(xiaomiFtpPort),xiaomiPriKeyFile); sftp.login(); System.out.println("login successed"); return sftp; } private void closeFtp(SFTPUtil sftp) { try { sftp.logout(); } catch (Exception e) { e.printStackTrace(); } } private boolean isLocalFileExists(String filePathName) { File file = new File(filePathName); if (file.exists()) { return true; } else { return false; } } private String initLocalFilePath() { String filePath = XIAOMI_LOCAL_FilPath; File file = new File(filePath); file.mkdirs(); return filePath; } private void writeErrorMsg(String msg) { System.out.println(DateUtils.date2FullStr(new Date()) + "=====" + msg); errorLogService.writeErrorLog("", msg); } }4、定时任务调用package com.jack.xxxx.task; import com.jack.xxxx.service.xxxxFtpService; import com.jack.xxxx.service.XiaoMiFtpService; import com.jack.common.utils.DateUtils; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @Description: * @author: zhenghao * @date: 2019/6/12 16:30 */ @Component public class SynXiaoMiFtpTask { @Resource XiaoMiFtpService xiaoMiFtpService; /** * @Description: 第一次 导入 * @author: zhenghao * @date: 2019/6/12 16:33 */ @Scheduled(cron = "0 0/2 09 * * ? ") public void firsRrunTask() { System.out.println(DateUtils.getStrDate() + " 小米第一次runTask"); try { xiaoMiFtpService.sync(1); } catch (Exception e) { e.printStackTrace(); } } }5、配置文件#sftp地址 xiaomi_ftp_host=xxx.com xiaomi_ftp_user=xxx xiaomi_ftp_port=2222 xiaomi_ftp_local_file_path=/xmdata #秘 xiaomi_prikey_file =/root/xx xiaomi_notify_host_url = http://xx.xx.xx.xx.:8096/xx/xx/xx6、小结以上最近使用sftp传送数据的总结,希望对读者有些帮助!
引言最近有一个统计的需求,一个案件在崔记中有多条记录,我们需要根据崔记表中的最新一条记录来判断此时案件的状态,并且需要按天统计,也就说今天的最新案件状态,不能覆盖掉昨天的案件的最新状态。刚开始我用了mysql 中的一个求差集ide函数,sql如下:SELECT COUNT(1) FROM robot_collection_record AS b WHERE NOT EXISTS ( SELECT 1 FROM robot_collection_record WHERE case_id = b.case_id AND b.create_time <![CDATA[ < ]]>create_time )这条SQL语句实现查询结果的去重并且通过字段排序,其中case_id是去重的字段,create_time是排序的字段。但是当我在查看结果的时候发现,只有统计今天的数量是正确的,但是如果加上一些条件,统计历史数据就会出现问题。最后自己动手直接写了一个sql,来满足上面需求:SELECT COUNT(1) FROM robot_collection_record a WHERE a.create_time IN ( SELECT m.createTime FROM ( SELECT DISTINCT (q.case_id), max(q.create_time) createTime FROM ( SELECT * FROM robot_collection_record WHERE partner_id = ${partnerId} AND ${startTime} <![CDATA[ <= ]]> create_time AND create_time <![CDATA[ <= ]]> ${endTime} ) q GROUP BY q.case_id ) m ) AND a.robot_answer_state = 1;上面的sql就可以满足了。小结其实这个需求还是比较常见,但是这需要我们根据自己的表设计和一些业务需求,来转换成我们的sql,所以sql功底在一些图标统计功能上还是比较明显,如果我们能灵活的运用一些mysql内置的函数,写起sql还是非常精简的,小编的sql功底很是不好呀,还需要在这上面下工夫。
经过247天的持续研发,阿里巴巴于10月14日在杭州云栖大会上,正式发布众所期待的《阿里巴巴Java开发规约》扫描插件!该插件由阿里巴巴P3C项目组研发。P3C是世界知名的反潜机,专门对付水下潜水艇,寓意是扫描出所有潜在的代码隐患。这个项目组是阿里巴巴开发爱好者自发组织形成的虚拟项目组,把《阿里巴巴Java开发规约》强制条目转化成自动化插件,并实现部分的自动编程。 阿里技术公众号于今年的2月9日首次公布《阿里巴巴Java开发规约》,瞬间引起全民代码规范的热潮,上月底又发布了PDF的终极版,大家踊跃留言,期待配套的静态扫描工具开放出来。为了让开发者更加方便、快速将规范推动并实行起来,阿里巴巴基于手册内容,研发了一套自动化的IDE检测插件(IDEA、Eclipse)。该插件在扫描代码后,将不符合规约的代码按Blocker/Critical/Major三个等级显示在下方,甚至在IDEA上,我们还基于Inspection机制提供了实时检测功能,编写代码的同时也能快速发现问题所在。对于历史代码,部分规则实现了批量一键修复的功能,如此爽心悦目的功能是不是很值得拥有?提升代码质量,提高团队研发效能,插件将会一路同行。 插件下载地址:https://github.com/alibaba/p3c好了废话不多说了,如何安装IDEA插件?你只需打开 Settings >> Plugins >> Browse repositories 输入 Alibaba 搜索,看到对应插件后即可安装。如果是mac 则Preferences >> Plugins >> Browse repositories 输入 Alibaba 搜索,看到对应插件后即可安装。
引言最近在使用局域网内的映射盘来共享文件,这样比通过外网来上传和下载文件的速度快很多,但是对于安全问题我们不得不去考虑权限问题,就是我们需要分配不同的用户名和密码来区分权限,有的用户名只能读,有的可以读写。但是小编最近在使用的时候遇到了一个问题,原来我的是记录的只读的用户名,但是现在我需要登录另外一个用户名来上传文件,这时候遇到了这么一个问题。问题重现顺便给大家说一下我们应该怎样更换我们登录映射盘用户名和密码的:如下图:1、点击ip位置回到此目录下2、右击资料共享,点击映射网络驱动器3、选择使用其他凭据连接4、选择使用其他账户登录,然后输入新的用户名和密码就可以了当我们输入新的密码以后就出现了上面的错误。这是因为:被映射的网络共享文件夹所在的机器给不同的共享文件夹设置了不同的用户访问权限,而目前连接的机器与被映射的机器已经用另一个用户建立了连接,从而导致了此错误。解决方法:首先在我dos窗口输入命令:net use 即可看到当前已建立的连接:然后我们输入命令:net use * /delete 来结束所有的当前所有的连接。然后再输入命令: Y 来结束所有连接;然后在按照更换登录映射盘密码的方法来重新登录就完美解决了。小结当时自己遇到这个问题的时候没有想怎样去解决这个问题,而是想直接放开所有的权限。这样我们就不论用那个账号登录我们都可以直接上传下载了。但是这样会给我们带来很多的问题。可能造成资料误删或者资料冗余等问题,所以说我们不论是在敲代码还是平常学习的时候遇到了问题我们应该多查一些资料来解决。这样不仅解决我们的问题而且让我们收获更多!!!
引言我们接触最多的、用的最多的搜索引擎就是百度了,可以说现在我们的生活如果离开像百度、谷歌这样的搜索引擎还是我们的生活没有了方向,我们在使用的时候都有一个体验就是我我们在输入关键紫的时候,他会自动给我们匹配很多的关键字,像下图所示:这种非常人性化的功能对于我们在平常开发软件的时候是非常有必要借鉴的,所以在小编最近的项目中就需要用到这个功能给用户提供方便,于是小编就对这个方面的内容进行了研究,必备的知识可能是JQuery+Ajax,因为这需要频繁的刷新网页,因为我们在输入框中内容发生改变的时候,下拉框中的内容是需要改变的,下面小编就给大家分享一下小编的实现代码和思路。 首先给大家说一下后台的思路,既然是自动匹配所以我们用到模糊查询的功能: #region 模糊查询 提供自动匹配违规公司 public ActionResult QueryIllegalCompany() { //获得搜索框中输入的内容 string CompanyName = Request["strLike"]; //调用后台的模糊查询方法查询匹配的数据,返回值为list List<BlackListViewModel> IllegalCompany = iBlackListManagerWCF.QueryIllegalCompany(CompanyName); string[] results = { "CompanyName" }; //将需要的字段拼接成字符串 string s = FuzzyQuery.GetLikeStr<BlackListViewModel>(IllegalCompany, results).ToString(); return Content(s); } #endregion下面来看一下前台页面的代码:<pre name="code" class="html"> <tr margin-left:40px> <td style="margin-left:40px;font-size:14px">公司名称:</td> <td style="width:300px"> <input type="text" value="请输入公司名称" id="companyName" οnblur="QueryCompanyInfo(this)" οnchange="QueryIllegalCompany()" οnfοcus=" if (this.value == this.defaultValue) this.value = ''" class="easyui-validatebox" required="true" missingmessage="公司名称必须填写" size="10" type="text" /> </td> </tr> @*存放后台链接地址的隐藏input*@ <input id="urllink" type="hidden" value="/BidTRecordManager/QueryIllegalCompany" /> @*添加存放模糊查询结果的自动提示框*@ <div id="auto" style="z-index:4; position: absolute; display: none; border: 1px solid #95b8e7; background-color:#fff"></div>下面分享一下最为关键的js代码://跨浏览器现代事件绑定queryCondition function addEvent(obj, type, fn) { if (obj.addEventListener) {//火狐浏览器 obj.addEventListener(type, fn, false); } else if (obj.attachEvent) {//ie浏览器 obj.attachEvent("on" + type, fn); } } addEvent(window, "load", function () { var input = document.getElementById("companyName"); addEvent(input, "keyup", function (event, input, urllink) { var xmlHttp; var params; var strLike; var input = document.getElementById("companyName"); var keycode = AutoComplete.prototype.isIE() ? window.event.keyCode : event.which;//e.which代表火狐中的键码 if (typeof XMLHttpRequest != 'undefined') {//火狐浏览器 xmlHttp = new XMLHttpRequest(); } else if (typeof ActiveXObject != 'undefined') { //IE浏览器 var version = [ 'MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ]; for (var i = 0; i < versions.length; i++) { try { xmlHttp = new ActiveXObject(version[i]); } catch (e) { //跳过 } } } else { throw new Error('您的浏览器不支持XHR对象!'); } //定义要访问的url和提交方式 strLike = document.getElementById("companyName").value; //var url = "queryTestHandler.ashx?strLike=" + strLike + new Date().getTime(); var urllink = document.getElementById("urllink").value; var url = urllink + "?strLike=" + strLike + "&t=" + new Date().getTime(); xmlHttp.open("get", url, true);//get方式提交 xmlHttp.setRequestHeader("Content-Type", "text/xml");//设置请求头信息 xmlHttp.send(null); xmlHttp.onreadystatechange = callback; function callback() { if (xmlHttp.readyState == 4) { //表示和服务器端的交互已经完成 if (xmlHttp.status == 200) { //表示和服务器的响应代码是200,正确的返回了数据 //纯文本数据的接受方法 params = xmlHttp.responseText; if ((keycode >= 37 && keycode <= 40) || keycode == 13) { if (autoComplete != null && (keycode == 38 || keycode == 40 || keycode == 13)) { autoComplete.setStyle(keycode); } } else { autoComplete = new AutoComplete(params, input); autoComplete.show(); } } } }//callback End }) }) //出现两个模糊查询输入框要调用的方法 setTimeout(function AutoSuggest(input, event, urllink) { var xmlHttp; var params; var strLike; //alert(); //alert(input); //alert(urllink); //alert(input.value); var keycode = AutoComplete.prototype.isIE() ? window.event.keyCode : event.which;//e.which代表火狐中的键码 //alert(); if (typeof XMLHttpRequest != 'undefined') {//火狐浏览器 xmlHttp = new XMLHttpRequest(); } else if (typeof ActiveXObject != 'undefined') { //IE浏览器 var version = [ 'MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ]; for (var i = 0; i < versions.length; i++) { try { xmlHttp = new ActiveXObject(version[i]); } catch (e) { //跳过 } } } else { throw new Error('您的浏览器不支持XHR对象!'); } //定义要访问的url和提交方式 //strLike = document.getElementById("queryCondition").value; //var url = "queryTestHandler.ashx?strLike=" + strLike + new Date().getTime(); // var urllink = document.getElementById("urllink").value; strLike = input.value; //alert(strLike); var url = urllink + "?strLike=" + strLike + "&t=" + new Date().getTime(); xmlHttp.open("get", url, true);//get方式提交 xmlHttp.setRequestHeader("Content-Type", "text/xml");//设置请求头信息 xmlHttp.send(null); xmlHttp.onreadystatechange = callback; function callback() { if (xmlHttp.readyState == 4) { //表示和服务器端的交互已经完成 if (xmlHttp.status == 200) { //表示和服务器的响应代码是200,正确的返回了数据 //纯文本数据的接受方法 params = xmlHttp.responseText; if ((keycode >= 37 && keycode <= 40) || keycode == 13) { if (autoComplete != null && (keycode == 38 || keycode == 40 || keycode == 13)) { autoComplete.setStyle(keycode); } } else { autoComplete = new AutoComplete(params, input); autoComplete.show(); } } } }//callback End }, 100); //AutoComplete Object var autoComplete = null; //AutoComplete constructor function AutoComplete(params, input){ this.params = params//接收的参数,这里的参数为接收的所有需要查询的数据源 this.input = input;//Input输入的值 this.messages = new Array();//将所有需要查询的数据分成一个数组的形式,messages代表这个数组 this.message = input.value;//单数形式代表当前输入的值 this.inputValue = "";//原始输入值默认为空 this.size = 0;//下拉提示框条数为空,默认 this.index = 0;//当前选中的内容索引 this.likemsgs = new Array();//下拉框中要显示的数据,相似数据,作为一个数组来出现 //this.likemsgs = this.unique(this.likemsgs); this.div = this.$$$('auto');//通过$$$代表getElementById,自动提示框的节点 this.divInnerHTML = this.div.innerHTML;//自动提示框的内容 this.input.onblur = function(){ autoComplete.lostFocus(); } } //keyup事件需要调用的方法,params为需要查询的所有数据源,input为输入的值,e为keyup事件 //将字符串转换成数组的方法 AutoComplete.prototype.strToArray = function(){ this.messages = this.params.split(","); } //展现自动提示框 AutoComplete.prototype.show = function(){ if (this.message != '') { this.inputValue = this.input.value; this.strToArray(); this.emptyDiv(); this.getLikeMegs(); this.setDivAttr(); this.addMessageToDiv(); } else { this.emptyDiv(); } } //Style set of information AutoComplete.prototype.setStyle = function(keycode){ if (this.size > 0) { if (keycode == 38) {//Up上键 this.setStyleUp(); } else if (keycode == 40) { //Down下键 this.setStyleDown(); } else if (keycode == 13) {//Enter this.textToInput(this.$$$(this.index).innerText); } } } //按上键时,设置鼠标上移事件,及相应的格式 AutoComplete.prototype.setStyleUp = function(){ if (this.index == 0) {//如果当前选中内容索引为0,默认没有匹配项的情况 this.index = this.size;//如果索引值与下拉框中内容条数相同 this.$$$(this.index).style.backgroundColor = '#E2EAFF';//出现粉色背景 this.input.value = this.$$$(this.index).innerText; } else if (this.index == 1) { this.$$$(this.index).style.backgroundColor = '#FFFFFF';//背景为白色 this.input.value = this.inputValue; this.index = 0; this.input.focus(); } else { this.index--; this.setOtherStyle(); this.$$$(this.index).style.backgroundColor = '#E2EAFF'; this.input.value = this.$$$(this.index).innerText; } } //点击down时,触发事件,设置提示框中内容的样式 AutoComplete.prototype.setStyleDown = function(){ if (this.index == this.size) { this.$$$(this.index).style.backgroundColor = '#FFFFFF'; this.input.value = this.inputValue; this.index = 0; this.input.focus(); } else { this.index++;//否则的话,当前索引继续加 this.setOtherStyle(); this.$$$(this.index).style.backgroundColor = '#E2EAFF'; this.input.value = this.$$$(this.index).innerText; } } //设置没有被选中的内容背景色为白色 AutoComplete.prototype.setOtherStyle = function(){ for (var i = 1; i <= this.size; i++) { if (this.index != i) { this.$$$(i).style.backgroundColor = '#FFFFFF'; } } } //当浏览器改变大小时,设置提示框的样式 window.onresize = function(){ if (autoComplete != null) { autoComplete.setDivAttr(); } } //当鼠标点击浏览器的其他地方时,页面的响应事件,要求如果输入那么,文本框中的值,为当前输入的值 -- //firefox window.onclick = function(e){ if (AutoComplete.prototype.$$$('auto').style.display != 'none') { var x = e.clientX, y = e.clientY; var left = autoComplete.calcOffsetLeft(autoComplete.input); var right = autoComplete.calcOffsetLeft(autoComplete.input) + autoComplete.input.offsetWidth; var top = autoComplete.calcOffsetTop(autoComplete.input); var bottom = autoComplete.input.offsetHeight + autoComplete.calcOffsetTop(autoComplete.input) + autoComplete.div.offsetHeight; if (x < left || x > right || y < top || y > bottom) { autoComplete.emptyDiv();// 如果鼠标点击的地方为浏览器的可见区,那么清空提示框 } } } //设置鼠标移入事件 AutoComplete.prototype.mouseover = function(li){ li.style.backgroundColor = '#E2EAFF';//背景色 this.index = li.id;//鼠标移入时,当前索引为菜单项中所选的内容的索引 this.setOtherStyle(); } //设置提示框的样式,包括宽和高等基本属性 AutoComplete.prototype.setDivAttr = function(){ if (this.input != null) { this.div.style.width = this.input.offsetWidth + 'px';//宽度和输入框的宽度相同 this.div.style.left = this.calcOffsetLeft(this.input) + 'px';//与输入框左边距离相同 this.div.style.top = (this.input.offsetHeight + this.calcOffsetTop(this.input)) + 'px';//设置高度为输入框距离顶端的距离+输入框的高度 this.div.style.display = 'block';//作为一个单独的块儿来显示 } } //消除重复记录 AutoComplete.prototype.unique = function (someArray) { //alert(someArray); tempArray = someArray.slice(0);//复制数组到临时数组 for (var i = 0; i < tempArray.length; i++) { for (var j = i + 1; j < tempArray.length;) { //这里需要用正则表达式替换掉所有两边的空格 if (tempArray[j].replace(/(^\s+)|(\s+$)/g, "") == tempArray[i].replace(/(^\s+)|(\s+$)/g, "")) //后面的元素若和待比较的相同,则删除并计数; //删除后,后面的元素会自动提前,所以指针j不移动 { tempArray.splice(j, 1); } else { j++; } //不同,则指针移动 } } return tempArray; } AutoComplete.prototype.getLikeMegs = function () { var j = 0; for (var i = 0; i < this.messages.length; i++) { if ((this.messages[i].length >= this.message.length)) { for (var k = 0; k < this.messages[i].length; k++) { if (this.messages[i].substring(k, k+this.message.length) == this.message ) { //消除重复记录 this.likemsgs[j++] = this.messages[i]; } } } } this.likemsgs = this.unique(this.likemsgs); } //把查询到的可能匹配的结果添加进提示框中 AutoComplete.prototype.addMessageToDiv = function(){ var complete = this; for (var i = 0; i < this.likemsgs.length; i++) { var li = document.createElement('li');//创建一个菜单节点 li.id = i + 1; li.style.fontSize = '12px'; li.style.listStyleType = 'none'; li.style.listStylePosition = 'outside';//不占用Li宽度 li.onmouseover = function(){//设置鼠标上移事件 complete.mouseover(this); } li.onmouseout = function(){//设置鼠标移除事件 this.style.backgroundColor = '#FFFFFF';//背景为白色 } li.onclick = function(){//鼠标点击时,文本框内容为点击内容 complete.textToInput(this.innerText); } li.appendChild(document.createTextNode(this.likemsgs[i]));//创建一个此内容的文本节点,并添加进li菜单项中 this.div.appendChild(li);//将菜单项添加进提示框中 this.divInnerHTML = this.div.innerHTML; this.size++; } if (this.size == 0) {//如果没有匹配项,size=0为空结果 this.div.innerHTML = "<li style=\"list-style: none outside;font-size:12px;color:red;\">未找到相匹配的结果!</li>"; this.divInnerHTML = this.div.innerHTML; } } //设置鼠标点击时,文本框中的内容跟点击时相同,同事清空提示框中的内容 AutoComplete.prototype.textToInput = function(value){ this.input.value = value; this.emptyDiv(); } //清空提示框中的内容 AutoComplete.prototype.emptyDiv = function(){ this.divInnerHTML = ''; this.div.innerHTML = this.divInnerHTML; this.div.style.display = 'none'; this.size = 0; this.index = 0; } //计算物体左边到提示框左边之间的距离 AutoComplete.prototype.calcOffsetLeft = function(field){ return this.calcOffset(field, 'offsetLeft'); } //设置物体从顶端到当前提示框之间的距离 AutoComplete.prototype.calcOffsetTop = function(field){ return this.calcOffset(field, 'offsetTop'); } //设置物体到浏览器左边的距离,这里主要用来计算输入框到浏览器左边的距离 AutoComplete.prototype.calcOffset = function(field, attr){ var offset = 0; while (field) { offset += field[attr]; field = field.offsetParent; } return offset; } //当输入框失去焦点时IE浏览器,清空提示框 AutoComplete.prototype.lostFocus = function(){ var active = document.activeElement; if (AutoComplete.prototype.isIE() && active != null && active.id != 'auto') { this.emptyDiv() } } //Use $$$ replace getElementById AutoComplete.prototype.$$$ = function(obj){ return document.getElementById(obj); } //判断当前浏览器为IE浏览器 AutoComplete.prototype.isIE = function(){ return window.navigator.userAgent.indexOf('MSIE') != -1; } //Firefox innerText define if (!AutoComplete.prototype.isIE()) { HTMLElement.prototype.__defineGetter__("innerText", function(){ var anyString = ""; var childS = this.childNodes; for (var i = 0; i < childS.length; i++) { if (childS[i].nodeType == 1) //元素节点 anyString += childS[i].innerText; else if (childS[i].nodeType == 3) //文本节点 anyString += childS[i].nodeValue; } return anyString; }); HTMLElement.prototype.__defineSetter__("innerText", function(sText){ this.textContent = sText; }); }在使用上面这个js的时候只需要将我们的搜索框的id替成我们自己的id就可以了,这就可以了实现百度搜索框的功能了,这个js的来源是Jquery封装的一个插件AutoCoplete(点我下载),上面的js从布局样式、浏览器兼容等方面对这个插件进行了扩展,是这个功能实现的非常的人性化。希望能对需要的读者给出非常大的帮助。小结在开始的时候感觉这个功能是比较好实现的,但是在实现的过程中遇到了很多的坎坷,自己分析这个原因是自己对js真的是太菜了,一点都不熟悉。另外也在感叹js的强大的,什么功能js都可以实现。但是需要我们对js有很深的功底。其次是对Jquery封装的插件不能很好的扩展,因为不论是API文档还是别人写的js我们都需要读懂并且能很好的修改,这样我们才能真正的叫做站在巨人的肩膀上。对目前的自己来说js还非常的嫩,几乎自己写不出js的代码,所以我们在学习的过程中需要不断的积累这个方面的代码量,这样当我们接触到新的功能的时候才能写出非常优秀的js代码,希望这篇博客能给广大读者带来帮助。
引言在机房重构还剩下上下机功能的时候突然听到这么一个消息——我们最好不要用DataTable来从D层返回,应该用泛型集合!当时没有特别在意这件事情,因为那时候的注意力都在设计模式的添加和应用上面,没有过多的考虑“泛型”这方面的知识,当我完成基本功能以后,这个想法有冒出来了,所以这次没有再次放过,而是找了一些相关资料来总结了一下,下面和大家共享一下我的总结:DataTabel的弊端首先我们来看一下我们使用DataTable时候的U层的代码: '填充文本框 txtStudentNo.Text = dt.Rows(0).Item(2) txtName.Text = dt.Rows(0).Item(3) txtSex.Text = dt.Rows(0).Item(4) txtTie.Text = dt.Rows(0).Item(5) txtGrade.Text = dt.Rows(0).Item(6) txtClass.Text = dt.Rows(0).Item(7) txtStatus.Text = dt.Rows(0).Item(11) txtExplain.Text = dt.Rows(0).Item(9) txtBalance.Text = dt.Rows(0).Item(8)这是当我们从数据库中查到结果以后显示在我们窗体上的代码,从上面的代码我们可以看出,我们必须清楚的知道数据库中字段名对应的位置,这样就和数据库的耦合性太大,这在我们编程中是特别忌讳的,另外它不符合面向对象的思想而更倾向于面向过程,所以我们仔细想想DataTable是非常不可取的一个东西,当我们数据库表中的字段非常多的时候,会给我们带来难以调试的错误。什么是泛型集合?泛型听起来很高深的一个词,但实际上它的作用很简单,就是提高程序的性能.比如在计算机中经常用到一些数据结构,如队列,链表等,而其中的元素以前一般这么定义:object a=new object();这样就带来一个严重的问题,用object来表示元素没有逻辑问题,但每次拆箱、封箱就占用了大量的计算机资源,导致程序性能低下,而这部分内容恰恰一般都是程序的核心部分,如果使用object,那么程序的表现就比较糟糕.而使用泛型则很好的解决这个问题,本质就是在编译阶段就告诉编译器,数据结构中元素的种类,既然编译器知道了元素的种类,自然就避免了拆箱、封箱的操作,从而显著提高c#程序的性能.比如List就直接使用string对象作为List的元素,而避免使用object对象带来的封箱、拆箱操作,从而提高程序性能.泛型集合和DataTable的区别在三层架构中,实体类即数据库的映射,因此实体类中的属性和数据库表中的字段是相对应的。把DataTable中的每一行记录视为一个实体类,把其中的字段读取出来,到实体类的属性中,再把所有的实体类存在泛型集合中。因此,DataTable中有多少个记录,泛型集合中就有多少个实体类,每个实体类的属性和DataTable的字段是相对应的。这样一来,传到B层或U层的将是一个实体类的泛型集合。使用泛型集合传递数据,编写B层的人员无需手动填写需要的字段,直接按一下点,全都提示出来了,想用哪个用哪个,不会出现写错的情况;你不必了解数据库结构;符合面向对象思想。泛型集合的使用下面以查询学生余额功能来展示泛型集合的使用,因为我们在机房收费系统的时候有多个查询功能,而我们通过Sqlhelper类返回的都是DataTable,所以我们需要多次进行泛型集合的转换,为了体现代码的复用,我们将这个转换的方法抽象出单独的一个类放在D层,以供调用。'********************************************** '说明:将DataTable转化为泛型集合 '命名空间:DAL '机器名称:晓 '创建日期:2015/2/23 11:13:28 '作者:郑浩 '版本号:V1.00 '********************************************** Imports System.Collections.Generic '增加泛型的命名空间 Imports System.Reflection '引入反射:为了使用PropertyInfo Public Class ConvertGenericsHelper '将datatable转化为泛型集合 Public Shared Function convertToGenerics(Of T As {New})(ByVal dt As DataTable) As IList(Of T) '注意: 这里的new是用来约束T的,必须有,不然new T的时候会出现错误 Dim myGenericsAs New List(Of T) '定义最终返回的集合 Dim myTpye As Type = GetType(T) '得到实体类的类型名 Dim dr As DataRow '定义行集 Dim tempName As String = String.Empty '定义一个临时变量 '遍历DataTable的所有数据行 For Each dr In dt.Rows Dim myT As New T '定义一个实体类的对象 Dim propertys() As PropertyInfo = myT.GetType().GetProperties() '定义属性集合 Dim Pr As PropertyInfo '遍历该对象的所有属性 For Each Pr In propertys tempName = Pr.Name '将属性名称赋值给临时变量 '检查DataTable是否包含此列(列名==对象的属性名) If (dt.Columns.Contains(tempName)) Then '将此属性与datatable里的列明比较,查看datatable是否包含此属性 '判断此属性是否有Setter If (Pr.CanWrite = False) Then '判断此属性是否可写,如果不可写,跳出本次循环 Continue For End If Dim value As Object = dr(tempName) '定义一个对象型的变量来保存列的值 If (value.ToString <> DBNull.Value.ToString()) Then '如果非空,则赋给对象的属性 Pr.SetValue(myT, value, Nothing) '在运行期间,通过反射,动态的访问一个对象的属性 End If End If Next myGenerics.Add(myT) '添加到集合 Next Return myGenerics '返回实体集合 End Function End ClassD层代码:'********************************************** '说明:利用泛型集合来查看学生余额 '命名空间:DAL '机器名称:晓 '创建日期:2015/2/23 11:27:21 '作者:郑浩 '版本号:V1.00 '********************************************** Imports IDAL Imports System.Data.SqlClient Public Class SqlserverCheckBalanceDAL : Implements ICheckBalance Public Function ICheckBalance(studentinfo As Entity.EN_StudentInfo) As List(Of Entity.EN_StudentInfo) Implements ICheckBalance.ICheckBalance Dim sql As String sql = "select * from ZH_StudentInfo where CardNo = @CardNo" Dim paras As SqlParameter() = {New SqlParameter("CardNo", studentinfo.CardNo)} Dim dt As New DataTable '保存转换后的泛型集合 Dim myList As New List(Of Entity.EN_StudentInfo) dt = SqlHelper.SqlHelper.ExecSelect(sql, CommandType.Text, paras) '先判断dt是否为空 If dt.Rows.Count > 0 Then '将dt转换为泛型集合 myList = ConvertGenericsHelper.convertToList(Of Entity.EN_StudentInfo)(dt) Return myList Else Return Nothing End If End Function End ClassU层核心部分代码: Dim studentinfo As New Entity.EN_StudentInfo studentinfo.CardNo = Trim(txtCardNo.Text) Dim dt As New List(Of Entity.EN_StudentInfo) Dim checkbalance As New Facade.FacadeCheckBalance dt = checkbalance.CheckBalance(studentinfo) txtClass.Text = dt(0).Classes '0表示泛型集合中的第一个实体 txtStudentNo.Text = dt(0).StudentNo txtName.Text = dt(0).StudentName txtSex.Text = dt(0).Sex txtStatus.Text = dt(0).Status txtGrade.Text = dt(0).Grade txtBalance.Text = dt(0).Cash txtExplain.Text = dt(0).Explain上面是我们直接给文本框赋值,和DataGridView控件交互和DataTable的用法一样,直接将我们声明的泛型集合赋值给DataGridView的DataSourc即可。常见错误上面这中错误是我们在运用泛型的时候经常遇到的错误,原因是我们封装的实体中的属性的类型和数据库中相应字段的类型不同,例如上面错误的原因是在数据库中cash字段的类型是numeric型而我在实体中用的是string型。特别注意当我们用泛型集合来代替DataTable的时候我们必须保证我们封装的实体的属性名和数据库中字段名一模一样,也就是说我们必须明确要转换的实体类的类型,否则会报错,另外我们在封装实体的属性的类型必须和我们数据库中相应字段的属性一样。小结当我们遇到新的知识的时候不要轻易的放过,我们需要自己查询一些资料然后通过实践慢慢的来理解这新知识的好处,好好的体会为什么其他人会这么用?其实就单纯工编码的难以来说DataTable比泛型集合容易的多,但是人们选择用一个比较难的方式来实现,其中一定有很多你想不到也就是值得你探索的知识,在重构的过程中功能实现并不是很重要,重要的是我们应该从第一遍的基础上加上以前学习的知识,用一种新的方式来实现功能,在这过程中不断探索,不断收获
2022年11月