Fork me on GitHub

面向面试题学习(2) 2019-6-3

  1. 大数相加
  2. 反转链表
  3. 生产者消费者模式
  4. 阻塞队列
  5. 死锁的SQL
  6. group by having
  7. 网络tcp https
  8. 数据库索引优化
  9. 设计模式

1. 大数相加

方法一:模拟进位

算法

每次处理最后两个字符数字,进行模拟进位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Solution {

public String addStrings(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int carry = 0; // 进位
for(int i = num1.length() - 1, j = num2.length() - 1; i >= 0 || j >= 0 || carry == 1; i--, j--){
int x = i < 0 ? 0 : num1.charAt(i) - '0';
int y = j < 0 ? 0 : num2.charAt(j) - '0';
sb.append((x + y + carry) % 10);
carry = (x + y + carry) / 10;
}
return sb.reverse().toString();
}
}

2. 反转链表

leetcode官方题解 https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode/

方法一:迭代

假设存在链表 1 → 2 → 3 → Ø,我们想要把它改成 Ø ← 1 ← 2 ← 3

在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。不要忘记在最后返回新的头引用!

1
2
3
4
5
6
7
8
9
10
11
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}

复杂度分析

  • 时间复杂度:O(n),假设 n 是列表的长度,时间复杂度是 O(n)。
  • 空间复杂度:O(1)。
阅读更多...

Spring Boot应用程序五种部署方式(翻译)

翻译自 Deploying Spring Boot Applications

原作者 Murat Artim

可以使用各种方法将Spring Boot应用程序部署到生产系统中。在本文中,我们将通过以下5种方法来部署Spring Boot应用程序:

  • 使用 Java Archive (JAR) 作为独立应用程序进行部署
  • 将 Web Application Archive (WAR) 部署到servlet容器中
  • 在Docker容器中部署
  • 部署在NGINX Web服务器后面 - 直接设置
  • 部署在NGINX Web服务器后面 - 容器化设置

1. 使用 Java Archive (JAR) 作为独立应用程序进行部署

Spring Boot 应用程序可以轻松打包到 JAR 文件中,并作为独立应用程序进行部署。这是通过 spring-boot-maven-plugin 这个插件完成的。一旦Spring项目通过 Spring Initializr 创建为Maven项目,插件就会自动添加到pom.xml中。

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

要将应用程序打包在单个 jar 文件中,需要在项目目录下运行maven命令 mvn package。这将把应用程序打包到一个可执行的jar文件中,该文件包含所有依赖项(包括嵌入式servlet容器 ,如果它是一个Web应用程序)。要运行 jar 文件,请使用以下标准JVM命令 java -jar <jar-file-name>.jar

2. 将 Web Application Archive (WAR) 部署到servlet容器中

可以将Spring Boot应用程序打包到WAR文件中,以部署到现有的servlet容器(例如Tomcat,Jetty等)中。可以按如下方式完成:

在pom.xml文件中,通过 <packaging> war </ packaging> 指定为WAR包。这会将应用程序打包成WAR文件(而不是JAR)。在第二步,将Tomcat(servlet容器)依赖关系的范围设置为provided(以便它不会部署到WAR文件中):

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId
<scope>provided</scope>
</dependency>

通过继承 SpringBootServletInitializer 并重写 configure 方法来初始化 Tomcat 所需的 Servlet 上下文,如下所示:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class DemoApp extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoApp.class);
}
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}

要将应用程序打包到 war 文件中,请在项目目录下运行标准maven命令mvn clean package。这将生成可以部署到 servlet 容器中的 WAR 包。要在现有 Tomcat 容器中运行应用程序,请将生成的 WAR 文件复制到 tomcat/webapps/目录。

3. 在Docker容器中部署

在将应用程序部署到Docker容器之前,我们首先要将应用程序打包在 JAR 文件中。之前已经解释了这个过程,因此我假设我们有一个jar文件。

在第一步,我们需要构建一个容器镜像。为此,我们首先在项目根目录中创建一个Dockerfile文件,如下所示:

