Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea
*.iml
node_modules
target
target
upload
.DS_Store
17 changes: 16 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

## 模块一览

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

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

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

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

## 技术栈

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

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

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

跑测试:

```bash
mvn test
```

![run](screenshot/run.png)

部署到服务器时,Spring Boot 内置了 Tomcat,把打好的 jar 传上去执行就可以:
Expand Down
51 changes: 51 additions & 0 deletions actuator/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?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">
<parent>
<artifactId>xiaomo</artifactId>
<groupId>info.xiaomo</groupId>
<version>2020.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>actuator</artifactId>

<dependencies>
<dependency>
<groupId>info.xiaomo</groupId>
<artifactId>core</artifactId>
<version>2020.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
23 changes: 23 additions & 0 deletions actuator/src/main/java/info/xiaomo/actuator/ActuatorMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package info.xiaomo.actuator;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* @author : xiaomo
* Description: 健康检查与运行指标启动器
*/
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@ComponentScan("info.xiaomo.actuator")
public class ActuatorMain {

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package info.xiaomo.actuator.controller;

import info.xiaomo.core.base.Result;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 自定义业务指标。注册到 MeterRegistry 后可以在 /actuator/metrics/greeting.count 看到。
*
* @author : xiaomo
*/
@RestController
@RequestMapping("/greeting")
public class GreetingController {

private final Counter greetingCounter;
private final Timer greetingTimer;

public GreetingController(MeterRegistry registry) {
this.greetingCounter = Counter.builder("greeting.count")
.description("打招呼接口被调用的次数")
.register(registry);
this.greetingTimer = Timer.builder("greeting.latency")
.description("打招呼接口的耗时")
.register(registry);
}

@GetMapping("/{name}")
public Result<String> greet(@PathVariable("name") String name) {
return greetingTimer.record(() -> {
greetingCounter.increment();
return new Result<>("你好, " + name);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package info.xiaomo.actuator.health;

import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;

import java.io.File;

/**
* 自定义健康检查。Bean 名字去掉 HealthIndicator 后缀就是它在 /actuator/health 里的键,
* 所以这里会显示为 "diskSpaceRatio"。
*
* @author : xiaomo
*/
@Component
public class DiskSpaceRatioHealthIndicator implements HealthIndicator {

/**
* 可用空间低于该比例就判定为不健康。
*/
private static final double THRESHOLD = 0.05;

@Override
public Health health() {
File root = new File(".");
long total = root.getTotalSpace();
long free = root.getUsableSpace();
if (total <= 0) {
return Health.unknown().withDetail("reason", "无法读取磁盘信息").build();
}

double freeRatio = (double) free / total;
Health.Builder builder = freeRatio >= THRESHOLD ? Health.up() : Health.down();
return builder
.withDetail("totalBytes", total)
.withDetail("freeBytes", free)
.withDetail("freeRatio", String.format("%.4f", freeRatio))
.withDetail("threshold", THRESHOLD)
.build();
}

}
17 changes: 17 additions & 0 deletions actuator/src/main/resources/config/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
logging.config=classpath:config/logback-dev.xml
server.port=8080

server.max-http-header-size=20971520

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

# 暴露哪些端点。生产环境不要用 * , 按需列出即可
management.endpoints.web.exposure.include=health,info,metrics,env,loggers
# 展示健康检查的明细, 默认只返回一个总的 status
management.endpoint.health.show-details=always

# /actuator/info 的内容
management.info.env.enabled=true
info.app.name=SpringBootUnity actuator \u793A\u4F8B
info.app.description=\u5065\u5EB7\u68C0\u67E5\u4E0E\u8FD0\u884C\u6307\u6807
17 changes: 17 additions & 0 deletions actuator/src/main/resources/config/logback-dev.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true">

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %highlight(%msg) %n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="stdout"/>
</root>

<logger name="info.xiaomo" level="DEBUG"/>

</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package info.xiaomo.actuator;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(classes = ActuatorMain.class)
@AutoConfigureMockMvc
class ActuatorEndpointTest {

@Autowired
private MockMvc mockMvc;

@Test
void 健康检查应当返回UP并包含自定义的检查项() throws Exception {
mockMvc.perform(get("/actuator/health"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"))
.andExpect(jsonPath("$.components.diskSpaceRatio").exists())
.andExpect(jsonPath("$.components.diskSpaceRatio.details.freeRatio").exists());
}

@Test
void info端点应当返回配置的应用信息() throws Exception {
mockMvc.perform(get("/actuator/info"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.app.name").value("SpringBootUnity actuator 示例"));
}

@Test
void 未在白名单中的端点不应当被暴露() throws Exception {
// beans 没有列在 management.endpoints.web.exposure.include 里
mockMvc.perform(get("/actuator/beans"))
.andExpect(status().isNotFound());
}

@Test
void 调用业务接口后自定义指标应当可见且计数递增() throws Exception {
mockMvc.perform(get("/greeting/{name}", "xiaomo"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value("你好, xiaomo"));

mockMvc.perform(get("/greeting/{name}", "houko"))
.andExpect(status().isOk());

mockMvc.perform(get("/actuator/metrics/greeting.count"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("greeting.count"))
.andExpect(jsonPath("$.measurements[0].value").value(2.0));
}

}
5 changes: 5 additions & 0 deletions async/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
<artifactId>core</artifactId>
<version>2020.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
10 changes: 5 additions & 5 deletions async/src/main/java/info/xiaomo/async/AsyncMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

/**
* 把今天最好的表现当作明天最新的起点..~
Expand All @@ -20,15 +20,15 @@
* email: xiaomo@xiaomo.info
* <p>
* Date: 2016/4/1 15:38
* Description: RabbitMq启动器
* Description: 异步任务启动器
* Copyright(©) 2015 by xiaomo.
**/
@Configuration
@EnableAsync
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@ComponentScan("info.xiaomo")
@EntityScan("info.xiaomo.*.model")
public class AsyncMain {
public static void main(String[] args) throws Exception {
public static void main(String[] args) {
SpringApplication.run(AsyncMain.class, args);
}

Expand Down
Loading
Loading