Java-020-RESTful

RESTful API

HTTP协议

  • URI – 互联网资源的地址
  • 操作 – 对资源执行的操作: GET, PUT, POST, DELETE
  • 返回状态码 – 表示执行结果: 200, 201, 403, 404, 500

以上是 HTTP协议在程序语义层面的定义, 同时也抽象了整个互联网内容的组织和交互方式.

  • 互联网上所有的内容都是资源, 每个资源有唯一的URI
  • 交互就是对互联网资源的操作(GET, PUT, POST, DELETE)
  • 然后资源的操作结果就是状态返回码

每一次请求的发起都是客户端向服务端发起.

RESTful API

RESTful API就是基于HTTP协议对互联网的内容定义的方式提出的一套互联网应用的架构体系.

RESTful是一种设计模式规范的指南, 不是一个强制性的要求, 所以遵守都必须是靠开发者自己遵守.

RESTful API的基本要求

URI

  1. 协议: HTTP或HTTPS
  2. 域名和地址一般格式: http://<域名>/api/

    1
    有 api 路径是为了版本的更新
  3. 域名和地址之后接资源名字统一用复数, 例如

    1
    2
    3
    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals
    https://api.example.com/v1/employees

操作

  1. GET /accounts – 列出所有的账户信息
  2. GET /accounts/ – 查询id为的账户信息
  3. PUT /accounts/ – 更新id为的账户信息, 注意这个操作是幂等的, 幂等是多次同样的操作, 最终结果只有一个!
  4. POST /accounts – 创建一个新的账户
  5. DELETE /accounts – 删除一个账户 (危险操作)

额外参数

URI里的参数列表, 一般用在查询里面. 比如 GET /accounts?type=ADMIN 这样用来筛选账户, 一般不推荐直接把id这样直接资源的索引键值直接用在URL的参数列表里, 而是直接放在URI里, 因为它本身是一个资源!

状态转换

HTTP协议是无状态的, 也就是说, 客户端不保存状态, 然后通过RESTful API定义的操作改变资源在服务端的状态

返回状态

操作的执行结果

  • 请求负载和返回负载

JSON – JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

JSON的基本结构是对象, 以及对象里的键值对, 例如:

1
2
3
4
{ "user": {
"username": "admin",
"password": "1234"}
};

详细资料参见: https://www.json.org/json-zh.html

或者XML

总结

我们如果反思一下上述对RESTful API的介绍, 是不是就很容易感觉到, 其实RESTful = HTTP + JSON

确实也是这样的

为什么我们需要RESTful API

  • 原生的互联网架构方式来建构互联网内容
  • 统一的通信协议和开发语义. 统一标准可以带来效率的提升和协作的高效
  • 接口一致, 操作没有歧义

RESTful API 扩展阅读

案例

登录逻辑

1
2
3
4
5
6
7
8
9
# 以前的登录url
/signup?username=abc&password=123456

POST /accounts

{
"username": "abc",
"password": "123456"
}

操作

1
2
3
4
5
6
7
8
9
GET /accounts 列举 -- ListAccounts(filter: AccountType, Account创建时间)
GET /accounts/<id> -- GetAccount()
PUT /accounts/<id> -- UpdateAccount()
POST /accounts -- CreateAccount()
DELETE /accounts -- DeleteAccount()

# 错误的url
GetAccount() -- GET /accounts?id= ->>>>>>>>>>>>>ListAccounts()
# url 应该用户一看到就知道是获取什么内容

状态码

  • 200, 201 – 成功
  • 400, 403, 404 – 用户的输入错误
  • 500 – 服务端的错误

我们RESTful API交互

  • 读别人的API, 然后理解和使用 => 用户
  • 写自己的API, 让别人理解和使用 => 开发者

读API

  1. 有什么资源, 资源的层次结构是什么样的?
    用户 -> 仓库 -> issues
  2. 操作: 增删改查
  3. 请求参数: URL的参数, 负载里的json
  4. 返回的状态(200, 201, 404) 和 返回的json格式

调用API

  1. 调用 API: 构造URL (资源) –> 发送响应请求(GET, POST, PUT) –> 参数
  2. 检验返回的状态码: 成功状态如何下一步的解析结果, 错误处理
  3. 解析结果

写RESTful API

  1. 想清楚我们的应用里可以抽象出什么样的资源, 和他们的层次结构
  2. 想清楚对对象的基本操作: GET, POST, PUT, DELETE的含义是什么
  3. 组织接口代码, Spring如何去定义这些路由(URI): URI -> Java类和方法
  4. 开始实现

Spring

资源

  • RestController
  • GetMapping, PutMapping, PostMapping, DeleteMapping: URI -> Java类和方法

参数

  • RequestParam: URL额外参数
  • PathVariable
  • RequestBody

处理逻辑*

  • 遵循Java代码的组织和开发方式
  • 面向对象的方式抽象和组织代码

