code: /*
   * 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.skywalking.oap.server.core.alarm.provider.feishu;
  
  import com.google.gson.Gson;
  import com.google.gson.JsonObject;
  import lombok.RequiredArgsConstructor;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
  import org.apache.skywalking.oap.server.core.alarm.HttpAlarmCallback;
  import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
  import org.apache.skywalking.oap.server.library.util.StringUtil;
  
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.net.URI;
  import java.security.InvalidKeyException;
  import java.security.NoSuchAlgorithmException;
  import java.util.Arrays;
  import java.util.Base64;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  /**
   * Use SkyWalking alarm feishu webhook API.
   */
  @Slf4j
  @RequiredArgsConstructor
  public class FeishuHookCallback extends HttpAlarmCallback {
      private final AlarmRulesWatcher alarmRulesWatcher;
  
      /**
       * Send alarm message if the settings not empty
       */
      @Override
      public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
          if (alarmRulesWatcher.getFeishuSettings() == null || alarmRulesWatcher.getFeishuSettings().getWebhooks().isEmpty()) {
              return;
          }
          final var feishuSettings = alarmRulesWatcher.getFeishuSettings();
          for (final var webHookUrl : feishuSettings.getWebhooks()) {
              for (final var alarmMessage : alarmMessages) {
                  final var requestBody = getRequestBody(webHookUrl, alarmMessage);
                  post(URI.create(webHookUrl.getUrl()), requestBody, Map.of());
              }
          }
      }
  
      /**
       * deal requestBody,if it has sign set the sign
       */
      private String getRequestBody(FeishuSettings.WebHookUrl webHookUrl, AlarmMessage alarmMessage) {
          final var requestBody = String.format(
                  alarmRulesWatcher.getFeishuSettings().getTextTemplate(), alarmMessage.getAlarmMessage()
          );
          final var gson = new Gson();
          final var jsonObject = gson.fromJson(requestBody, JsonObject.class);
          final var content = buildContent(jsonObject);
          if (!StringUtil.isBlank(webHookUrl.getSecret())) {
              final var timestamp = System.currentTimeMillis() / 1000;
              content.put("timestamp", timestamp);
              try {
                  content.put("sign", sign(timestamp, webHookUrl.getSecret()));
              } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                  throw new RuntimeException(e);
              }
          }
          return gson.toJson(content);
      }
  
      /**
       * build content,if it has ats someone set the ats
       */
      private Map<String, Object> buildContent(JsonObject jsonObject) {
          final var content = new HashMap<String, Object>();
          content.put("msg_type", jsonObject.get("msg_type").getAsString());
          if (jsonObject.get("ats") != null) {
              final var ats = jsonObject.get("ats").getAsString();
              final var collect = Arrays.stream(ats.split(",")).map(String::trim).collect(Collectors.toList());
              var text = jsonObject.get("content").getAsJsonObject().get("text").getAsString();
              for (final var userId : collect) {
                  text += "<at user_id=\"" + userId + "\"></at>";
              }
              jsonObject.get("content").getAsJsonObject().addProperty("text", text);
          }
          content.put("content", jsonObject.get("content").getAsJsonObject());
          return content;
      }
  
      /**
       * Sign webhook url using HmacSHA256 algorithm
       */
      private String sign(final Long timestamp, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
          final var stringToSign = timestamp + "\n" + secret;
          final var mac = Mac.getInstance("HmacSHA256");
          mac.init(new SecretKeySpec(stringToSign.getBytes(), "HmacSHA256"));
          final var signData = mac.doFinal();
          return Base64.getEncoder().encodeToString(signData);
      }
  
  }
  