Java Web App体系结构

我曾经利用Servlet,JSP,JAX-RS,Spring框架,Play框架,带有Facelets的JSF和一些Spark框架。 以我的拙见,所有这些解决方案都远非面向对象和优雅的。 它们都充满了静态方法,无法测试的数据结构和肮脏的骇客。 因此,大约一个月前,我决定创建自己的Java Web框架。 我将一些基本原则纳入其基础:1)没有NULL,2)没有公共静态方法,3)没有可变的类以及4)没有类的转换,反射和instanceof运算符。 这四个基本原则应保证干净的代码和透明的体系结构。 这就是Takes框架的诞生方式。 让我们看看创建了什么以及它如何工作。

教父的制作(1972),弗朗西斯·福特·科波拉

教父的制作(1972),弗朗西斯·福特·科波拉

简而言之,Java Web体系结构

简单来说,这就是我理解Web应用程序体系结构及其组件的方式。

首先,要创建Web服务器,我们应该创建一个新的网络套接字 ,该套接字在某个TCP端口上接受连接。 通常是80,但是我将使用8080进行测试。 这是通过Java使用ServerSocket类完成的:

import java.net.ServerSocket;
public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true);}
}

这足以启动Web服务器。 现在,套接字已准备就绪并且正在侦听端口8080。当有人在其浏览器中打开http://localhost:8080时,将建立连接,浏览器将永远旋转其等待轮。 编译此代码段,然后尝试。 我们只是构建了一个简单的Web服务器,而没有使用任何框架。 我们尚未对传入的连接做任何事情,但是我们也不拒绝它们。 所有这些都在该server对象内对齐。 它是在后台线程中完成的。 这就是为什么我们需要将while(true)放在后面。 没有这种无休止的暂停,该应用程序将立即完成其执行,并且服务器套接字将关闭。

下一步是接受传入的连接。 在Java中,这是通过对accept()方法的阻塞调用来完成的:

final Socket socket = server.accept();

该方法正在阻塞其线程,并等待新的连接到达。 一旦发生这种情况,它将返回Socket的实例。 为了接受下一个连接,我们应该再次调用accept() 。 因此,基本上,我们的Web服务器应该像这样工作:

public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true) {final Socket socket = server.accept();// 1. Read HTTP request from the socket// 2. Prepare an HTTP response// 3. Send HTTP response to the socket// 4. Close the socket}}
}

这是一个无休止的循环,接受一个新的连接,理解它,创建一个响应,返回响应,然后再次接受一个新的连接。 HTTP协议是无状态的,这意味着服务器不应记住任何先前连接中发生的情况。 它关心的只是此特定连接中的传入HTTP请求。

HTTP请求来自套接字的输入流,看起来像是多行文本块。 如果读取套接字的输入流,将看到以下内容:

final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())
);
while (true) {final String line = reader.readLine();if (line.isEmpty()) {break;}System.out.println(line);
}

您将看到如下内容:

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4

客户端(例如Google Chrome浏览器)将此文本传递到已建立的连接中。 它连接到localhost端口8080,一旦连接就绪,它将立即将文本发送到其中,然后等待响应。

我们的工作是使用在请求中获得的信息来创建HTTP响应。 如果我们的服务器非常原始,那么我们基本上可以忽略请求中的所有信息,而只需返回“ Hello,world!”。 到所有请求(为简单起见,我使用IOUtils ):

import java.net.Socket;
import java.net.ServerSocket;
import org.apache.commons.io.IOUtils;
public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true) {try (final Socket socket = server.accept()) {IOUtils.copy(IOUtils.toInputStream("HTTP/1.1 200 OK\r\n\r\nHello, world!"),socket.getOutputStream());}}}
}

而已。 服务器已准备就绪。 尝试编译并运行它。 将浏览器指向http:// localhost:8080 ,您将看到Hello, world!

$ javac -cp commons-io.jar Foo.java
$ java -cp commons-io.jar:. Foo &
$ curl http://localhost:8080 -v
* Rebuilt URL to: http://localhost:8080/
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
Hello, world!

这就是构建Web服务器所需的全部。 现在让我们讨论如何使其面向对象和可组合。 让我们尝试看看Takes框架是如何构建的。

路由/调度

最重要的步骤是确定谁负责构建HTTP响应。 每个HTTP请求都具有1)查询,2)方法和3)许多标头。 使用这三个参数,我们需要实例化一个将为我们构建响应的对象。 在大多数Web框架中,此过程称为请求分派或路由。 这是我们在Takes中的做法:

final Take take = takes.route(request);
final Response response = take.act();

基本上有两个步骤。 第一个是创建的一个实例Taketakes ,而第二个是创建的实例Responsetake 。 为什么这样做呢? 主要是为了分开职责。 实例Takes负责调度请求和实例右Take ,和实例Take负责创建响应。

要在Takes中创建一个简单的应用程序,您应该创建两个类。 首先,执行Takes

import org.takes.Request;
import org.takes.Take;
import org.takes.Takes;
public final class TsFoo implements Takes {@Overridepublic Take route(final Request request) {return new TkFoo();}
}

我们分别为TakesTake使用这些TsTk前缀。 您应该创建的第二个类是Take的实现:

import org.takes.Take;
import org.takes.Response;
import org.takes.rs.RsText;
public final class TkFoo implements Take {@Overridepublic Response act() {return new RsText("Hello, world!");}
}

现在是时候启动服务器了:

import org.takes.http.Exit;
import org.takes.http.FtBasic;
public class Foo {public static void main(final String... args) throws Exception {new FtBasic(new TsFoo(), 8080).start(Exit.NEVER);}
}

FtBasic类执行与上述完全相同的套接字操作。 它在端口8080上启动服务器套接字,并通过我们提供给其构造函数的TsFoo实例调度所有传入的连接。 它以无休止的周期进行此分派,每秒检查一次是否应该使用Exit实例停止。 显然, Exit.NEVER始终不会回答“请别停下来”。

HTTP请求

现在,让我们看看到达TsFoo的HTTP请求中TsFoo什么以及我们可以从中获得什么。 这是在Takes中定义Request接口的方式:

public interface Request {Iterable<String> head() throws IOException;InputStream body() throws IOException;
}

该请求分为两部分:头部和身体。 根据RFC 2616中的 HTTP规范,头部包含开始于正文的空行之前的所有行。 框架中有许多有用的装饰器用于Request 。 例如, RqMethod将帮助您从标题的第一行获取方法名称:

final String method = new RqMethod(request).method();

RqHref将帮助提取查询部分并进行解析。 例如,这是请求:

GET /user?id=123 HTTP/1.1
Host: www.example.com

此代码将提取123

final int id = Integer.parseInt(new RqHref(request).href().param("id").get(0)
);

RqPrint可以将整个请求或其主体打印为String

final String body = new RqPrint(request).printBody();

这里的想法是使Request接口保持简单,并向其装饰器提供此请求解析功能。 这种方法有助于框架使类保持较小且具有凝聚力。 每个装饰器都非常小巧,坚固,只能做一件事。 所有这些装饰器都在org.takes.rq包中。 您可能已经知道, Rq前缀代表Request

第一个Real Web App

让我们创建第一个真正的Web应用程序,它将做一些有用的事情。 我建议从Entry类开始,这是Java从命令行启动应用程序所必需的:

import org.takes.http.Exit;
import org.takes.http.FtCLI;
public final class Entry {public static void main(final String... args) throws Exception {new FtCLI(new TsApp(), args).start(Exit.NEVER);}
}

此类仅包含一个main()静态方法,当应用程序从命令行启动时,JVM将调用该方法。 如您所见,它实例化FtCLI ,为它提供类TsApp和命令行参数的实例。 我们稍后将创建TsApp类。 FtCLI (转换为“带有命令行界面的前端”)创建相同FtBasic的实例,将其包装到一些有用的修饰符中,并根据命令行参数进行配置。 例如,-- --port=8080将转换为8080端口号,并作为FtBasic构造函数的第二个参数传递。

该Web应用程序本身称为TsApp并扩展了TsWrap

import org.takes.Take;
import org.takes.Takes;
import org.takes.facets.fork.FkRegex;
import org.takes.facets.fork.TsFork;
import org.takes.ts.TsWrap;
import org.takes.ts.TsClasspath;
final class TsApp extends TsWrap {TsApp() {super(TsApp.make());}private static Takes make() {return new TsFork(new FkRegex("/robots.txt", ""),new FkRegex("/css/.*", new TsClasspath()),new FkRegex("/", new TkIndex()));}
}

我们将在稍后讨论此TsFork课程。