1
2
3
4
5
6
7
8
# latest oracle openjdk is the basis 将最新的oracle openjdk作为基础
FROM openjdk:oracle
# copy jar file into container image under app directory 将jar文件复制到容器镜像的app目录
COPY target/demoApp.jar app/demoApp.jar
# expose server port accept connections 暴露服务端口接收连接
EXPOSE 8080
# start application 运行应用程序
CMD ["java", "-jar", "app/demoApp.jar"]

请注意,在上面的代码片段中,我们假设应用程序 JAR 文件“demoApp.jar”位于项目的 target 目录下。我们还假设嵌入式 servlet 端口是8080(这是 Tomcat 的默认情况)。

我们现在可以使用以下命令构建Docker镜像(Dockerfile所在的位置):

1
docker image build -t demo-app:latest .

其中 -t 是要构建的镜像的名称和标记。构建镜像后,我们可以通过以下方式创建和运行容器:

1
docker container run -p 8080:8080 -d --name app-container demo-app

其中-p将主机端口(映射)到容器端口(在这种情况下,两者都是8080)。选项-d(detach)是指在后台运行容器, - name指定容器的名称。

4. 部署在NGINX Web服务器后面 - 直接设置

为实际生产配置 servlet 容器(例如 Tomcat 或 Jetty )(即在端口80上运行,没有root用户和使用SSL)可能不是直接的(但可行)。因此,建议在 Spring Boot 应用程序前使用Web服务器(如Nginx)。这可以通过两种方式完成: 直接设置或容器设置。在本节中,我们将演示直接设置。

在直接设置中,我们直接在 localhost 上运行 Nginx Web 服务器和 Spring Boot 应用程序(当然在不同的端口上)。我们让 Ngnix 代理 REST 请求到 Spring Boot 应用程序。为了这:

1.通过 sudo apt-get install nginx 在 Linux 上安装 Nginx Web 服务器

2.使用文本编辑器打开文件/etc/ngnix/sites-available/default

3.比如说,我们有两个 Spring Boot 应用程序需要代理。可以用以下内容替换文件中的 location 块。请注意,可以在此处找到所有Nginx-Java配置

1
2
3
4
5
6
location /app1 {
proxy_pass http://localhost:8080;
}
location /app2 {
proxy_pass http://localhost:9000;
}

基于此,来自 http://localhost/app1/的请求将被定向到 /http://localhost:8080/,来自 http://localhost/app2/ 的请求将被定向到 /http://localhost:9000/

负载均衡

如果您正在运行 Spring Boot 应用程序的多个实例,则可以启用 Nginx 以应用负载均衡。例如,如果我们在端口8080,8081和8082上运行3个 app1 实例。我们可以在这些服务器之间进行负载均衡,如下所示:

打开文件 /etc/ngnix/sites-available/default 并在文件顶部添加以下内容(在服务器内容之前):

1
2
3
4
5
6
# configure load-balancing 配置负载均衡
upstream backend {
server localhost:8080;
server localhost:8081;
server localhost:8082;
}

修改 app1 的 proxy_pass 参数,如下所示:

1
2
3
location /app1 {
proxy_pass http://backend;
}

基于此,来自 http://localhost/app1/ 的请求将被定向到 /http://localhost:8080//http://localhost:8081//http://localhost:8082/的其中之一。

5. 部署在NGINX Web服务器后面 - 容器化设置

在容器化设置中,我们将 Nginx Web 服务器和所有 Spring Boot 应用程序部署在单独的 Docker 容器上。我们让Nginx(在自己的容器中运行)向Spring Boot应用程序容器代理REST请求。

我们首先将所有 Spring Boot 应用程序打包在 jar 文件中(之前已经解释过)。此时,请注意通过向application.properties(或application.yml)文件添加以下代码来为每个Spring Boot应用程序设置单个服务器端口和根上下文路径:

1
2
server.port=8082
server.servlet.context-path=/search-service

然后我们将生成的 jar 包部署在单独的 Docker 容器中(之前也有解释)。

例如,我们部署了四个 Spring Boot 应用程序; 一个“分析服务”实例和三个“搜索服务”实例。搜索服务的三个实例将由Nginx负载均衡。我们的基本架构如下所示:

我们基于默认配置创建 Nginx 配置文件 nginx.conf 。我们为每个服务添加负载均衡和代理信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http {

upstream backend {
server search-service-1:8080;
server search-service-2:8081;
server search-service-3:8082;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
server_name _;
location /search-service {
proxy_pass http://backend/search-service;
}
location /analysis-service {
proxy_pass http://analysis-service:8083/analysis-service;
}
}
}
events { worker_connections 1024; }

基于此,来自http://localhost/search-service/的请求将被定向到 /http://search-service-1:8080/search-service/, /http://search-service-2:8081/search-service/ /http://search-service-3:8082/search-service/,来自 http://localhost/analysis-service/ 的请求将被定向到 /http://analysis-service:8083/analysis-service/

创建配置文件(nginx.conf)后,我们将在Docker容器中部署 Nginx Web 服务器。为此,我们创建一个Dockerfile ,如下所示:

1
2
3
4
5
6
7
8
# latest nginx 最新的nginx
FROM nginx
# copy custom configuration file 复制自定义配置文件
COPY nginx.conf /etc/nginx/nginx.conf
# expose server port 暴露服务端口
EXPOSE 80
# start server 开启服务
CMD ["nginx", "-g", "daemon off;"]

我们为 Nginx Web 服务器构建一个Docker镜像,如下所示:

1
docker image build -t custom-nginx:latest .

构建所有Docker镜像后,可以通过在以下 docker-compose.yml 文件上运行docker-compose up命令来部署所有系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: '3.7'
services:
nginx_server:
image: custom-nginx
ports:
- '80:80'
networks:
- demo-network
depends_on:
- "search-service-1"
- "search-service-2"
- "search-service-3"
- "analysis-service"
search-service-1:
image: search-service-1
ports:
- '8080:8080'
networks:
- demo-network
search-service-2:
image: search-service-2
ports:
- '8081:8081'
networks:
- demo-network
search-service-3:
image: search-service-3
ports:
- '8082:8082'
networks:
- demo-network
analysis-service:
image: analysis-service
ports:
- '8083:8083'
networks:
- demo-network
networks:
demo-network:
name: demo-network

以上,我们演示了部署 Spring Boot 应用程序的五种方法。选择哪种部署方式要基于整体架构以及目标平台的要求,例如安全性和可用资源。

面向面试题学习(1) 2019-6-2

  1. 500和505的WEB错误码
  2. 二叉树的翻转算法

1. 500和505的WEB错误码

500

服务器500错误。500错误的出现原因是很多的,但是你要知道,500错误是服务器内部错误,而且一般程序上是ASP错误为多的,可能是你的用户权限的问题导致,或者是数据库连接出现了错误,那么要好好检查下服务器语句错误问题。

501

服务器501错误。服务器501错误是服务器还是不具有请求功能的,而且501错误原因是没有实施的,可以用来HttpWebRequest指定一个UserAgent来试试的,有时候你可以换电脑来测试一下的。

502

服务器502错误。这是服务器上的一个错误网关 ,因此说它是无效的,我们在出现了服务器502错误问题的时候,最好是先清除下缓存或者是在服务器上进行刷新试试的,因为502错误牵扯的问题也是很多的,最好是让程序们来去在服务器上下文章。

503

服务器503错误。服务不可用是的一种状态,那么在服务器503错误出现了之后,大家不必担心的, 服务器或许就是正在维护或者暂停了,你可以联系一下服务器空间商。还有的时候cpu占用的频率大导致的。

504

服务器504错误。这是代表着网关超时是现象出现了。504错误问题是一个不好办的问题,当然你必须尝试着和网站官方获得联系,认真的去检查不同的电脑简的ip传输的状况。而且这个504错误要专业的负责人才能去解决。

505

服务器505错误。http的版本是不受支持的,一般的请款下浏览器的默认都是1.x 的版本的, 如果出现了HTTP 1.1版本的,那么你需要在Internet 选项的高级下进行设置的。

2. 二叉树的翻转算法

英文题解地址 invert-binary-tree

方法一:递归

这是一个经典的树问题,最适合递归方法。

算法

