Skip to content

Commit bb5f6a2

Browse files
authored
Merge pull request #43 from houko/feat/more-examples
feat: add five example modules, fix the async example, and fix a runtime-only -parameters bug
2 parents f52d94b + b058ae6 commit bb5f6a2

53 files changed

Lines changed: 1770 additions & 203 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.idea
22
*.iml
33
node_modules
4-
target
4+
target
5+
upload
6+
.DS_Store

Readme.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
## 模块一览
2020

21-
一共 16 个模块。`core` 是被其他模块共同依赖的基础包,其余每个模块各自演示一项技术。
21+
一共 21 个模块。`core` 是被其他模块共同依赖的基础包,其余每个模块各自演示一项技术。
2222

2323
| 模块 | 演示内容 | 启动类 | 需要的外部服务 |
2424
| --- | --- | --- | --- |
@@ -38,9 +38,16 @@
3838
| `crawler` | jsoup 网络爬虫(阴阳师式神数据),抓取后落库 | `CrawlerMain` | MySQL |
3939
| `freemarker` | FreeMarker 模板引擎 | `FreemarkerMain` ||
4040
| `thymeleaf` | Thymeleaf 模板引擎 | `ThymeleafMain` ||
41+
| `validation` | 参数校验(`@Valid` / `@Validated`)与 `@RestControllerAdvice` 全局异常处理 | `ValidationMain` ||
42+
| `fileupload` | 文件上传下载,含路径穿越防护 | `FileUploadMain` ||
43+
| `restclient` |`RestClient` 调用外部 HTTP 服务 | `RestClientMain` ||
44+
| `cache` | Spring Cache 抽象 + Caffeine 本地缓存 | `CacheMain` ||
45+
| `actuator` | 健康检查、自定义 `HealthIndicator` 与业务指标 | `ActuatorMain` ||
4146

4247
`socket` 使用 8081 外,其余模块都监听 **8080**,所以一次只启动一个模块。
4348

49+
其中 `validation``fileupload``restclient``cache``actuator``async` 六个模块附带可直接运行的测试。它们都不依赖外部服务,`mvn test` 即可跑通,也可以当作各自技术点的可执行文档来读。
50+
4451
## 技术栈
4552

4653
| 组件 | 版本 |
@@ -52,6 +59,8 @@
5259
| Jackson | 3.x |
5360
| API 文档 | springdoc-openapi 3.x(OpenAPI 3.1) |
5461
| 数据库驱动 | MySQL Connector/J |
62+
| 缓存 | Caffeine(`cache` 模块) |
63+
| 监控 | Micrometer + Spring Boot Actuator(`actuator` 模块) |
5564
| 其他 | MyBatis、Lettuce(Redis)、jsoup、Apache POI、fastjson2、zxing |
5665

5766
Spring、Jackson、Hibernate、JUnit 等版本统一由 `spring-boot-dependencies` BOM 管理,不在本项目中单独指定。
@@ -84,6 +93,12 @@ mvn spring-boot:run -pl order
8493

8594
在 IDE 中则找到对应模块的 `*Main` 类直接运行即可。
8695

96+
跑测试:
97+
98+
```bash
99+
mvn test
100+
```
101+
87102
![run](screenshot/run.png)
88103

89104
部署到服务器时,Spring Boot 内置了 Tomcat,把打好的 jar 传上去执行就可以:

actuator/pom.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>xiaomo</artifactId>
7+
<groupId>info.xiaomo</groupId>
8+
<version>2020.1</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>actuator</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>info.xiaomo</groupId>
17+
<artifactId>core</artifactId>
18+
<version>2020.1</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-actuator</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-test</artifactId>
27+
<scope>test</scope>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.springframework.boot</groupId>
31+
<artifactId>spring-boot-starter-webmvc-test</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
</dependencies>
35+
36+
<build>
37+
<plugins>
38+
<plugin>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-maven-plugin</artifactId>
41+
<executions>
42+
<execution>
43+
<goals>
44+
<goal>repackage</goal>
45+
</goals>
46+
</execution>
47+
</executions>
48+
</plugin>
49+
</plugins>
50+
</build>
51+
</project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package info.xiaomo.actuator;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5+
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
6+
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
7+
import org.springframework.context.annotation.ComponentScan;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
/**
11+
* @author : xiaomo
12+
* Description: 健康检查与运行指标启动器
13+
*/
14+
@Configuration
15+
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
16+
@ComponentScan("info.xiaomo.actuator")
17+
public class ActuatorMain {
18+
19+
public static void main(String[] args) {
20+
SpringApplication.run(ActuatorMain.class, args);
21+
}
22+
23+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package info.xiaomo.actuator.controller;
2+
3+
import info.xiaomo.core.base.Result;
4+
import io.micrometer.core.instrument.Counter;
5+
import io.micrometer.core.instrument.MeterRegistry;
6+
import io.micrometer.core.instrument.Timer;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
/**
13+
* 自定义业务指标。注册到 MeterRegistry 后可以在 /actuator/metrics/greeting.count 看到。
14+
*
15+
* @author : xiaomo
16+
*/
17+
@RestController
18+
@RequestMapping("/greeting")
19+
public class GreetingController {
20+
21+
private final Counter greetingCounter;
22+
private final Timer greetingTimer;
23+
24+
public GreetingController(MeterRegistry registry) {
25+
this.greetingCounter = Counter.builder("greeting.count")
26+
.description("打招呼接口被调用的次数")
27+
.register(registry);
28+
this.greetingTimer = Timer.builder("greeting.latency")
29+
.description("打招呼接口的耗时")
30+
.register(registry);
31+
}
32+
33+
@GetMapping("/{name}")
34+
public Result<String> greet(@PathVariable("name") String name) {
35+
return greetingTimer.record(() -> {
36+
greetingCounter.increment();
37+
return new Result<>("你好, " + name);
38+
});
39+
}
40+
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package info.xiaomo.actuator.health;
2+
3+
import org.springframework.boot.health.contributor.Health;
4+
import org.springframework.boot.health.contributor.HealthIndicator;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.io.File;
8+
9+
/**
10+
* 自定义健康检查。Bean 名字去掉 HealthIndicator 后缀就是它在 /actuator/health 里的键,
11+
* 所以这里会显示为 "diskSpaceRatio"。
12+
*
13+
* @author : xiaomo
14+
*/
15+
@Component
16+
public class DiskSpaceRatioHealthIndicator implements HealthIndicator {
17+
18+
/**
19+
* 可用空间低于该比例就判定为不健康。
20+
*/
21+
private static final double THRESHOLD = 0.05;
22+
23+
@Override
24+
public Health health() {
25+
File root = new File(".");
26+
long total = root.getTotalSpace();
27+
long free = root.getUsableSpace();
28+
if (total <= 0) {
29+
return Health.unknown().withDetail("reason", "无法读取磁盘信息").build();
30+
}
31+
32+
double freeRatio = (double) free / total;
33+
Health.Builder builder = freeRatio >= THRESHOLD ? Health.up() : Health.down();
34+
return builder
35+
.withDetail("totalBytes", total)
36+
.withDetail("freeBytes", free)
37+
.withDetail("freeRatio", String.format("%.4f", freeRatio))
38+
.withDetail("threshold", THRESHOLD)
39+
.build();
40+
}
41+
42+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
logging.config=classpath:config/logback-dev.xml
2+
server.port=8080
3+
4+
server.max-http-header-size=20971520
5+
6+
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
7+
spring.jackson.time-zone=GMT+8
8+
9+
# 暴露哪些端点。生产环境不要用 * , 按需列出即可
10+
management.endpoints.web.exposure.include=health,info,metrics,env,loggers
11+
# 展示健康检查的明细, 默认只返回一个总的 status
12+
management.endpoint.health.show-details=always
13+
14+
# /actuator/info 的内容
15+
management.info.env.enabled=true
16+
info.app.name=SpringBootUnity actuator \u793A\u4F8B
17+
info.app.description=\u5065\u5EB7\u68C0\u67E5\u4E0E\u8FD0\u884C\u6307\u6807
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<configuration scan="true">
4+
5+
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
6+
<encoder charset="UTF-8">
7+
<pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %highlight(%msg) %n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<root level="INFO">
12+
<appender-ref ref="stdout"/>
13+
</root>
14+
15+
<logger name="info.xiaomo" level="DEBUG"/>
16+
17+
</configuration>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package info.xiaomo.actuator;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.boot.test.context.SpringBootTest;
6+
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
7+
import org.springframework.test.web.servlet.MockMvc;
8+
9+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
10+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
11+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
12+
13+
@SpringBootTest(classes = ActuatorMain.class)
14+
@AutoConfigureMockMvc
15+
class ActuatorEndpointTest {
16+
17+
@Autowired
18+
private MockMvc mockMvc;
19+
20+
@Test
21+
void 健康检查应当返回UP并包含自定义的检查项() throws Exception {
22+
mockMvc.perform(get("/actuator/health"))
23+
.andExpect(status().isOk())
24+
.andExpect(jsonPath("$.status").value("UP"))
25+
.andExpect(jsonPath("$.components.diskSpaceRatio").exists())
26+
.andExpect(jsonPath("$.components.diskSpaceRatio.details.freeRatio").exists());
27+
}
28+
29+
@Test
30+
void info端点应当返回配置的应用信息() throws Exception {
31+
mockMvc.perform(get("/actuator/info"))
32+
.andExpect(status().isOk())
33+
.andExpect(jsonPath("$.app.name").value("SpringBootUnity actuator 示例"));
34+
}
35+
36+
@Test
37+
void 未在白名单中的端点不应当被暴露() throws Exception {
38+
// beans 没有列在 management.endpoints.web.exposure.include 里
39+
mockMvc.perform(get("/actuator/beans"))
40+
.andExpect(status().isNotFound());
41+
}
42+
43+
@Test
44+
void 调用业务接口后自定义指标应当可见且计数递增() throws Exception {
45+
mockMvc.perform(get("/greeting/{name}", "xiaomo"))
46+
.andExpect(status().isOk())
47+
.andExpect(jsonPath("$.data").value("你好, xiaomo"));
48+
49+
mockMvc.perform(get("/greeting/{name}", "houko"))
50+
.andExpect(status().isOk());
51+
52+
mockMvc.perform(get("/actuator/metrics/greeting.count"))
53+
.andExpect(status().isOk())
54+
.andExpect(jsonPath("$.name").value("greeting.count"))
55+
.andExpect(jsonPath("$.measurements[0].value").value(2.0));
56+
}
57+
58+
}

async/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
<artifactId>core</artifactId>
1717
<version>2020.1</version>
1818
</dependency>
19+
<dependency>
20+
<groupId>org.springframework.boot</groupId>
21+
<artifactId>spring-boot-starter-test</artifactId>
22+
<scope>test</scope>
23+
</dependency>
1924
</dependencies>
2025

2126
<build>

0 commit comments

Comments
 (0)