如果您使用的是Maven,则应以pom.xml开头:

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>foo</groupId><artifactId>foo</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.takes</groupId><artifactId>takes</artifactId><version>0.9</version> <!-- check the latest in Maven Central --></dependency></dependencies><build><finalName>foo</finalName><plugins><plugin><artifactId>maven-dependency-plugin</artifactId><executions><execution><goals><goal>copy-dependencies</goal></goals><configuration><outputDirectory>${project.build.directory}/deps</outputDirectory></configuration></execution></executions></plugin></plugins></build>
</project>

运行mvn clean package应该在target目录中构建foo.jar文件,并在target/deps构建所有JAR依赖项的集合。 现在,您可以从命令行运行该应用程序:

$ mvn clean package
$ java -Dfile.encoding=UTF-8 -cp ./target/foo.jar:./target/deps/* foo.Entry --port=8080

该应用程序已准备就绪,您可以将其部署到Heroku。 只需在存储库的根目录中创建一个Procfile文件,然后将存储库推送到Heroku。 这是Procfile样子:

web: java -Dfile.encoding=UTF-8 -cp target/foo.jar:target/deps/* foo.Entry --port=${PORT}

叉车

这个TsFork类似乎是框架的核心元素之一。 它有助于路线传入的HTTP请求到右 。 它的逻辑非常简单,并且里面只有几行代码。 它封装了“ forks”的集合,它们是Fork<Take>接口的实例:

public interface Fork<T> {Iterator<T> route(Request req) throws IOException;
}

它唯一的route()方法要么返回一个空的迭代器,要么返回一个带有单个Take的迭代器。 TsFork遍历所有fork,调用它们的route()方法,直到其中一个返回take 。 一旦出现这种情况, TsFork返回此给调用者,这是FtBasic

现在让我们自己创建一个简单的fork。 例如,当请求/status URL时,我们想显示应用程序的/status 。 这是代码:

final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new Fork.AtTake() {@Overridepublic Iterator<Take> route(Request req) {final Collection<Take> takes = new ArrayList<>(1);if (new RqHref(req).href().path().equals("/status")) {takes.add(new TkStatus());}return takes.iterator();}});}
}

我相信这里的逻辑很明确。 我们要么返回一个空的迭代器,要么返回一个内部带有TkStatus实例的迭代器。 如果返回一个空的迭代器,则TsFork将尝试在集合中找到另一个实际上获取Take实例的fork,以产生Response 。 顺便说一句,如果未找到任何内容,并且所有派生都返回空的迭代器,则TsFork将引发“找不到页面”异常。

这种精确的逻辑由一个名为FkRegex即用即用的叉子FkRegex ,它尝试将请求URI路径与提供的正则表达式进行匹配:

final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new FkRegex("/status", new TkStatus()));}
}

我们可以组成TsFork类的多层结构。 例如:

final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new FkRegex("/status",new TsFork(new FkParams("f", "json", new TkStatusJSON()),new FkParams("f", "xml", new TkStatusXML()))));}
}

同样,我认为这很明显。 实例FkRegex会问的一个封装实例TsFork返回一个take,它会尝试从一个获取它FkParams封装。 如果HTTP查询为/status?f=xml ,则将返回TkStatusXML的实例。

HTTP响应

现在让我们讨论HTTP响应的结构及其面向对象的抽象Response 。 界面外观如下:

public interface Response {Iterable<String> head() throws IOException;InputStream body() throws IOException;
}

看起来非常类似于Request ,不是吗? 好吧,它是相同的,主要是因为HTTP请求和响应的结构几乎相同。 唯一的区别是第一行。

有很多有用的装饰器,可以帮助您建立响应。 它们是可组合的 ,这使它们非常方便。 例如,如果要构建一个包含HTML页面的响应,则可以这样编写它们:

final class TkIndex implements Take {@Overridepublic Response act() {return new RsWithStatus(new RsWithType(new RsWithBody("<html>Hello, world!</html>"),"text/html"),200);}
}

在此示例中,装饰器RsWithBody创建一个带有主体但根本没有标题的响应。 然后, RsWithType添加标题Content-Type: text/html 。 然后, RsWithStatus确保响应的第一行包含HTTP/1.1 200 OK

您可以创建自己的装饰器,以重用现有的装饰器。 看看RsPageRsPage是如何完成的。

模板如何?

如我们所见,返回简单的“ Hello,world”页面不是什么大问题。 但是,诸如HTML页面,XML文档,JSON数据集等更复杂的输出呢? 有一些方便的Response装饰器可以实现所有功能。 让我们从简单的模板引擎Velocity开始。 好吧,这不是那么简单。 它相当强大,但是我建议仅在简单情况下使用它。 下面是它的工作原理:

final class TkIndex implements Take {@Overridepublic Response act() {return new RsVelocity("Hello, ${name}").with("name", "Jeffrey");}
}

RsVelocity构造函数接受必须为Velocity模板的单个参数。 然后,调用with()方法,将数据注入Velocity上下文中。 当需要呈现HTTP响应时, RsVelocity将根据配置的上下文“评估”模板。 同样,我建议您仅对简单输出使用此模板方法。

对于更复杂HTML文档,我建议您将XML / XSLT与Xembly结合使用。 我在之前的几篇文章中对此想法进行了解释: 浏览器和RESTful API 中的XML + XSLT,以及同一URL中的网站 。 它简单而强大-Java生成XML输出,而XSLT处理器将其转换为HTML文档。 这就是我们将表示形式与数据分开的方式。 就MVC而言,XSL样式表是“视图”, TkIndex是“控制器”。

我将很快写另一篇关于Xembly和XSL模板的文章。

同时,我们将在Takes中为JSF / Facelets和JSP渲染创建装饰器。 如果您有兴趣提供帮助,请分叉框架并提交请求。

持久性呢?

现在,出现的一个问题是如何处理持久性实体,例如数据库,内存结构,网络连接等。我的建议是在Entry类内部对其进行初始化,并将其作为参数传递给TsApp构造函数。 然后, TsApp将它们传递到构造函数的定制需要

例如,我们有一个PostgreSQL数据库,其中包含一些需要渲染的表数据。 这是在Entry类中初始化与它的连接的方式(我使用的是BoneCP连接池):

public final class Entry {public static void main(final String... args) throws Exception {new FtCLI(new TsApp(Entry.postgres()), args).start(Exit.NEVER);}private static Source postgres() {final BoneCPDataSource src = new BoneCPDataSource();src.setDriverClass("org.postgresql.Driver");src.setJdbcUrl("jdbc:postgresql://localhost/db");src.setUser("root");src.setPassword("super-secret-password");return src;}
}

现在, TsApp的构造TsApp必须接受类型为java.sql.Source的单个参数:

final class TsApp extends TsWrap {TsApp(final Source source) {super(TsApp.make(source));}private static Takes make(final Source source) {return new TsFork(new FkRegex("/", new TkIndex(source)));}
}

TkIndex类还接受Source类的单个参数。 我相信您知道如何在TkIndex中使用它来获取SQL表数据并将其转换为HTML。 这里的要点是,在实例化依赖项时,必须将其注入到应用程序中(类TsApp的实例)。 这是一种纯净的依赖注入机制,它绝对没有容器。 在“依赖注入容器是代码污染者”中阅读有关它的更多信息。

单元测试

由于每个类都是不可变的,并且所有依赖项仅通过构造函数注入,因此单元测试非常容易。 假设我们要测试TkStatus ,它应该返回HTML响应(我正在使用JUnit 4和Hamcrest ):

import org.junit.Test;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
public final class TkIndexTest {@Testpublic void returnsHtmlPage() throws Exception {MatcherAssert.assertThat(new RsPrint(new TkStatus().act()).printBody(),Matchers.equalsTo("<html>Hello, world!</html>"));}
}

此外,我们可以开始在测试HTTP服务器的整个应用程序或任何个人起飞 ,并通过一个真实的TCP套接字测试它的行为; 例如(我正在使用jcabi-http发出HTTP请求并检查输出):

public final class TkIndexTest {@Testpublic void returnsHtmlPage() throws Exception {new FtRemote(new TsFixed(new TkIndex())).exec(new FtRemote.Script() {@Overridepublic void exec(final URI home) throws IOException {new JdkRequest(home).fetch().as(RestResponse.class).assertStatus(HttpURLConnection.HTTP_OK).assertBody(Matchers.containsString("Hello, world!"));}});}
}

FtRemote在随机的TCP端口启动测试Web服务器,并在提供的FtRemote.Script实例上调用exec()方法。 此方法的第一个参数是刚启动的Web服务器主页的URI。

Takes框架的体系结构非常模块化且可组合。 任何个体可以进行测试作为一个独立的部件,绝对独立于框架和其他需要

为什么叫名字?

这就是我经常听到的问题。 这个想法很简单,它起源于电影业。 当影片制成,剧组芽许多需要以捕捉现实,把它放在电影。 每次捕获称为一次获取

换句话说, 拍摄就像现实的快照。

同样适用于此框架。 Take每个实例在某个特定时刻代表一个现实。 然后,将该现实以Response的形式发送给用户。

翻译自: https://www.javacodegeeks.com/2015/04/java-web-app-architecture-in-takes-framework.html

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

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

相关文章

Mono for Android 篇二 使用Spinner 实现下拉列表读取Browser.BookmarksUri

http://www.cnblogs.com/ivanyb/archive/2013/03/05/2944818.html 1、首先在VS2010里面创建一个Android Application 简单说明一下解决方案中各个文件的作用&#xff1a; AndroidManifest.xml&#xff1a;项目环境配置文件&#xff0c;指明了使用Android SDK的版本&#xff0c;…

mysql磁盘临时表清理_mysql 创建大量磁盘临时表

发现 mysql 中创建的临时表基本都是磁盘临时表&#xff0c;对此进行排查。最后为临时表机制说明。通过查看 mysql 状态变量&#xff0c;看到创建的临时表基本都是磁盘临时表。查看临时表配置&#xff0c;默认配置 16M&#xff0c;配置并没有问题。创建两个表&#xff0c;做一个…

吃鸡服务器8月10日维护,《黑潮之上》2021年8月10日不停服维护公告

在黑潮之上手游中2021年8月10日更新了哪些有趣的内容呢&#xff1f;想了解本次更新情况如何的小伙伴们&#xff0c;接下来就让我们一起来看一下吧&#xff01;各位接触者:为了提供良好的游戏体验&#xff0c;保证服务器稳定运行&#xff0c;《黑潮之上》将于2021年8月10日早5:0…

资格赛:题目1:同构

时间限制:2000ms单点时限:1000ms内存限制:256MB描述 给定2个树A和B&#xff0c;保证A的节点个数>B的节点个数。 现在你需要对树A的边进行二染色。 一个好的染色方案&#xff0c;指不存在一个树A中的连通块&#xff0c;同时满足以下2个条件 1. 其中只有同色的边 2. 和B同构。…

java 获取mysql链接_Java中如何获取mysql连接的3种方法总结

前言本文主要来说说三种 Java 中获取 mysql 连接的方式&#xff0c;分享出来供大家参考学习&#xff0c;下面话不多说了&#xff0c;来一起看看详细的介绍&#xff1a;第一种&#xff1a;传统的连接方式&#xff1b;第二种&#xff1a;读取配置文件方式&#xff1b;第三种&…

JavaScript学习总结(六)——JavaScript判断数据类型总结

最近做项目中遇到了一些关于javascript数据类型的判断处理&#xff0c;上网找了一下资料&#xff0c;并且亲自验证了各种数据类型的判断&#xff0c;在此做一个总结吧&#xff01; 一、JS中的数据类型 1.数值型&#xff08;Number&#xff09;&#xff1a;包括整数、浮点数。 2…

material 项目_Web开发必备的 10 个开源项目,不用自己亲自造轮子!

来自&#xff1a;Java and Python君Web 开发中几乎的平台都需要一个后台管理&#xff0c;但是从零开发一套后台控制面板并不容易&#xff0c;幸运的是有很多开源免费的后台控制面板可以给开发者使用&#xff0c;那么有哪些优秀的开源免费的控制面板呢&#xff1f;我在 Github 上…

SVN部署(本地)

1.安装TortoiseSVN 2.建立Repository。在F:/下建立文件夹SVN_NATIVE_REPOSITORY&#xff0c; —— 》 3.在其他任意路径建立任意名称的路径&#xff0c;这里为D:\MSVC Project\SVN_WORK&#xff0c;确保该文件夹为空 右键单击&#xff0c;点击SVN Checkout&#xff0c; 第一行 …

mysql sqlserver schema_MySQL数据库数据迁移到SQLserver

最近因工作需要&#xff0c;需要将mysql数据库迁移到sqlserver&#xff0c;仅仅是数据迁移&#xff0c;因此相对比较简单。对于mysql迁移到sqlserver&#xff0c;我们需要使用到mysql odbc驱动&#xff0c;然后透过sqlserver链接服务器的方式来访问mysql。具体见下文描述。一、…

mysql客户端安装错误_windows下mysql 5.7以上版本安装及遇到的问题

(原)早些前用window安装mysql挺简单的&#xff0c;一个安装程序&#xff0c;一路下一步。2006的5.0版本&#xff0c;确实太早了点。于是官网上又下了一个版本&#xff0c;windows也是提供了二个版本Installer(安装)版和Archive(文档)版。Installer版本的后缀是.msi&#xff0c;…

sklearn中eof报错_sklearn中的数据预处理和特征工程

小伙伴们大家好~o(&#xffe3;▽&#xffe3;)ブ&#xff0c;今天我们看一下Sklearn中的数据预处理和特征工程&#xff0c;老规矩还是先强调一下&#xff0c;我的开发环境是Jupyter lab&#xff0c;所用的库和版本大家参考&#xff1a;Python 3.7.1&#xff08;你的版本至少要…

sql显示前10行数据_SPL 简化 SQL 案例详解:计算各组前 N 行

取出各组的前N行数据是较常见的运算&#xff0c;比如&#xff1a;每个月每种产品销量最高的五天是哪五天&#xff0c;每位员工涨薪最多的一次是哪次&#xff0c;高尔夫会员成绩最差的三次是哪三次&#xff0c;等等。在SQL中&#xff0c;这类运算要用窗口函数以及keep/top/rownu…

4月21日会议总结(整理—祁子梁)

会议成果&#xff1a; 1.今天我们确定了软件版本的时间alphe版在12周做出来&#xff0c;在我们内部测试基本通过。 bate版在13周发布和其他组作交换测试&#xff0c;在14周release版发布并给其他人使用体验准备15周的演讲。 2.同时确定了部分功能实现顺序&#xff0c;”谁是卧底…

如何查看mysql的gtid_汇总丨MySQL GTID技术点,看这一篇就够了!

mysql> SELECT * FROM mysql.gtid_executed;mysql.gtid_executed表是由MySQL服务器提供给内部使用的。它允许副本在副本上禁用二进制日志记录时使用GTIDs&#xff0c;并允许在二进制日志丢失时保留GTID状态。RESET MASTER命令&#xff0c;gtid_executed表将被清除。服务意外…

vb.net详解MDI窗体操作方法

MDI窗体可以避免打开窗体的时候被无数个子窗体困扰&#xff0c;我将为大家一一的介绍一下vb.net中MDI窗体的操作方法 一、如何创建MDI窗体&#xff1f; 1、创建mdi主窗体 新建建立一个默认空白的Windows应用程序&#xff0c;在Form1窗体的属性窗口中找到IsMDIContainer 属性&am…

lokijs可以用mysql_JavaScript实现的内存数据库LokiJS介绍和入门实例_javascript技巧

LokiJS是一个内存数据库&#xff0c;将性能考虑放在第一位。LokiJS支持索引和更快的文档访问&#xff0c;执行性能非常好(近50万OPS/秒)。其内置DynamicView类可以用于数据子集的索引&#xff0c;甚至获取更快的性能。*阅读这篇文章来看一看LokiJS的性能表现。LokiJS支持collec…

python读取word图片_Python中如何读取Word中的图片

Python能够快速的编写、调试&#xff0c;用来提取各类软件中的图片再好不过了。今天小编就为大家带来在Python中提取Word图片的方法。方法需要批量的修改文件后缀名&#xff0c;并且解压之后将图片拷贝到需要存放的地方&#xff0c;然后将该文件夹清空留作下次的路径&#xff0…

PAT 1074. Reversing Linked List (25)

Given a constant K and a singly linked list L, you are supposed to reverse the links of every K elements on L. For example, given L being 1→2→3→4→5→6, if K 3, then you must output 3→2→1→6→5→4; if K 4, you must output 4→3→2→1→5→6. Input Sp…

php与mysql连接程序_PHP与Mysql连接

首先请确保LAMP环境完全配置成功&#xff0c;否则请猛击我&#xff01;然后通过mysql的密码登陆到phpMyAdmin&#xff0c;在浏览器中输入http://127.0.0.1/phpMyAdmin登陆后就像是这样&#xff1a;首先我们来创建一个用于测试的数据库。偷懒的话直接在phpMyAdmin中创建就可以了…

python pandas excel数据处理_Python处理Excel数据-pandas篇

Python处理Excel数据-pandas篇非常适用于大量数据的拼接、清洗、筛选及分析在计算机编程中&#xff0c;pandas是Python编程语言的用于数据操纵和分析的软件库。特别是&#xff0c;它提供操纵数值表格和时间序列的数据结构和运算操作。它的名字衍生自术语“面板数据”(panel dat…