空树翻转之后还是空树。根节点(root)的左子节点及其所有的子孙节点构成根节点的左子树(left subtree),同样的,根节点(root)的右子节点及其所有的子孙节点构成根节点的右子树(right subtree)。因此翻转一个二叉树,就是把根节点的左子树翻转一下,同样的把右子树翻转一下,在交换左右子树就可以了。

1
2
3
4
5
6
7
8
9
10
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode right = invertTree(root.right);
TreeNode left = invertTree(root.left);
root.left = right;
root.right = left;
return root;
}

复杂度分析

  • 时间复杂度:O(n),由于树中的每个节点仅被访问一次,因此时间复杂度为O(n),其中n是树中的节点数。我们不能做得更好,因为我们必须访问每个节点来反转它。。
  • 空间复杂度:O(n),由于递归,在最坏的情况下,O(h)函数调用将被放置在堆上,其中h是树的高度。因为在 h ∈ O(n),所以空间复杂度是O(n)

方法二:迭代

或者,我们可以以类似于广度优先搜索的方式迭代地解决问题

算法

我们的想法是,我们需要交换树中所有节点的左右子节点。因此,我们创建一个队列来存储其左右孩子尚未交换的节点。最初,只有根位于队列中。只要队列不为空,就从队列中取出下一个节点,交换其子节点,然后将子节点添加到队列中。空节点不添加到队列中。最终,队列将为空并且所有子项都交换,我们返回原始根。

  • 使用队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
// LinkedList实现了集合框架的Queue接口
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root); // 加入元素
while (!queue.isEmpty()) {
// 获取并移出元素
TreeNode current = queue.poll();

//交换左右子树
TreeNode temp = current.left;
current.left = current.right;
current.right = temp;

//左子树不为空,将左子树入栈
if (current.left != null) queue.add(current.left);
//右子树不为空,将右子树入栈
if (current.right != null) queue.add(current.right);
}
return root;
}
  • 使用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;

Stack<TreeNode> stack = new Stack<>();
stack.push(root);//先将根节点压入堆栈
while (stack.size() > 0) {
//根据栈的先进后出操作,获取栈中最后一个元素,即最先入栈的元素
TreeNode temp = stack.lastElement();
stack.pop();//弹出栈顶元素

//交换左右子树
TreeNode tempLeft = temp.left;
temp.left = temp.right;
temp.right = tempLeft;

//左子树不为空,将左子树入栈
if (temp.left != null) {
stack.push(temp.left);
}
//右子树不为空,将右子树入栈
if (temp.right != null) {
stack.push(temp.right);
}
}
return root;
}

复杂度分析

  • 时间复杂度:O(n),由于树中的每个节点仅被访问 / 添加到队列一次,因此时间复杂度为O(n),其中n是树中的节点数。
  • 空间复杂度:O(n),因为在最坏的情况下,队列将包含二叉树的一个级别中的所有节点。对于完整的二叉树,叶级别为⌈n/2⌉=O(n) 。

Analysis written by: @noran

注意

翻译的可能不够准确,请在下面的评论中批评指正。

参考

http://www.voidcn.com/article/p-sjjaskjk-u.html

Spring @Autowired和构造函数的顺序

最近写代码需要在构造函数中调用一个本class的函数,而这个函数中用到了本class的@Autowired的变量。

于是觉得可能会出错。因为@Autowired一定要等本类构造完成后,才能从外部引用设置进来。所以@Autowired的注入时间一定会晚于构造函数的执行时间。

那,这个局怎么破?

原始代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class PluginServiceImpl implements IPluginService {
/**
* 系统加载的所有插件的接入接口列表
*/
@Autowired
private List<IPluginAccess> plugins;
/**
* 系统加载的所有插件的信息列表
*/
private Map<String, PluginInfo> pluginInfoMap;

public PluginServiceImpl() {
pluginInfoMap = new HashMap<String, PluginInfo>();
// 初始化所有插件
initial();
}

/**
* 初始化
*/
public void initial() {
// 用到了plugins变量
plugins.xxxxxxxx;
}
}

后来查到Spring的建议: 总是在您的bean中使用构造函数建立依赖注入。

