Skip to content

[feature] add smslocal sms notification #3135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Mar 9, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public class SmsConfig {
*/
private UniSmsProperties unisms;

/**
* Smslocal SMS configuration
*/
private SmslocalSmsProperties smslocal;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hertzbeat.alert.config;


import lombok.Data;

/**
* Smslocal SMS Properties
*/
@Data
public class SmslocalSmsProperties {
/**
* SmsLocal account api key
*/
private String apiKey;

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmsConfig;
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
Expand All @@ -33,6 +34,7 @@
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;

/**
* SMS client factory
Expand All @@ -49,9 +51,7 @@ public class SmsClientFactory {

private volatile SmsClient currentSmsClient;

public SmsClientFactory(GeneralConfigDao generalConfigDao,
ObjectMapper objectMapper,
SmsConfig yamlSmsConfig) {
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
Expand Down Expand Up @@ -133,6 +133,9 @@ private void createSmsClient(SmsConfig smsConfig) {
case ALIBABA:
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
break;
case SMSLOCAL:
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
break;
default:
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hertzbeat.alert.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.common.constants.SmsConstants;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.exception.SendMessageException;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
* Smslocal SMS Client Implement
*/
@Slf4j
public class SmsLocalSmsClientImpl implements SmsClient {
private static final String HOST = "secure.smslocal.com";

private static final String PATH = "/api/service/enterprise-service/external/sms";

private static final String FROM = "Hertzbeat";

private static final String SUCCESS_CODE = "200";

private final SmslocalSmsProperties config;

public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) {
this.config = smslocalSmsProperties;
}

@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
if (Objects.isNull(receiver) || Objects.isNull(alert)) {
log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert);
return;
}

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String content = alert.getCommonAnnotations().get("summary");
if (Objects.isNull(content) || Objects.isNull(alert.getCommonAnnotations().get("description"))) {
content = alert.getAlerts().get(0).getContent();
}
SmsMessage smsMessage = new SmsMessage(FROM, receiver.getPhone(), content);

String payload = JsonUtil.toJson(smsMessage);

HttpPost httpPost = new HttpPost("https://" + HOST + PATH);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Token", config.getApiKey());
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));

log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);

// send http request and handle response
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());

log.debug("SMS response status: {}, body: {}", statusCode, responseBody);

if (statusCode != 200) {
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
}

JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
JsonNode jsonNode = jsonResponse.get(0);
if (Objects.isNull(jsonNode)) {
log.warn("jsonResponse parse errorCode failed: {}", jsonResponse);
return;
}
String errorCode = jsonNode.get("errorCode").asText();
if (!SUCCESS_CODE.equals(errorCode)) {
String msgid = jsonNode.get("id").asText();
throw new SendMessageException(errorCode + ":" + msgid);
}

log.info("Successfully sent SMS to phone: {}", receiver.getPhone());
}
} catch (Exception e) {
log.error("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}

}

@Override
public String getType() {
return SmsConstants.SMSLOCAL;
}

@Override
public boolean checkConfig() {
if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) {
log.warn("smslocal properties can not be null: {}", config);
return false;
}
return true;
}

@Getter
@Setter
private static class SmsMessage {
String from;
String to;
String content;
final int datacoding = 0;
final String direction = "mt";

public SmsMessage(String from, String to, String content) {
this.from = from;
this.to = to;
this.content = content;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
public interface SmsConstants {
// Tencent cloud SMS
String TENCENT = "tencent";

// Alibaba Cloud SMS
String ALIBABA = "alibaba";

// UniSMS
String UNISMS = "unisms";

// Smslocal SMS
String SMSLOCAL = "smslocal";
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ public class SmsNoticeSender {

private SmsUniSmsConfig unisms;

private SmslocalConfig smslocal;

private boolean enable = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.hertzbeat.manager.pojo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


/**
* Smslocal SMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalConfig {
/**
* Smslocal api key
*/
private String apiKey;
}
4 changes: 3 additions & 1 deletion hertzbeat-manager/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE

scheduler:
server:
Expand All @@ -247,4 +249,4 @@ ai:
# api key
api-key:
#At present, only IFLYTEK large model needs to be filled in
api-secret:
api-secret:
27 changes: 27 additions & 0 deletions home/docs/help/alert_sms.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,33 @@ alerter:

Now you can configure this information in your hertzbeat application.

### Smslocal SMS Configuration

SMSLocal is an all-in-one SMS service for businesses, with features like multi-way sending, strong security, and 24/7 support. You can refer to smslocal's [Developer Documentation](https://www.smslocal.com/developer/) for configuration.

Add/Fill in the following Smslocal configuration to `application.yml` (replace parameters with your own SMS server configuration):

```yaml
alerter:
sms:
enable: true # Whether to enable
type: smslocal # SMS provider type, set to smslocal
smslocal: # Smslocal configuration
api-key: YOUR_API_KEY_HERE
```

1. Register smslocal account
- Visit [Smslocal Website](https://www.smslocal.com/)

2. Obtain `api-key`
- Log in to [Smslocal Api Access](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- Go to "API Access" page
- Click the eye button
- Copy the displayed access key
- Then you can configure the `application.yml` file

Now you can configure this information in your hertzbeat application.

## Operation steps

1. **【Alarm notification】->【Add new recipient】 ->【Select SMS notification method】**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,33 @@ alerter:

现在您可以把这些信息配置到您的hertzbeat应用中。

### smslocal短信配置

smslocal是一款面向企业的一体化短信服务平台,具备诸如多种发送方式、强大的安全性以及全天候支持等特性。你可以参考 smslocal 的[开发者文档](https://www.smslocal.com/developer/)来进行配置。

在 `application.yml` 中添加/填写以下 smslocal 配置内容(请用你自己的短信服务器配置参数替换相关参数):

```yaml
alerter:
sms:
enable: true # 是否启用
type: smslocal # 短信服务提供商类型,设置为smslocal
smslocal: # smslocal配置
api-key: 在此处填入你的API密钥
```

1. 注册 smslocal 账号
- 访问 [smslocal官网](https://www.smslocal.com/)

2. 获取 `api-key`
- 登录 [smslocal API accessKey访问页面](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- 进入 “API 访问” 页面
- 点击眼睛图标按钮
- 复制显示的访问密钥
- 然后你就可以配置 `application.yml` 文件了

现在你可以在你的 Hertzbeat 应用程序中配置这些信息。

## 操作步骤

1. **【告警通知】->【新增接收人】 ->【选择短信通知方式】**
Expand Down
2 changes: 2 additions & 0 deletions script/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE

scheduler:
server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE

scheduler:
server:
Expand Down
Loading
Loading