一、项目初始化与依赖准备
先创建一个标准的Spring Boot工程,依赖上只需要引入Web、Scheduling和Http客户端相关组件。Spring Boot自带的RestTemplate或WebClient都可以用于请求外部行情接口,这里以RestTemplate为例,因为它配置简单,适合中小型监控任务。如果希望非阻塞和高并发,可以改用WebClient,思路完全一致。

在pom.xml中加入spring-boot-starter-web和spring-boot-starter-mail(如果要邮件提醒),还可以加入spring-boot-starter-validation做配置校验。定时任务由Spring框架自带的@Scheduled提供,不需要额外依赖。RestTemplate在Spring Boot 3.x中需要手动创建Bean,因为自动配置不再提供。可以定义如下配置类,并设置连接超时和读取超时,避免外部API响应缓慢拖垮定时任务线程。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
@Configuration
public class HttpClientConfig {
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}
}
上面代码中的超时值单位是毫秒,5秒对于公共行情接口来说比较合理。如果使用的是需要鉴权的交易所接口,还需要在请求头中附加API Key,这部分放到后续章节说明。
二、接入加密货币行情API
免费的加密货币行情API选择不少,例如CoinGecko、CoinMarketCap、Binance公共接口。CoinGecko的/simple/price接口使用最简单,返回JSON结构清晰,不需要Key即可调用,适合个人项目和小规模监控。以获取比特币和以太坊的美元价格为例,请求地址如下。
GET https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd
返回结果是一个嵌套JSON,形如{"bitcoin":{"usd":65000},"ethereum":{"usd":3200}}。我们可以定义一个对应的DTO来接收,也可以直接用Map解析。为了代码可读性,建议定义CoinPriceResponse类,包含Map<String, Map<String, Double>>类型的price字段。这里使用Jackson自动映射,需要保证字段名与JSON一致。
public class CoinPriceResponse {
private Map<String, Map<String, Double>> price;
public Map<String, Map<String, Double>> getPrice() {
return price;
}
public void setPrice(Map<String, Map<String, Double>> price) {
this.price = price;
}
}
实际请求时,可以将币种列表和计价货币写成配置项,便于后续增加监控币种。例如在application.yml中定义:
crypto:
api:
base-url: https://api.coingecko.com/api/v3
monitor:
coins: bitcoin,ethereum
currency: usd
threshold:
bitcoin: 70000
ethereum: 3500
通过@ConfigurationProperties绑定这些参数,服务层就能动态读取。请求逻辑封装在PriceService中,使用UriComponentsBuilder构建请求地址,避免手写字符串拼接导致编码问题。下面是一个基础实现。
@Service
public class PriceService {
private final RestTemplate restTemplate;
private final CryptoProperties properties;
public PriceService(RestTemplate restTemplate, CryptoProperties properties) {
this.restTemplate = restTemplate;
this.properties = properties;
}
public CoinPriceResponse fetchPrice() {
String url = UriComponentsBuilder
.fromHttpUrl(properties.getApi().getBaseUrl())
.path("/simple/price")
.queryParam("ids", properties.getMonitor().getCoins())
.queryParam("vs_currencies", properties.getMonitor().getCurrency())
.toUriString();
return restTemplate.getForObject(url, CoinPriceResponse.class);
}
}
这里注意属性的层级与配置项对应,CryptoProperties内部使用静态嵌套类即可。对于需要API Key的交易所接口,可以在请求头中加入X-MBX-APIKEY之类的头信息,RestTemplate可以通过HttpHeaders和HttpEntity来设置。
三、实现价格监控与阈值判断
监控的核心逻辑是周期性地拉取价格,并与预设阈值比较。Spring的@Scheduled注解可以实现固定频率执行,默认单线程调度,适合这种轻量任务。如果后续监控币种很多,建议配置线程池,避免任务堆积。一个典型的监控方法如下:定时任务每60秒执行一次,获取价格后遍历监控币种,对比配置中的阈值。当价格高于阈值时触发提醒,并记录提醒时间防止重复发送。可以使用ConcurrentHashMap保存最近一次提醒时间,实际项目中建议使用Redis等共享存储。
@Component
public class PriceMonitor {
private final PriceService priceService;
private final AlertService alertService;
private final CryptoProperties properties;
private final Map<String, LocalDateTime> lastAlertTime = new ConcurrentHashMap<>();
public PriceMonitor(PriceService priceService, AlertService alertService,
CryptoProperties properties) {
this.priceService = priceService;
this.alertService = alertService;
this.properties = properties;
}
@Scheduled(fixedDelay = 60000)
public void monitor() {
try {
CoinPriceResponse response = priceService.fetchPrice();
Map<String, Map<String, Double>> prices = response.getPrice();
Map<String, Double> thresholds = properties.getMonitor().getThreshold();
prices.forEach((coin, currencyMap) -> {
Double currentPrice = currencyMap.get(properties.getMonitor().getCurrency());
Double threshold = thresholds.get(coin);
if (currentPrice != null && threshold != null && currentPrice >= threshold) {
if (shouldAlert(coin)) {
alertService.sendAlert(coin, currentPrice, threshold);
lastAlertTime.put(coin, LocalDateTime.now());
}
}
});
} catch (RestClientException e) {
// 记录日志,下一轮继续
}
}
private boolean shouldAlert(String coin) {
LocalDateTime last = lastAlertTime.get(coin);
return last == null || last.plusMinutes(30).isBefore(LocalDateTime.now());
}
}
上面的shouldAlert方法实现了一个简单的去重窗口,同一币种30分钟内最多提醒一次,避免价格在阈值附近震荡时频繁推送。对于更复杂的策略,可以增加低于阈值提醒、涨跌幅提醒,甚至结合技术指标判断,但核心结构保持不变。这里有必要强调异常处理,公共API可能出现限流、超时或返回异常结构,定时任务中捕获RestClientException防止整个调度中断。日志记录建议使用@Slf4j或LoggerFactory,将失败的币种和原因输出,便于排查。
四、集成邮件与Webhook提醒
价格触发阈值后,需要把消息送达用户。邮件是最基础的方式,Spring Boot的JavaMailSender可以让邮件发送非常简单。配置SMTP服务器后,编写AlertService的邮件实现。邮件提醒适合个人使用,但移动端推送更及时。可以接入Server酱、PushPlus或Telegram Bot等Webhook服务。其本质都是向指定URL发送POST请求,带上标题和内容。
@Service
public class EmailAlertService implements AlertService {
private final JavaMailSender mailSender;
private final String recipient;
public EmailAlertService(JavaMailSender mailSender,
@Value("${alert.email.recipient}") String recipient) {
this.mailSender = mailSender;
this.recipient = recipient;
}
@Override
public void sendAlert(String coin, Double currentPrice, Double threshold) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(recipient);
message.setSubject("币价提醒: " + coin);
message.setText(coin + " 当前价格 " + currentPrice + " 已超过阈值 " + threshold);
mailSender.send(message);
}
}
下面是一个通用的Webhook提醒实现。实际开发中可以把AlertService作为接口,通过配置决定启用邮件还是Webhook,或者两者同时发送。也可以使用消息队列将提醒任务异步化,避免定时任务线程被慢速的HTTP请求或SMTP调用阻塞。
@Service
public class WebhookAlertService implements AlertService {
private final RestTemplate restTemplate;
private final String webhookUrl;
public WebhookAlertService(RestTemplate restTemplate,
@Value("${alert.webhook.url}") String webhookUrl) {
this.restTemplate = restTemplate;
this.webhookUrl = webhookUrl;
}
@Override
public void sendAlert(String coin, Double currentPrice, Double threshold) {
Map<String, String> body = new HashMap<>();
body.put("title", "币价提醒: " + coin);
body.put("content", coin + " 当前价格 " + currentPrice + " 已超过阈值 " + threshold);
restTemplate.postForObject(webhookUrl, body, String.class);
}
}
五、稳定性优化与扩展方向
公共API通常有频率限制,CoinGecko免费版每分钟约10到30次请求。如果监控多个币种,可以将所有币种合并到一次请求中,而不是循环调用。本示例已经采用这种方式,通过ids参数一次获取所有币种价格。如果币种数量超过API单次限制,可以分批请求并注意间隔。另一个常见问题是价格缓存。如果多个定时任务或服务实例同时运行,频繁请求外部API既浪费配额又增加延迟。可以引入本地缓存Caffeine或集中式缓存Redis,在短时间内复用价格数据。
对于需要秒级监控的场景,建议直接使用交易所WebSocket流而非REST轮询,例如Binance的WebSocket接口可以实时推送价格,但实现复杂度更高。在架构层面,可以将监控服务拆分为采集器和告警服务。采集器负责拉取价格并写入时序数据库,告警服务通过规则引擎判断并发送通知。例如使用Prometheus+Grafana做监控看板,配合Alertmanager实现告警,适合生产环境。但对于个人项目,本文中的单体Spring Boot服务已经足够。
最后提醒一点,币价波动剧烈,阈值设置需要结合自身风险承受能力。同时不要将API Key硬编码在源码中,建议通过环境变量或配置中心管理。至此,一个具备价格拉取、阈值判断和消息推送的监控系统已经搭建完成,后续可以按需扩展更多币种和提醒策略。
Spring Boot加密货币API币价监控修改时间:2026-10-04 02:59:42