所以,代码改成如下,问题解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PluginServiceImpl implements IPluginService {
/**
* 系统加载的所有插件的接入接口列表
*/
private final List<IPluginAccess> plugins;
/**
* 系统加载的所有插件的信息列表
*/
private Map<String, PluginInfo> pluginInfoMap;

@Autowired
public PluginServiceImpl(List<IPluginAccess> plugins) {
this.plugins = plugins;
pluginInfoMap = new HashMap<String, PluginInfo>();
// 初始化所有插件
initial();
}

/**
* 初始化
*/
public void initial() {
// 用到了plugins变量
plugins.xxxxxxxx;
}
}

相关文章

@Autowired的使用:推荐对构造函数进行注释

作者:阿丙

www.cnblogs.com/acm-bingzi

在编写代码的时候,使用@Autowired注解是,发现IDE报的一个警告,如下:

Spring Team recommends “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.

翻译:

Spring建议”总是在您的bean中使用构造函数建立依赖注入。总是使用断言强制依赖”。

这段代码警告原来的写法是:

1
2
@Autowired
private EnterpriseDbService service;

建议后写成下面的样子:

1
2
3
4
5
6
private final EnterpriseDbService service;

@Autowired
public EnterpriseDbController(EnterpriseDbService service) {
this.service = service;
}

奇怪,为何会有这样的建议。

我们知道:**@Autowired 可以对成员变量、方法以及构造函数进行注释。**那么对成员变量和构造函数进行注释又有什么区别呢?

@Autowired注入bean,相当于在配置文件中配置bean,并且使用setter注入。而对构造函数进行注释,就相当于是使用构造函数进行依赖注入了吧。莫非是这两种注入方法的不同。

以下是:@Autowired和构造方法执行的顺序解析

先看一段代码,下面的代码能运行成功吗?

1
2
3
4
5
6
7
@Autowired
private User user;
private String school;

public UserAccountServiceImpl(){
this.school = user.getSchool();
}

答案是不能。

因为Java类会先执行构造方法,然后再给注解了@Autowired 的user注入值,所以在执行构造方法的时候,就会报错。

报错信息可能会像下面:

1
2
3
4
5
6
7
Exception in thread "main" 
org.springframework.beans.factory.BeanCreationException: Error creating
bean with name '...' defined in file [....class]: Instantiation of bean
failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [...]: Constructor threw exception; nested exception is
java.lang.NullPointerException

报错信息说:创建Bean时出错,出错原因是实例化bean失败,因为bean时构造方法出错,在构造方法里抛出了空指针异常。

解决办法是,使用构造器注入,如下:

1
2
3
4
5
6
7
8
private User user;
private String school;

@Autowired
public UserAccountServiceImpl(User user){
this.user = user;
this.school = user.getSchool();
}

可以看出,使用构造器注入的方法,可以明确成员变量的加载顺序。

PS:Java变量的初始化顺序为:静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>@Autowired

参考:http://blog.csdn.net/ruangong1203/article/details/50992147

那么最开始Spring建议,为何要将成员变量加上final类型呢?

网上有解释如下:spring配置默认的bean的scope是singleton,也就是启动后一直有。通过设置bean的scope属性为prototype来声明该对象为动态创建。但是,如果你的service本身是singleton,注入只执行一次。

@Autowired本身就是单例模式,只会在程序启动时执行一次,即使不定义final也不会初始化第二次,所以这个final是没有意义的吧。

可能是为了防止,在程序运行的时候,又执行了一遍构造函数;

或者是更容易让人理解的意思,加上final只会在程序启动的时候初始化一次,并且在程序运行的时候不会再改变。

vultr安装ssr

ssh连接服务器

运行脚本

1
wget --no-check-certificate -O shadowsocks-libev_CN.sh https://raw.githubusercontent.com/uxh/shadowsocks_bash/master/shadowsocks-libev_CN.sh && bash shadowsocks-libev_CN.sh

我对 RESTful API、GraphQL、RPC API 的思考

原文作者:梁桂钊

原文链接:人人都是 API 设计师:我对 RESTful API、GraphQL、RPC API 的思考

一、定义好的规范,已经成功了一大半