状态和结果

  • 使用正确和准确的状态码!
  • 返回结果!
  • ResponseEntity
  • HttpStatus

PUT和POST的比较

  • PUT – 做具体资源的更新
  • POST – 创建新的资源, 在资源列表里添加一个资源

PUT — PUT操作是幂等的! op^n == op^(n + 1), 所以用PUT来做整体更新的时候, 多次重复同样内容的更新操作, 最终产生的结果是一致的!

所以在代码实现PUT操作时候, 要把这个操作实现成幂等的!

POST — POST操作不是幂等的! 创建的操作

Java-019-HTTP

HTTP协议

HTTP协议是互联网的基础协议

  • 本质上是Web前端程序和Web后端程序的通信的协议
  • 定义了前端给后端发送请求的格式
  • 同时也定义了后端解析前端发来的请求(HTTP请求)的方式
  • 使用程序语言来描述, HTTP协议给后端程序定义了一个接口

HTTP协议格式

HTTP请求格式

基本元素
  • 请求路径 — URL, 例如: https://xiedaimala.com/lives/723a04ed
  • 请求操作命令(动词): GET, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH
  • 客户端标记 User-Agent, 值的例子: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
  • 传输的内容格式: Content-Type: application/json
  • 接收类型 Accept, 值的例子: text/plain, text/html, image/jpeg
  • Cookie: 用于传送和状态相关的信息的键值对

示例

1
2
3
4
5
6
7
8
9
10
GET /favicon.ico HTTP/1.1
Host: www.xiedaimala.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,ja;q=0.7,zh-TW;q=0.6
Cookie: _ga=GA1.2.963880079.1538204516; _gid=GA1.2.932523408.1538204516; _gat=1; Hm_lvt_f89e0235da0841927341497d774e7b15=1538204517,1538205893; Hm_lpvt_f89e0235da0841927341497d774e7b15=1538205893

HTTP响应格式

基本元素
  • HTTP协议版本: HTTP/1.1
  • 返回状态 Status: 200, 201, 404 (详细示例看后文)
  • 时间, 例如: Sat, 29 Sep 2018 07:24:53 GMT
  • 返回内容类型: text/plain, text/html, image/jpeg

示例

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Date: Sat, 29 Sep 2018 07:24:53 GMT
Content-Type: image/vnd.microsoft.icon
Content-Length: 3638
Connection: keep-alive
Server: nginx/1.6.2
Last-Modified: Thu, 24 May 2018 05:56:53 GMT
ETag: "e36-56ced517f0753"
Cache-Control: max-age=2592000, public
X-ORCA-Accelerator: HIT from 006.mul.lax01.us.krill.zenlogic.net
X-Cache: HIT from 006.mul.lax01.us.krill.zenlogic.net
Accept-Ranges: bytes
总结
  • 一个HTTP请求在语义上表达对一个互联网资源(URL)的操作(GET, POST, PUT)
  • 然后Web后端返回资源操作的结果和相关信息

常见的Content-Type字段值

  • text/plain
  • text/html
  • text/css
  • image/jpeg
  • image/png
  • image/svg+xml
  • audio/mp4
  • video/mp4
  • application/javascript
  • application/pdf
  • application/zip
  • application/atom+xml

HTTP状态码 Status

HTTP状态码

  • 1xx消息——请求已被服务器接收,继续处理
  • 2xx成功——请求已成功被服务器接收、理解、并接受
  • 3xx重定向——需要后续操作才能完成这一请求
  • 4xx请求错误——请求含有词法错误或者无法被执行(客户端的责任)
  • 5xx服务器错误——服务器在处理某个正确请求时发生错误(服务端的责任)

*如何理解HTTP协议是无状态

HTTP请求的实现应该是无状态的

  • 每个请求都是独立的
  • 每个请求都需要显示的附带状态信息. 比如告诉后端, 我是谁 (认证), 我现在在什么状态

如何使用浏览器查看HTTP请求和返回结果

  • Chrome调试工具 –> 浏览器打开页面发送HTTP请求和返回结果
    -Wireshark –> 抓包工具, 用来查看其他客户端的请求

Spring实现响应GET, POST, PUT, DELETE操作请求

使用Postman发送GET, POST, PUT, DELETE操作请求

  • 操作相关注解 @PostMapping, @GetMapping, @PutMapping, Deletemapping
  • 参数相关注解 @RequestBody, @PathVariable
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package user;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.*;
/*
操作相关注解 @PostMapping, @GetMapping, @PutMapping, Deletemapping
参数相关注解 @RequestBody, @PathVariable
*/
@RestController
public class UserController {
private final Map<String,User> map = new HashMap<String,User>();

UserController(){
map.put("aaa",new User("aaa",11));
map.put("bbb",new User("bbb",22));
map.put("ccc",new User("ccc",33));
}

@GetMapping("/users")
public List<User> getUsers() {
Set<String> keys = map.keySet();
List<User> res = new ArrayList<>();
for (String name:keys) {
res.add(map.get(name));
}
return res;
}

@GetMapping("/users/{name}")
public User getUserByName(@PathVariable("name") String name) {
if(map.containsKey(name)){
return map.get(name);
}else{
return null;
}
}

@PostMapping("/users")
public User AddUser(@RequestBody User user) {
String name = user.getName();
map.put(name,user);
return user;
}

@PutMapping("/users/{name}")
public User UpdateUser(@PathVariable("name") String name ,@RequestBody User user) {
map.get(name).setAge(user.getAge());
return user;
}
@DeleteMapping("/users/{name}")
public User DelUserByName(@PathVariable("name") String name){
if(map.containsKey(name)){
return map.remove(name);
}else{
return null;
}
}
}