通常情况下,规范就是大家约定俗成的标准,如果大家都遵守这套标准,那么自然沟通成本大大降低。例如,大家都希望从阿里的规范上面学习,在自己的业务中也定义几个领域模型:VO、BO、DO、DTO。其中,DO(Data Object)与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 而 DTO(Data Transfer Object)是远程调用对象,它是 RPC 服务提供的领域模型。对于 BO(Business Object),它是业务逻辑层封装业务逻辑的对象,一般情况下,它是聚合了多个数据源的复合对象。那么,VO(View Object) 通常是请求处理层传输的对象,它通过 Spring 框架的转换后,往往是一个 JSON 对象。

事实上,阿里这种复杂的业务中如果不划分清楚 DO、BO、DTO、VO 的领域模型,其内部代码很容易就混乱了,内部的 RPC 在 service 层的基础上又增加了 manager 层,从而实现内部的规范统一化。但是,如果只是单独的域又没有太多外部依赖,那么,完全不要设计这么复杂,除非预期到可能会变得庞大和复杂化。对此,设计过程中因地制宜就显得特别重要了。

另外一个规范的例子是 RESTful API。在 REST 架构风格中,每一个 URI 代表一种资源。因此,URI 是每一个资源的地址的唯一资源定位符。所谓资源,实际上就是一个信息实体,它可以是服务器上的一段文本、一个文件、一张图片、一首歌曲,或者是一种服务。RESTful API 规定了通过 GET、 POST、 PUT、 PATCH、 DELETE 等方式对服务端的资源进行操作。

1
2
3
4
5
6
【GET】          /users                 # 查询用户信息列表
【GET】 /users/1001 # 查看某个用户信息
【POST】 /users # 新建用户信息
【PUT】 /users/1001 # 更新用户信息 (全部字段)
【PATCH】 /users/1001 # 更新用户信息 (部分字段)
【DELETE】 /users/1001 # 删除用户信息

事实上,RESTful API 的实现分了四个层级。第一层次(Level 0)的 Web API 服务只是使用 HTTP 作为传输方式。第二层次(Level 1)的 Web API 服务引入了资源的概念。每个资源有对应的标识符和表达。第三层次(Level 2)的 Web API 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。第四层次(Level 3)的 Web API 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。通常情况下,伪 RESTful API 都是基于第一层次与第二层次设计的。例如,我们的 Web API 中使用各种动词,例如 get_menu 和 save_menu ,而真正意义上的 RESTful API 需要满足第三层级以上。如果我们遵守了这套规范,我们就很可能就设计出通俗易懂的 API。

注意的是,定义好的规范,我们已经成功了一大半。如果这套规范是业内标准,那么我们可以大胆实践,不要担心别人不会用,只要把业界标准丢给他好好学习一下就可以啦。例如,Spring 已经在 Java 的生态中举足轻重,如果一个新人不懂 Spring 就有点说不过去了。但是,很多时候因为业务的限制和公司的技术,我们可能使用基于第一层次与第二层次设计的伪 RESTful API,但是它不一定就是落后的,不好的,只要团队内部形成规范,降低大家的学习成本即可。很多时候,我们试图改变团队的习惯去学习一个新的规范,所带来的收益(投入产出比)甚微,那就得不偿失了。

总结一下,定义好的规范的目的在于,降低学习成本,使得 API 尽可能通俗易懂。当然,设计的 API 通俗易懂还有其他方式,例如我们定义的 API 的名字易于理解,API 的实现尽可能通用等。

二、探讨 API 接口的兼容性

API 接口都是不断演进的。因此,我们需要在一定程度上适应变化。在 RESTful API 中,API 接口应该尽量兼容之前的版本。但是,在实际业务开发场景中,可能随着业务需求的不断迭代,现有的 API 接口无法支持旧版本的适配,此时如果强制升级服务端的 API 接口将导致客户端旧有功能出现故障。实际上,Web 端是部署在服务器,因此它可以很容易为了适配服务端的新的 API 接口进行版本升级,然而像 Android 端、IOS 端、PC 端等其他客户端是运行在用户的机器上,因此当前产品很难做到适配新的服务端的 API 接口,从而出现功能故障,这种情况下,用户必须升级产品到最新的版本才能正常使用。为了解决这个版本不兼容问题,在设计 RESTful API 的一种实用的做法是使用版本号。一般情况下,我们会在 url 中保留版本号,并同时兼容多个版本。