Spring实现自定义返回状态码

ResponseEntity类的使用

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package user;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.*;
/*
操作相关注解 @PostMapping, @GetMapping, @PutMapping, Deletemapping
参数相关注解 @RequestBody, @PathVariable
*/
@RestController
public class UserController {
private final Map<String,User> map = new HashMap<>();

UserController(){
map.put("aaa",new User("aaa",11));
map.put("bbb",new User("bbb",22));
map.put("ccc",new User("ccc",33));
}

@GetMapping("/users")
ResponseEntity<List<User>> getUsers(){
Set<String> keys = map.keySet();
List<User> res = new ArrayList<>();
for (String name:keys) {
res.add(map.get(name));
}
return new ResponseEntity<>(res, HttpStatus.OK);
}

@GetMapping("/users/{name}")
public ResponseEntity<User> getUserByName(@PathVariable("name") String name) {
if(map.containsKey(name)){
return new ResponseEntity<>(map.get(name),HttpStatus.OK);
}else{
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

@PostMapping("/users")
public ResponseEntity<User> AddUser(@RequestBody User user) {
String name = user.getName();
if(map.containsKey(name)){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}else{
map.put(name,user);
return new ResponseEntity<>(map.get(name),HttpStatus.CREATED);
}
}



@PutMapping("/users/{name}")
public ResponseEntity<User> UpdateUser(@PathVariable("name") String name ,@RequestBody User user) {
if(map.containsKey(name)){
User updateUser = map.get(name);
updateUser.setAge(user.getAge());
return new ResponseEntity<>(map.get(name),HttpStatus.OK);
}else{
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

@DeleteMapping("/users/{name}")
public ResponseEntity<User> DelUserByName(@PathVariable("name") String name){
if(map.containsKey(name)){
return new ResponseEntity<>(map.remove(name),HttpStatus.OK);
}else{
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}

参考资料

Java-018-springboot入门

spring参考资料

Spring Boot 入门

@(Programming_Training)[Web]

Spring是什么

Spring Framework 5.0 - The right stack for the right job.

Spring 是 JVM (Java虚拟机) 生态环境里一个提供应用开发需要用到的技术栈整合的框架

最主要的应用场景就是Web开发:

  • Web请求路由
  • HTTP请求解析
  • Session管理
  • MVC架框架
  • 依赖注入
  • 等等…

Spring现在仍然再不断的进化, 比如说最近比较热门的Reactive栈

https://spring.io/img/homepage/diagram-boot-reactor.svg

Spring Boot是什么

Spring Boot是帮助我们快速使用Spring创建应用的工具和框架.

  • 快速开始使用Spring
  • 构建应用 - REST API, WebSocket, Web, Streaming, Tasks, 等等
  • 简化安全
  • 对SQL和NoSQL的丰富支持
  • 内嵌的运行时支持 - Tomcat, Jetty等
  • 提高开发效率的工具
  • 生产环境支持: 跟踪, 指标, 监控
  • 集成IDE

Spring Boot是一个很好的学习Java Web App技术栈平台

把Spring开发中使用到的流程和工具给集成到一起, 然后boot

第一个Spring Boot项目

之前我们学习了如何使用Maven, 那我们来看看如何使用Maven来安装和配置Spring Boot项目

项目创建和配置

  1. 打开IntelliJ创建默认的Maven项目

    1
    2
    3
    groupid 输入  com.xiedaimala.java
    artifactid 输入 hello-world
    version 是默认的
  2. 配置 POM.xml 文件

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <?xml version="1.0" encoding="UTF-8"?>
    <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>com.xiedaimala.java</groupId>
    <artifactId>spring-sample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    </parent>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <properties>
    <java.version>1.10</java.version>
    </properties>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    <repositories>
    <repository>
    <id>spring-releases</id>
    <url>https://repo.spring.io/libs-release</url>
    </repository>
    </repositories>
    <pluginRepositories>
    <pluginRepository>
    <id>spring-releases</id>
    <url>https://repo.spring.io/libs-release</url>
    </pluginRepository>
    </pluginRepositories>
    </project>
  3. 点击Maven标签里的刷新

使用Spring Boot构建第一个Web程序

Controller

定义Web请求的接收和处理

创建文件: src/main/java/hello/GreetingController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package hello;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.atomic.AtomicLong;

@RestController
public class GreetingController {

private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong(); // 原子操作, 因为Spring默认是支持多线程的

@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value="name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}

结果返回对象

定义处理Web请求的结果对象

创建文件: src/main/java/hello/Greeting.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package hello;

public class Greeting {

private final long id;
private final String content;

public Greeting(long id, String content) {
this.id = id;
this.content = content;
}

public long getId() {
return id;
}

public String getContent() {
return content;
}
}

Web主程序

定义一个Spring程序的入口, Spring负责初始化Context

创建文件: src/main/java/hello/Application.java

1
2
3
4
5
6
7
8
9
10
11
12
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

运行

双击运行spring-boot:run

或者在项目根目录, 运行命令:

mvn spring-boot:run

在浏览器中打开: http://localhost:8080/greeting?name=hjx

代码链接

JVM JRE

Scala, Clojure, Groory

Spring可以说是集成很多应用开发中必须使用到的通用功能的框架和类库, 提供一个开发应用的解决方案

Spring Boot

内置支持Web server

Spring Boot项目配置

功能

  1. 接收用户的请求和用户输入: name
  2. 解析输入, 然会给用户一个json的结构, 里面就是Helle,

Spring Web 程序结构讲解

Controller
Model
层次结构

  1. Spring 接收请求, 解析请求
  2. 属于我们的代码 – 随便写
  3. 可以读取数据库, 做个爬虫
  4. 把结果返回给Spring, Spring再返回用户

Web后端程序调试

Postman

Java-017-linux使用二

VI 命令

在ssh远程终端环境下, vi是最常用的终端内编辑器

操作模式

  • 命令模式
  • 插入模式

操作模式

命令模式下可以使用输入字母按键进行常见操作

例如:

  • h j k l: 向左 向上 向下 向右
  • i: 切换到输入模式
  • x: 删除
  • X: 撤销删除

输入模式

在命令模式下, 按 i 可以切入到输入模式, 然后可以进行文本的输入.

按ESC可以退出输入模式退到操作模式

命令模式

在操作模式下, 按 : 冒号则切换到命令模式, 可以输入一些命令进行操作:

  • ‘:w’: 保存当前文件
  • ‘:w [文件名]’: 保存到文件 [文件名]
  • ‘:wq’: 保存并退出
  • : q!: 不保存, 直接退出

扩展学习

scp命令

scp命令是用来远程复制文件, 双向

实例:

  1. 把本地文件复制到远程

    1
    scp [本地文件] [用户名]@[远程服务器IP地址]:[远程目录]
  2. 把文件目录和文件复制到远程

    1
    scp -r [本地目录] [用户名]@[远程服务器IP地址]:[远程目录]
  3. 复制远程文件到本地

    1
    scp [用户名]@[远程服务器IP地址]:[远程文件] [本地目录]
  4. 复制远程文件和目录到本地

    1
    scp -r [用户名]@[远程服务器IP地址]:[远程目录] [本地目录]

使用ssh配置缩写

1
2
3
4
5
6
7
scp -r [远程机器配置缩写]:[远程目录] [本地目录] 

scp -r [本地目录] [远程机器配置缩写]:[远程目录]

scp [本地文件] [远程机器配置缩写]:[远程目录]

scp -r [本地目录] [远程机器配置缩写]:[远程目录]

服务器

  • 稳定的开发环境
  • 统一的运行和测试环境 – Docker 容器,Serverless

服务器的来源

自己搭建

  • 虚拟机
  • 旧的机器 – 机箱

云服务器

  • 省钱, 方便, 稳定性
  • 全球访问 (?)
  • 其他的配套服务: 监控, 负载均衡, 备份

云服务的选择

  • AWS
  • 阿里云
  • 腾讯云

科学上网

  • Shadowsocks
  • VPN

SSH登录

  1. 地址, 用户名
  2. 公钥, 私钥

VI

  • 常见模式
  • 常见命令和操作

VIM已死, VIM永生

  • IntelliJ IDEA里的VIM支持插件: IdeaVim
  • VS Code的Vim插件: Vim
  • chrome浏览器的vim插件: Vimium

远程拷贝文件

  • 拷贝本地代码到远程, 然后在服务器上运行

开发场景流程

  1. 在本地写代码, 运行单元测试
  2. 使用同步工具把代码从本地同步到远程服务器
  3. 在远程服务器运行代码
  4. 把运行结果文件拷贝回本地分析, 或者日志文件

命令

1
scp --> ssh copy

Tmux

1
2
3
ssh -i "id_rsa.pem" ubuntu@ec2-54-193-62-229.us-west-1.compute.amazonaws.com -t "tmux"

ssh -i "id_rsa.pem" ubuntu@ec2-54-193-62-229.us-west-1.compute.amazonaws.com -t "tmux attach"

SSH Config

1
2
3
4
Host aws
HostName ec2-54-193-62-229.us-west-1.compute.amazonaws.com
User ubuntu
IdentityFile "~/.ssh/id_rsa"

练习

  1. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口为默认端口, 如果你需要用当前登录的默认账号登录, 最简洁的登录命令是?

    1
    ssh 192.168.0.18
  2. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口设置为44, 如果你需要用当前登录的默认账号登录, 最简洁的登录命令是?

    1
    ssh -p 44 192.168.0.18
  3. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口为44, 如果你需要用账号admin登录, 最简洁的登录命令是?

    1
    ssh -p 44 admin@192.168.0.18
  4. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口为44, 如果你需要用账号 admin 和个人私钥 ~/key.pem 登录, 最简洁的登录命令是?

    1
    ssh -i ~/key.pem -p 44 admin@192.168.0.18
  5. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口为默认端口, 如果用当前登录用户登录, 复制远程服务器上的文件 ~/sample.txt 到本地当前目录, 最简洁的登录命令是?

    1
    scp 192.168.0.18:~/sample.txt .
  6. 假设我们有一台服务器, 地址为: 192.168.0.18, SSH服务的端口为默认端口, 如果用用户 admin 登录, 复制本地当前目录下的文件 ~/sample.txt 到远程服务器的目录 ~/test, 最简洁的登录命令是?

    1
    scp ~/sample.txt admin@192.168.0.18:~/test/

Java-017-linux使用一

ssh远程登录

Windows工具

putty

SSH命令

ssh命令是用于远程登录和管理Linux服务器的一个工具. 主要功能就是本地输入Linux命令, 然后实际命令是在远程机器上运行.

这个命令是我们日常登录到服务器进行远程管理工作使用最多的命令.

登录命令
1
ssh [-p 端口] [用户名]@[远程服务器IP地址]

命令实例:

1
2
3
如果省略端口设置, 使用ssh默认端口22
如果省略用户名设置, 使用当前系统登录用户的用户名
ip地址不能省略

登录验证方式

  1. 密码验证

    运行命令后, 按照提示输入远程机器的用户的对应密码登录

  2. 使用密钥对验证

    • 公钥
    • 私钥

      公钥和私钥是一一对应的, 公钥类似于锁, 私钥是对应的钥匙. 可以提前把一个公钥上传到服务器上, 然后私钥保存在本地, 每次登录的时候, ssh命令会自动使用本地的私钥进行验证!

    1. 生成密钥, 如果已经生成过, 则保存在 ~/.ssh/目录下, 私钥: id_rsa 和公钥: id_rsa.pub. 重新生成记得备份
      1
      ssh-keygen // 在 ~/.ssh/下生成私钥和公钥
2. 复制生成的公钥 ~/.ssh/id_rsa.pub 到远程服务器

    
1
2
3
ssh-copy-id [-p 端口] [用户名]@[远程服务器IP地址]

ssh-copy-id ubuntu@ec2-54-193-80-137.us-west-1.compute.amazonaws.com
如果还是不行,就打开远程主机的/etc/ssh/sshd_config这个文件,检查下面几行前面"#"注释是否取掉。
1
2
3
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

ssh配置文件

ssh配置文件在 ~/.ssh/config, 可以在配置文件中, 添加常见的登录信息, 然后之后登录时候可以使用更短的命令

例如:

1
2
3
4
5
6
7
Host ubuntu
HostName 10.211.55.5
User hacker
IdentityFile "~/.ssh/hacker.pem"

# 缩写登录命令:
ssh ubuntu

参考文章和扩展

SSH服务器安装和启动

一般远程服务器, 例如阿里云服务器, AWS EC2. 默认情况下是开启ssh服务器的, 这样就可以直接使用ssh命令登录.

如果你的服务器没有安装则可以使用如下命令安装和启动, 以Ubuntu为例:

  1. 安装

    1
    sudo apt-get install openssh-server
  1. 启动
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //启动
    sudo service ssh start

    // 重启
    sudo service ssh restart

    //停止
    sudo service ssh stop
    安装服务自启动
    sudo update-rc.d ssh defaults
    取消服务自启动
    sudo update-rc.d ssh remove

Java-015-英语学习

英语学习

目标

  • 阅读文档的能力! 逻辑能力
  • 写的能力! 逻辑能力
  • 听! 听公开课, 听美剧, 听歌
  • 说!

学习英语一定要有环境? 环境不应该成为一个前提!

  • 大量的英语内容输入!

单词!

只有一种方法: 背

  • 抄写, 读

随便找一本单词书, 然后就读和抄写

音标

推荐 托福单词书 (3000个词)

每天固定1个小时, 比如晚饭后, 午饭后.

词典的发音

短期突击 –> 形成习惯! 打破从0到1的门槛!
保持大量的输入 (阅读) –> 总结不懂的单词 –> 抄写, 背诵
短期突击 –> 背另外一本单词书, 托福 (10000个词)

阅读

  • 技术文档
  • 英文入门读物 (动物农场, 哈利波特)
  • 英文的技术书籍

摘抄, 记单词

推断单词的意思. 一页书, 只查2个单词, 查过就直接添加到有道单词本. 然后零碎时间背诵

训练你的眼睛和脑子, 快速识别出单词和句型

听力和口语

  • 跟读! 找一些跟读材料, 听一句, 模仿着读一句!
  • 训练你的肌肉, 熟悉英语的节奏!

语法和句型

训练! 训练你的眼睛和脑子, 快速识别出单词和句型

写作
基础还是读, 足够量的输入!

背经典的句型, 经典的语句, 诗句!

心法

  1. 慢就是快

  2. 少就是多

  3. 不断重复。

过程

  1. 背单词是短期突击,从0到1,让你先认识一篇文章的大概其

  2. 大量输入,持续学习 从1到2 。。。 从1到100

Java-015-多线程

Java线程的概念

多线程程序包含两条或两条以上并发运行的部分.程序中每个这样的部分都叫一个线程(thread),每个线程都有独立的执行路径。因此,多线程是多任务处理的一种特殊形式.

一句话总结进程和线程的关系: 进程是线程的容器!

进程的主线程

主线程的重要性体现在:

  • 它是产生其他子线程的线程
  • 通常它必须最后完成执行,因为它执行各种关闭动作

主线程是运行程序时候自动创建的, 但是其也是一个Thread类的实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CurrentThreadDemo {
public static void main(String args[]) {
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);
// change the name of the thread
t.setName("My Thread");
System.out.println("After name change: " + t);
try {
for (int n = 5; n > 0; n--) {
System.out.println(n);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
}
}

创建线程

继承Thread

  • 我们可以通过继承Thread类, 然后Override run()方法, 然后创建该类的实例来创建除了主线程之外的其他线程, 并且在run()方法中赋予属于这个线程的代码逻辑, 这个run()方法是新线程的入口.
  • 这个新建的Thread类的子类, 会继承Thread类的一个方法 – start(), 当我们调用start()方法时候会自动生成一个线程, 然后调用run()方法执行线程的逻辑.
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
// Create a second thread by extending Thread
class NewThread extends Thread {
NewThread() {
// Create a new, second thread
super("Demo Thread");
System.out.println("Child thread: " + this);
start(); // Start the thread
}

// This is the entry point for the second thread.
public void run() {
try {
for (int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}

public class Demo {
public static void main(String args[]) {
new NewThread(); // create a new thread
try {
for (int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}

实现 Runnable 接口

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
// Create multiple threads.
class NewThread implements Runnable {
String name; // name of thread
Thread t;

NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}

// This is the entry point for thread.
public void run() {
try {
for (int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(name + "Interrupted");
}
System.out.println(name + " exiting.");
}
}

public class Demo {
public static void main(String args[]) {
new NewThread("One"); // start threads
new NewThread("Two");
new NewThread("Three");
try {
// wait for other threads to end
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}

练习

使用多线程技术, 写一个程序, 创建3个线程, 然后完成工作:

  • 主线程一直循环输出从1 开始的数字, 一直到100;
  • 创建的2个子线程: 子线程A输出大写字母从A到Z, 子线程B输出小写字母从a到z
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.repeat;


public class Work01 {
public static void main(String[] args) throws InterruptedException {
Thread t = Thread.currentThread();
t.setName("main thread");

MyThread t2 = new MyThread("aaa");
MyThread2 t3 = new MyThread2("bbb");

t2.start();
t3.start();

for (int i = 1; i <= 100; i++) {
System.out.println(t.getName() + " running " + i);
Thread.sleep(50);
}
}
}

class MyThread extends Thread{
private Thread t;
private String name;

public MyThread(String name) {
this.name = name;
}

@Override
public void run() {
try {
for (int i = 65; i < 65+26; i++) {
char a = (char) i;
System.out.println(name+ " running " + a);
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public void start(){
t = new Thread (this, name);
t.start ();
}
}

class MyThread2 implements Runnable{
private Thread t;
private String name;

public MyThread2(String name) {
this.name = name;
}

@Override
public void run() {
try {
for (int i = 97; i < 97 + 26; i++) {
char a = (char) i;
System.out.println(name+ " running " + a);
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}

}

public void start(){
t = new Thread (this, name);
t.start ();
}
}

我们的任务是: 在命令行模拟类似的逻辑:

  • 程序运行之后, 会接收用户输入的字符串, 用户输入回车之后, 把这行字符串写入到一个新的文件中,命名为数字序号 (1.txt, 2.txt, 3.txt)… (回顾一下之前IO的课程中, 对文件写入的操作)
  • 用户输入回车之后, 重新等待用户输入字符串, 并且循环执行 1 中的逻辑
  • 要求是敲下回车之后, 不能有卡顿的情况, 也就是, 写入文件必须在子线程中完成
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.repeat;

import java.io.*;
import java.util.Scanner;

public class Work02 {
public static void main(String[] args) throws IOException {

while (true){
// 获取输入
String input = getInput();
// 新开线程,写入文件
new MyFileIoThread( input ).start();

}
}

public static String getInput(){
Scanner sc = new Scanner(System.in);
String str = sc.next();
return str;
}
}

class MyFileIoThread extends Thread{
public static int count = 1;
private String content;

public MyFileIoThread(String content) {
this.content = content;
MyFileIoThread.count++;
System.out.println(MyFileIoThread.count);
}

@Override
public void run() {
FileOutputStream output = null;
try {
String fileName = MyFileIoThread.count + ".txt";
output = new FileOutputStream("day015Thread/res/" + fileName);
OutputStreamWriter writer = new OutputStreamWriter(output,"UTF-8");
PrintWriter printWriter = new PrintWriter(writer);

// 模拟写入文件慢的操作
Thread.sleep(500);
System.out.println(" file: " + fileName + " 写入成功");

printWriter.write(content);
printWriter.close();
output.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

参考阅读资料

可以慢慢看, 或者简单过一遍, 然后脑子留个概念. 等之后有经验和需求的时候反复回来咀嚼

大致了解下一些应用场景, 如果不理解也没关系, 慢慢等你有需求被逼无奈就会懂了

一个不错的Java多线程面试相关的题目总结, 可以准备面试时候大致过一遍, 有些内容可能是因为需要用于面试中题目原因, 忽略很多不同概念的联系和有些概念不是特别准确, 但是准备面试后可以看一下. 学习还是需要训练中慢慢体会

Java面试题之多线程.md)

Java-014-maven和包管理

maven

maven 添加运行命令

1
exec:java -Dexec.mainClass=你的包名.类名 -Dexec.args

mac安装 maven

安装并且配置好了 maven 但是一直报 command not found

~/.bash_profile

1
2
3
4
export M2_HOME=/Users/huangjiaxi/Desktop/soft/apache-maven-3.6.1
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# 这句一定要在上句下面
export PATH=$PATH:$M2_HOME/bin

maven 修改配置文件

修改maven根目录下的conf文件夹中的setting.xml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">

<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
`

macOS下可以配置同样内容在: ~/.m2/settings.xml

maven 运行报错

第一种可能

class not found

  • 原因大概是找不到 jar包里的文件,需要添加才行
1
https://blog.csdn.net/Liu_xud/article/details/84385755

第二种可能

你只运行了 clean 没运行 compile 也就是没编译

Java-013-泛型和集合

泛型

先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo01泛型 {
// 泛型
// 定义了hash表 key是 String类型 value是 Integer类型
static HashMap<String,Integer> counts = new HashMap<>();

static Stack<String> stringStack = new Stack<>();

static Stack<Integer> intStack = new Stack<>();


public static void main(String[] args) {
stringStack.push("abc");

String one = stringStack.pop();

intStack.push(1);
Integer two = intStack.pop();

// intStack.push("ssss"); 规定了Stack 内的类型是 int的 所以无法 加入 String类型的
}
}

为什么有泛型?

假如我们想要实现一个只接受 Integer 和一个只接受String 的栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IntStack {
ArrayList<Integer> store = new ArrayList<>();

public void push(Integer item){
store.add(item);
}

public Integer pop(){
Integer tmp = store.get(store.size() - 1);
store.remove(store.size() - 1);
return tmp;
}

public int size(){
return store.size();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringStack {
ArrayList<String> store = new ArrayList<>();

public void push(String item){
store.add(item);
}

public String pop(){
String tmp = store.get(store.size() - 1);
store.remove(store.size() - 1);
return tmp;
}

public int size(){
return store.size();
}
}

很明显,代码结构和意思都一样,很重复,但是有了问题就是bug,改两次

泛型出现了!

泛型出现了!

泛型出现了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自己实现一个泛型,支持任意类型的Stack ,
// 把数据类型变成参数,把重复代码去除了 如 IntStack / StringStack
// T 是 类型参数
public class Stack2<T> {
ArrayList<T> store = new ArrayList<>();

public void push(T item){
store.add(item);
}

public T pop(){
T tmp = store.get(store.size() - 1);
store.remove(store.size() - 1);
return tmp;
}

public int size(){
return store.size();
}
}

集合 List

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
// 列表
List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();

// 操作 在尾部添加
arrayList.add("hello");
arrayList.add("world");
arrayList.add("~~~~");

// 通过索引去访问
arrayList.get(0);

//arrayList.subList(0,5); // 取出 索引从 0 到 5 位置的列表 内容不够就报错

// 通过索引 删除
arrayList.remove(0);

// 访问 列表的长度
int size = arrayList.size();

// for循环里的语法糖

for (String item: arrayList) {
System.out.println( item );
}

Set

1
2
3
4
5
6
7
8
9
10
11
12
// Set 主要用来去重复
Set<String> set = new HashSet<>();

set.add("a");
set.add("b");
set.add("c");

set.add("a"); // 去重复

for (String item: set) {
System.out.println( item );
}

练习题

汉诺塔问题

1
2
3
4
5
6
7
8
9
在经典的汉诺塔问题中,有 3 个塔和 N 个可用来堆砌成塔的不同大小的盘子。要求盘子必须按照从小到大的顺序从上往下堆 (如,任意一个盘子,其必须堆在比它大的盘子上面)。同时,你必须满足以下限制条件:

(1) 每次只能移动一个盘子。
(2) 每个盘子从堆的顶部被移动后,只能置放于下一个堆中。
(3) 每个盘子只能放在比它大的盘子上面。

请写一段程序,实现将第一个堆的盘子移动到最后一个堆中.

在编程训练网站完成训练: https://www.lintcode.com/problem/mock-hanoi-tower-by-stacks/description

代码实现

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
public class Tower {
private Stack<Integer> disks;

public Tower(int i) {
// create three towers
disks = new Stack();
}
public void add(int d) {
// Add a disk into this tower
if (!disks.isEmpty() && disks.peek() <= d) {
System.out.println("Error placing disk " + d);
} else {
disks.push(d);
}
}
public void moveTopTo(Tower t) {
// Move the top disk of this tower to the top of t.
if(t.disks.isEmpty() || (!disks.isEmpty() && t.disks.peek() >= disks.peek())) {
t.disks.push(disks.pop());
}
}
public void moveDisks(int n, Tower destination, Tower buffer) {
// Move n Disks from this tower to destination by buffer tower
if(n <= 0) {
return;
} else if(n == 1) {
moveTopTo(destination);
} else {
moveDisks(n-1, buffer, destination);
moveDisks(1, destination, buffer);
buffer.moveDisks(n-1, destination, this);
}
}
public Stack<Integer> getDisks() {
// write your code here
return disks;
}
}

找不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Given two strings s and t which consist of only lowercase letters.

String t is generated by random shuffling string s and then add one more letter at a random position.

Find the letter that was added in t.

输入给两个只包含小写字母的字符串 S 和 T.

字符串 T 是在字符串 S 基础上添加一个小写字母, 然后随机打乱之后生成.

请找出那个被加入的小写字母.

示例:

Input:
s = "abcd"
t = "abcde"

Output:
e

Explanation:
'e' is the letter that was added.
在线测试网站: https://leetcode.com/problems/find-the-difference/description

代码

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
package work;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class Test02 {

public static void main(String[] args) {
char a = findTheDifference("abc","abcd");
System.out.println(a);
}


public static char findTheDifference(String s, String t) {
HashMap<Character,Integer> hash = new HashMap<>();

String tmp = s + t;
for(int i = 0;i<tmp.length();i++){
char current = tmp.charAt(i);
if(hash.containsKey(current)){
hash.put(current,hash.get(current) + 1);
}else{
hash.put(current,1);
}
}

Set<Character> a = hash.keySet();
Character res = null;
for (Character item: a) {
if(hash.get(item)==1){
res = item;
break;
}
}
return res;
}
}

Min Stack

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
41
42
43
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

push(x) -- Push element x onto stack.
pop() -- Removes the element on top of the stack.
top() -- Get the top element.
getMin() -- Retrieve the minimum element in the stack.
设计一个栈, 支持4种操作: push, pop, top和getMin, 关键是getMin是获取栈中的最小值.

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> Returns -3.
minStack.pop();
minStack.top(); --> Returns 0.
minStack.getMin(); --> Returns -2.
基本代码框架:

class MinStack {

/** initialize your data structure here. */
public MinStack() {

}

public void push(int x) {

}

public void pop() {

}

public int top() {

}

public int getMin() {

}
}

代码

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
41
42
43
package work;

import java.util.ArrayList;

class MinStack {
private ArrayList<Integer> store;
/** initialize your data structure here. */
public MinStack() {
store = new ArrayList<>();
}

public void push(int x) {
store.add(x);
}

public void pop() {
if(store.size()==0){
throw new Error("数组元素为空");
}
store.remove(store.get(store.size()-1));
}

public int top() {
if(store.size()==0){
throw new Error("数组元素为空");
}
return store.get(store.size()-1);
}

public int getMin() {
if(store.size()==0){
throw new Error("数组元素为空");
}

int res = store.get(0);
for (int i = 1; i < store.size(); i++) {
if( res > store.get(i) ){
res = store.get(i);
}
}
return res;
}
}

推荐一个 idea的插件

1
2
3
4
5
首选项- plugins-搜索 rainbow brackets 彩虹括号

bytecode viewer 字节码查看器
Key Promoter X 快捷键提示插件
Stream java8流调试器,debug 的bar里最后一个按钮

推荐书籍

  • Efective java
  • 设计模式
  • 算法**(强烈推荐)
  • if web开发 推荐
    • Spring
    • Play
  • 分布式系统开发
    • Hadoop
    • MapReduce