1
2
【GET】  /v1/users/{user_id}  // 版本 v1 的查询用户列表的 API 接口
【GET】 /v2/users/{user_id} // 版本 v2 的查询用户列表的 API 接口
阅读更多...

Gradle、Maven项目相互转换

原文作者: 夏末

原文链接: https://notes.wanghao.work/2017-08-21-Gradle、Maven项目相互转换.html

在开发Android项目的时候,使用的是Gradle构建工具,喜欢它的灵活和方便,在转向Java后端开发的时候更多时候使用的是Maven构建工具,然而看着漫天的尖括号,心里实在是难受。虽然只是一个构建工具,本着折腾的心,我还是更认可和看好Gradle。然而很多时候你的队友并没有习惯去使用或者快速熟悉Gradle构建工具,那么这个时候就需要将Gradle项目转换为Maven项目了,或者将Maven项目转换为Gradle项目了。

安装Gradle/Maven

首先是安装构建工具,这个没啥好说的。

Windows

打开Powershell或者Cmder执行以下命令完成安装:

1
2
choco install gradle
choco install maven

choco为windows下的一款包管理工具,可以方便安装管理配置一些常见的软件包,如果你没有安装choco的话,请移步:https://chocolatey.org/

Mac

打开Terminal,执行以下命令安装:

1
2
brew install gradle
brew install maven

Maven to Gradle

需要特别说明的是,GradleMaven的支持是比较完善的,因此,转换也是非常的简单,在pom.xml文件所在的目录下执行:

1
2
gradle init     # 根据pom.xml内容生成对应的gradle配置
gradle build # 开启gradle构建

Gradle to Maven

Gradle项目转Maven项目需要借助一个Gradle插件,在项目的modulebuild.gradle文件中加入以下配置即可:

1
apply plugin: 'maven'

通过双击Idea的Gradle Tasks GUI:

或者执行命令来完成转换:

1
gradle install

完成之后,将会在当前Module项目的build目录下的poms文件夹下生成pom-default.xml,将其拷贝到项目的根目录下即可。


通过实际测试,这样的生成的pom-default.xml文件是不能用于直接maven构建的,因为生成的pom-default.xml文件中的groupId还需要我们手动指定下。这样显然是不清真的,于是我们可以在build.gradle文件中将其事先定义好,这样生成的pom文件就不用我们再手动更改了:

然而这样我们还是觉得麻烦,毕竟需要手动复制到项目根目录,再重新命名。我们还可以通过Hook Gradle中Maven插件的installTask来完成自动的复制和命名,编辑build.gradle:

1
2
3
4
5
6
task convert2Maven {
doLast {
file("$buildDir/poms/pom-default.xml").renameTo(file("$rootDir/pom.xml"))
}
}
install.dependsOn(convert2Maven)

此时,再执行gradle install这个task就可以看到gradle已经自动为我们在项目的根目录下生成好了pom.xml文件啦。

五种Sublime text 3同时快速编辑多行内容

原文地址 五种Sublime text 3同时快速编辑多行内容

Sublime text 3是一个非常强大的网站编辑工具。

其中快速编辑多行内容功能最为强大。

先说下,使用下面的功能要安装一个叫emmet的插件。

下面就来看下具体的五种方式吧:

1,鼠标选中多行,按下 Ctrl Shift L (Command Shift L) 即可同时编辑这些行;

2,鼠标选中文本,反复按 CTRL D (Command D) 即可继续向下同时选中下一个相同的文本进行同时编辑;

3,鼠标选中文本,按下 Alt F3 (Win) 或 Ctrl Command G(Mac) 即可一次性选择全部的相同文本进行同时编辑;

4,Shift 鼠标右键 (Win) 或 Option 鼠标左键 (Mac) 或使用鼠标中键可以用鼠标进行竖向多行选择;

5,Ctrl 鼠标左键(Win) 或 Command 鼠标左键(Mac) 可以手动选择同时要编辑。

  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信