TDengine C# 连接示例和授权管理
关于TDengine 其实时序性服务器,安装TDengin.Connector。这个里面有一些服务的处理。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MqttServerApi.Models;
using TDengine.Driver;
using TDengine.Driver.Client;
namespace MqttServerApi.Services
{
public class TDengineService : ITDengineService, IDisposable
{
private readonly ILogger<TDengineService> _logger;
private readonly string _host;
private readonly int _port;
private readonly string _username;
private readonly string _password;
private readonly string _database;
private readonly bool _useSSL;
private object _client;
private bool _disposed;
private readonly object _lock = new object();
public TDengineService(IConfiguration configuration, ILogger<TDengineService> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_host = configuration["TDengine:Host"] ?? "localhost";
var portStr = configuration["TDengine:Port"] ?? "6041";
_port = int.TryParse(portStr, out var port) ? port : 6041;
_username = configuration["TDengine:Username"] ?? "root";
_password = configuration["TDengine:Password"] ?? "taosdata";
_database = configuration["TDengine:Database"] ?? "edge_data";
_useSSL = bool.TryParse(configuration["TDengine:UseSSL"], out var ssl) && ssl;
_logger.LogInformation("TDengineService initialized - Host: {Host}, Port: {Port}, Database: {Database}",
_host, _port, _database);
}
private string BuildConnectionString()
{
var connStr = new StringBuilder();
connStr.Append($"protocol=WebSocket;host={_host};port={_port};useSSL={_useSSL.ToString().ToLower()};database={_database}");
connStr.Append($";username={_username};password={_password}");
return connStr.ToString();
}
private object GetClient()
{
lock (_lock)
{
if (_client != null)
{
return _client;
}
try
{
var connectionString = BuildConnectionString();
_logger.LogDebug("Connecting to TDengine with: {ConnectionString}", connectionString.Replace(_password, "***"));
var builder = new ConnectionStringBuilder(connectionString);
_client = DbDriver.Open(builder);
if (_client == null)
{
throw new TDengineException("Failed to connect to TDengine: client is null");
}
_logger.LogInformation("Successfully connected to TDengine");
return _client;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error connecting to TDengine");
throw new TDengineException("Failed to connect to TDengine", ex);
}
}
}
public async Task<bool> TestConnectionAsync()
{
return await Task.Run(() =>
{
try
{
var client = GetClient();
((dynamic)client).Exec("SHOW DATABASES");
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to test TDengine connection");
return false;
}
});
}
private string GetStringValue(object value)
{
if (value == null)
{
return null;
}
if (value is byte[] byteArray)
{
return Encoding.UTF8.GetString(byteArray);
}
return value.ToString();
}
private bool DatabaseExists(object client, string database)
{
try
{
using (var rows = ((dynamic)client).Query("SHOW DATABASES"))
{
while (rows.Read())
{
var dbName = GetStringValue(rows.GetValue(0));
if (dbName != null && dbName.Equals(database, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking database existence");
return false;
}
}
public async Task<TableStructureResponse> GetTableStructureAsync(string database)
{
if (string.IsNullOrWhiteSpace(database))
{
throw new ArgumentException("Database name cannot be null or empty", nameof(database));
}
_logger.LogInformation("Getting table structure for database: {Database}", database);
return await Task.Run(() =>
{
var response = new TableStructureResponse();
try
{
var client = GetClient();
// 切换到指定数据库
((dynamic)client).Exec($"USE `{database}`");
var superTables = GetSuperTables(client);
foreach (var superTable in superTables)
{
var superTableInfo = BuildSuperTableInfo(client, superTable);
response.SuperTables.Add(superTableInfo);
}
_logger.LogInformation("Successfully retrieved table structure. Found {Count} tables", response.SuperTables.Count);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting table structure for database: {Database}", database);
throw;
}
});
}
private List<string> GetSuperTables(object client)
{
var superTables = new List<string>();
try
{
using (var rows = ((dynamic)client).Query("SHOW STABLES"))
{
while (rows.Read())
{
var tableName = GetStringValue(rows.GetValue(0));
if (!string.IsNullOrEmpty(tableName))
{
superTables.Add(tableName);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting super tables");
}
return superTables;
}
private SuperTableInfo BuildSuperTableInfo(object client, string superTableName)
{
var superTableInfo = new SuperTableInfo
{
TableName = superTableName,
TableType = "SUPER_TABLE",
Columns = new List<ColumnInfo>(),
Tags = new List<ColumnInfo>(),
ChildTables = new List<ChildTableInfo>()
};
try
{
var childTables = GetChildTables(client, superTableName);
foreach (var childTable in childTables)
{
var childTableInfo = new ChildTableInfo
{
TableName = childTable,
SuperTableName = superTableName,
TagValues = new Dictionary<string, object>()
};
superTableInfo.ChildTables.Add(childTableInfo);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error building super table info for: {TableName}", superTableName);
}
return superTableInfo;
}
private List<string> GetChildTables(object client, string superTableName)
{
var childTables = new List<string>();
try
{
using (var rows = ((dynamic)client).Query($"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.INS_TABLES WHERE STABLE_NAME = '{superTableName}'"))
{
while (rows.Read())
{
var tableName = GetStringValue(rows.GetValue(0));
if (!string.IsNullOrEmpty(tableName))
{
childTables.Add(tableName);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting child tables for: {SuperTableName}", superTableName);
}
return childTables;
}
private List<string> GetStandaloneTables(object client, List<string> superTables)
{
var tables = new List<string>();
try
{
using (var rows = ((dynamic)client).Query("SHOW TABLES"))
{
while (rows.Read())
{
var tableName = GetStringValue(rows.GetValue(0));
string superTable = null;
if (rows.FieldCount > 3)
{
superTable = GetStringValue(rows.GetValue(3));
}
if (string.IsNullOrEmpty(superTable) && !superTables.Contains(tableName))
{
tables.Add(tableName);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting standalone tables");
}
return tables;
}
public async Task<List<Dictionary<string, object>>> QueryDataAsync(string tableName, DateTime startTime, DateTime endTime, string database = "edge_data", int limit = 1000)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentException("Table name cannot be null or empty", nameof(tableName));
}
if (limit <= 0)
{
limit = 1000; // 默认值
}
//_logger.LogInformation("Querying data for table: {TableName}, from {StartTime} to {EndTime}, limit: {Limit}, offset: {Offset}", tableName, startTime, endTime, limit);
return await Task.Run(() =>
{
var result = new List<Dictionary<string, object>>();
try
{
var client = GetClient();
// 切换到指定数据库
((dynamic)client).Exec($"USE `{database}`");
// 构建查询SQL
string sql = $"SELECT * FROM `{tableName}` WHERE ts >= '{startTime:yyyy-MM-dd HH:mm:ss.fff}' AND ts <= '{endTime:yyyy-MM-dd HH:mm:ss.fff}' ORDER BY ts LIMIT {limit}";
_logger.LogDebug("Executing query: {Sql}", sql);
using (var rows = ((dynamic)client).Query(sql))
{
while (rows.Read())
{
var rowData = new Dictionary<string, object>();
// 获取列数
int fieldCount = rows.FieldCount;
for (int i = 0; i < fieldCount; i++)
{
// 获取列名和值
string columnName = GetStringValue(rows.GetName(i));
object value = rows.GetValue(i);
// 处理字节数组
if (value is byte[] byteArray)
{
rowData[columnName] = Encoding.UTF8.GetString(byteArray);
}
else
{
rowData[columnName] = value;
}
}
result.Add(rowData);
}
}
_logger.LogInformation("Successfully retrieved {Count} records from table: {TableName}", result.Count, tableName);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error querying data for table: {TableName}", tableName);
throw;
}
});
}
public void Dispose()
{
if (!_disposed)
{
if (_client != null)
{
try
{
((dynamic)_client).Close();
_logger.LogInformation("TDengine connection closed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error closing TDengine connection");
}
_client = null;
}
_disposed = true;
}
}
}
public class TDengineException : Exception
{
public TDengineException(string message) : base(message) { }
public TDengineException(string message, Exception innerException) : base(message, innerException) { }
}
}
还有一些服务器授权的代码。
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Cors; using System.IO; using System.Text.Json; using System.Threading.Tasks; using System.Text.Json.Serialization; using System.Runtime.InteropServices; using Microsoft.Win32; using System.Security.Cryptography; using System.Text; using System; namespace MqttServerApi.Controllers { [ApiController] [Route("api/[controller]")] [EnableCors("OpenCors")] public class AuthorizationController : ControllerBase { private const int AuthCodeLength = 16; private readonly string _authFilePath = "auth.json"; private readonly string _authLogPath = "auth.log"; /// <summary> /// 获取设备码,用于用户向公司申请授权码 /// </summary> [HttpGet("machine-code")] public IActionResult GetMachineCodeApi() { try { var machineCode = GetMachineCode(); if (string.IsNullOrEmpty(machineCode)) { return StatusCode(500, new { success = false, message = "无法获取机器码" }); } return Ok(new { success = true, machineCode = machineCode }); } catch (System.Exception ex) { return StatusCode(500, new { success = false, message = "获取机器码过程发生错误", error = ex.Message }); } } /// <summary> /// 获取授权状态 /// </summary> /// <returns> /// 授权状态信息 /// 未授权时返回: {"success": true, "status": null} /// 已授权时返回: {"success": true, "status": {"authCode": "授权码", "isAuth": true}} /// </returns> [HttpGet("status")] public async Task<IActionResult> GetAuthorizationStatus() { try { var authData = await ReadAuthFileAsync(); if (authData == null || !authData.IsAuth) { return Ok(new { success = true, status = (object)null }); } return Ok(new { success = true, status = authData }); } catch (System.Exception ex) { return StatusCode(500, new { success = false, message = "获取授权状态过程发生错误", error = ex.Message }); } } /// <summary> /// 用戶在網頁提交授權碼,API 用本機機器碼經 AES 加密後取 16 位比對,無論成功與否都寫入 auth.json,並將用戶碼與正確碼寫入日誌。 /// </summary> [HttpPost] public async Task<IActionResult> SubmitAuthorization([FromBody] AuthorizationRequest request) { try { var userSubmittedCode = request?.Code?.Trim() ?? string.Empty; var machineCode = GetMachineCode(); if (string.IsNullOrEmpty(machineCode)) { await SaveUserAuthToFileAsync(userSubmittedCode, false); WriteAuthLog(userSubmittedCode, "", false); return StatusCode(500, new { success = false, message = "无法获取机器码" }); } var correctCode = EncryptMachineCode(machineCode); // 已改為 16 位 bool isAuth = userSubmittedCode.Length == AuthCodeLength && userSubmittedCode == correctCode; await SaveUserAuthToFileAsync(userSubmittedCode, isAuth); WriteAuthLog(userSubmittedCode, correctCode, isAuth); if (isAuth) return Ok(new { success = true, message = "授权验证成功" }); return Ok(new { success = false, message = "授权码不匹配,验证失败,请重新录入" }); } catch (System.Exception ex) { var userCode = request?.Code?.Trim() ?? string.Empty; try { await SaveUserAuthToFileAsync(userCode, false); } catch { } try { WriteAuthLog(userCode, "", false); } catch { } return StatusCode(500, new { success = false, message = "验证过程发生错误", error = ex.Message }); } } /// <summary> /// 將用戶提交的授權碼與正確授權碼寫入日誌文件。 /// </summary> private void WriteAuthLog(string userSubmittedCode, string correctCode, bool isAuth) { try { var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | UserCode: {userSubmittedCode} | CorrectCode: {correctCode} | Result: {(isAuth ? "Success" : "Fail")}{Environment.NewLine}"; System.IO.File.AppendAllText(_authLogPath, line); } catch { /* 日誌寫入失敗不影響主流程 */ } } /// <summary> /// 將用戶提交的授權碼寫入 auth.json(僅用於記錄,不存明文機器碼)。 /// </summary> private async Task SaveUserAuthToFileAsync(string userSubmittedAuthCode, bool isAuth) { var authData = new AuthorizationFileData { AuthCode = userSubmittedAuthCode, IsAuth = isAuth }; await WriteAuthFileAsync(authData); } private async Task WriteAuthFileAsync(AuthorizationFileData authData) { var json = JsonSerializer.Serialize(authData, new JsonSerializerOptions { WriteIndented = true }); await System.IO.File.WriteAllTextAsync(_authFilePath, json); } private async Task<AuthorizationFileData> ReadAuthFileAsync() { if (!System.IO.File.Exists(_authFilePath)) { return null; } try { var json = await System.IO.File.ReadAllTextAsync(_authFilePath); return JsonSerializer.Deserialize<AuthorizationFileData>(json); } catch { return null; } } /// <summary> /// 使用 AES 加密機器碼,返回前 16 位作為授權碼(仍為 Base64 字元集,便於比對與顯示)。 /// </summary> private string EncryptMachineCode(string machineCode) { using var aes = CreateAesFromPassword("xwdsoft@2019"); using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); var plainBytes = Encoding.UTF8.GetBytes(machineCode); var cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); var fullBase64 = System.Convert.ToBase64String(cipherBytes); return fullBase64.Length >= AuthCodeLength ? fullBase64.Substring(0, AuthCodeLength) : fullBase64; } /// <summary> /// 使用 AES 解密(僅適用完整密文 Base64,不適用 16 位授權碼;16 位為截斷結果無法還原)。 /// </summary> private string DecryptMachineCode(string encryptedMachineCode) { using var aes = CreateAesFromPassword("xwdsoft@2019"); using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); var cipherBytes = System.Convert.FromBase64String(encryptedMachineCode); var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); return Encoding.UTF8.GetString(plainBytes); } private Aes CreateAesFromPassword(string password) { // 使用 SHA256(password) 作為 32 位元組金鑰,前 16 位元組作為 IV using var sha256 = SHA256.Create(); var pwdBytes = Encoding.UTF8.GetBytes(password); var hash = sha256.ComputeHash(pwdBytes); // 32 bytes var key = hash; // 32 bytes var iv = new byte[16]; System.Array.Copy(hash, iv, 16); var aes = Aes.Create(); aes.Key = key; aes.IV = iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; return aes; } private string GetMachineCode() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return GetWindowsMachineCode(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return GetLinuxMachineCode(); } return System.Environment.MachineName; } private string GetWindowsMachineCode() { try { using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography"); var machineGuid = key?.GetValue("MachineGuid")?.ToString(); if (!string.IsNullOrEmpty(machineGuid)) { return machineGuid; } } catch { // ignore and fallback } return System.Environment.MachineName; } private string GetLinuxMachineCode() { try { const string machineIdPath1 = "/etc/machine-id"; const string machineIdPath2 = "/var/lib/dbus/machine-id"; if (System.IO.File.Exists(machineIdPath1)) { return System.IO.File.ReadAllText(machineIdPath1).Trim(); } if (System.IO.File.Exists(machineIdPath2)) { return System.IO.File.ReadAllText(machineIdPath2).Trim(); } } catch { // ignore and fallback } return System.Environment.MachineName; } } public class AuthorizationRequest { public string Code { get; set; } public bool Authorized { get; set; } } public class AuthorizationFileData { [JsonPropertyName("authCode")] public string AuthCode { get; set; } [JsonPropertyName("isAuth")] public bool IsAuth { get; set; } } }
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using MqttServerApi.Services; namespace MqttServerApi.Controllers { [ApiController] [Route("api/[controller]")] [EnableCors("OpenCors")] public class DataManagementController : ControllerBase { private readonly ITDengineService _tdengineService; private readonly ILogger<DataManagementController> _logger; public DataManagementController( ITDengineService tdengineService, ILogger<DataManagementController> logger) { _tdengineService = tdengineService ?? throw new ArgumentNullException(nameof(tdengineService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } [HttpGet("table-structure")] public async Task<IActionResult> GetTableStructure([FromQuery] string database = "edge_data") { if (string.IsNullOrWhiteSpace(database)) { _logger.LogWarning("GetTableStructure called with empty database name"); return BadRequest(new { success = false, message = "数据库名称不能为空" }); } if (!IsValidDatabaseName(database)) { _logger.LogWarning("GetTableStructure called with invalid database name: {Database}", database); return BadRequest(new { success = false, message = "数据库名称包含无效字符" }); } try { _logger.LogInformation("Getting table structure for database: {Database}", database); var result = await _tdengineService.GetTableStructureAsync(database); if (result == null || result.SuperTables == null || result.SuperTables.Count == 0) { _logger.LogInformation("No tables found in database: {Database}", database); return NotFound(new { success = false, message = $"数据库 '{database}' 中未找到任何表", data = (object)null }); } _logger.LogInformation("Successfully retrieved {Count} tables from database: {Database}", result.SuperTables.Count, database); return Ok(new { success = true, message = "获取表结构成功", data = result }); } catch (TDengineException ex) { _logger.LogError(ex, "TDengine error while getting table structure for database: {Database}", database); return StatusCode(503, new { success = false, message = "数据库连接失败,请检查TDengine服务是否正常运行", error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error while getting table structure for database: {Database}", database); return StatusCode(500, new { success = false, message = "获取表结构时发生内部错误", error = ex.Message }); } } [HttpGet("test-connection")] public async Task<IActionResult> TestConnection() { try { _logger.LogInformation("Testing TDengine connection"); var isConnected = await _tdengineService.TestConnectionAsync(); if (isConnected) { _logger.LogInformation("TDengine connection test successful"); return Ok(new { success = true, message = "数据库连接成功" }); } _logger.LogWarning("TDengine connection test failed"); return StatusCode(503, new { success = false, message = "数据库连接失败" }); } catch (Exception ex) { _logger.LogError(ex, "Error testing TDengine connection"); return StatusCode(500, new { success = false, message = "测试连接时发生错误", error = ex.Message }); } } private bool IsValidDatabaseName(string database) { if (string.IsNullOrWhiteSpace(database)) { return false; } foreach (var c in database) { if (!char.IsLetterOrDigit(c) && c != '_') { return false; } } return true; } [HttpGet("query-data")] public async Task<IActionResult> QueryData( [FromQuery] string tableName, [FromQuery] DateTime startTime, [FromQuery] DateTime endTime, [FromQuery] string database = "edge_data", [FromQuery] int limit = 1000) { if (string.IsNullOrWhiteSpace(tableName)) { _logger.LogWarning("QueryData called with empty table name"); return BadRequest(new { success = false, message = "表名不能为空" }); } if (string.IsNullOrWhiteSpace(database)) { _logger.LogWarning("QueryData called with empty database name"); return BadRequest(new { success = false, message = "数据库名称不能为空" }); } if (!IsValidDatabaseName(database)) { _logger.LogWarning("QueryData called with invalid database name: {Database}", database); return BadRequest(new { success = false, message = "数据库名称包含无效字符" }); } if (startTime >= endTime) { _logger.LogWarning("QueryData called with startTime >= endTime"); return BadRequest(new { success = false, message = "开始时间必须小于结束时间" }); } if (limit <= 0) { limit = 1000; // 默认值 } try { _logger.LogInformation("Querying data for table: {TableName}, database: {Database}, from {StartTime} to {EndTime}, limit: {Limit}", tableName, database, startTime, endTime, limit); var result = await _tdengineService.QueryDataAsync(tableName, startTime, endTime, database, limit); _logger.LogInformation("Successfully retrieved {Count} records from table: {TableName}", result.Count, tableName); return Ok(new { success = true, message = "查询数据成功", data = result, total = result.Count, limit = limit }); } catch (TDengineException ex) { _logger.LogError(ex, "TDengine error while querying data for table: {TableName}", tableName); return StatusCode(503, new { success = false, message = "数据库连接失败,请检查TDengine服务是否正常运行", error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error while querying data for table: {TableName}", tableName); return StatusCode(500, new { success = false, message = "查询数据时发生内部错误", error = ex.Message }); } } } }
最后需要你写个配置。用于监听服务监听。auth.json
{
"authCode": "string",
"isAuth": false
}
#!/bin/bash
JSON_FILE="/home/mqttauth/auth.json"
PROCESS_NAME="XWDTechnology.EdgeServer"
EXEC_CMD="sudo /home/linux64/XWDTechnology.EdgeServer"
STATE_FILE="/tmp/hsl_edge_server_state"
LOG_FILE="/tmp/monitor_debug.log"
# 服务名(用于 systemctl)
SERVICE_NAME="mqttserverapi.service"
LED_PATH="/sys/class/leds/led1/brightness"
# EdgeServer HTTP 关闭接口配置
EDGE_SERVER_IP="192.168.0.15"
EDGE_SERVER_PORT="522"
EDGE_SERVER_USER="admin"
EDGE_SERVER_PASS="123456"
# 新接口:关闭并重启,用户名密码同时放在 URL 参数和 Basic 认证中
EDGE_SERVER_CLOSE_URL="http://${EDGE_SERVER_IP}:${EDGE_SERVER_PORT}/Admin/ServerClose?name=${EDGE_SERVER_USER}&password=${EDGE_SERVER_PASS}"
echo "$(date): 监控脚本启动(仅HTTP关闭,禁用pkill)" >> "$LOG_FILE"
# 检查必要工具
if ! command -v jq &>/dev/null; then
echo "$(date): 警告: jq 未安装,将使用 grep 备用解析" >> "$LOG_FILE"
fi
if ! command -v curl &>/dev/null; then
echo "$(date): 错误: curl 未安装,无法发送 HTTP 关闭请求,脚本退出" >> "$LOG_FILE"
exit 1
fi
# 读取上次 isAuth 状态
if [ -f "$STATE_FILE" ]; then
last_isAuth=$(cat "$STATE_FILE")
else
last_isAuth=""
fi
echo "$(date): 初始 last_isAuth = $last_isAuth" >> "$LOG_FILE"
# --- 初始化 LED 状态(根据 systemd 服务状态)---
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
sudo sh -c "echo 1 > $LED_PATH" 2>>"$LOG_FILE"
last_service_active=true
echo "$(date): 服务 $SERVICE_NAME 为 active,LED 设置为 1" >> "$LOG_FILE"
else
sudo sh -c "echo 0 > $LED_PATH" 2>>"$LOG_FILE"
last_service_active=false
echo "$(date): 服务 $SERVICE_NAME 未运行,LED 设置为 0" >> "$LOG_FILE"
fi
# 函数:通过 HTTP 请求关闭/重启 EdgeServer(新接口)
stop_edge_server_via_http() {
echo "$(date): 尝试通过 HTTP 请求关闭 EdgeServer(新接口)" >> "$LOG_FILE"
# 发送 POST 请求,同时使用 Basic 认证和 URL 参数,无请求体
response=$(curl --location --request POST "$EDGE_SERVER_CLOSE_URL" \
--user "${EDGE_SERVER_USER}:${EDGE_SERVER_PASS}" \
--header "User-Agent: Apifox/1.0.0 (https://apifox.com)" \
--header "Accept: */*" \
--header "Host: ${EDGE_SERVER_IP}:${EDGE_SERVER_PORT}" \
--header "Connection: keep-alive" \
--header "Content-Length: 0" \
-s -w "\n%{http_code}")
curl_exit=$?
http_code=$(echo "$response" | tail -n1)
content=$(echo "$response" | sed '$d') # 去除最后一行状态码,得到响应内容
if [ $curl_exit -eq 0 ] && [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "$(date): HTTP 请求成功,状态码 $http_code,响应内容: $content" >> "$LOG_FILE"
sleep 3
else
echo "$(date): HTTP 请求失败 (curl 退出码 $curl_exit, HTTP 状态 $http_code)" >> "$LOG_FILE"
fi
}
# 函数:启动 EdgeServer
start_edge_server() {
echo "$(date): 启动 EdgeServer" >> "$LOG_FILE"
nohup $EXEC_CMD > /dev/null 2>&1 &
sleep 2
if pgrep -f "$PROCESS_NAME" > /dev/null; then
echo "$(date): EdgeServer 启动成功" >> "$LOG_FILE"
else
echo "$(date): EdgeServer 启动失败" >> "$LOG_FILE"
fi
}
# 主循环
while true; do
if [ ! -f "$JSON_FILE" ]; then
echo "$(date): 错误: 文件 $JSON_FILE 不存在,退出监控" >> "$LOG_FILE"
exit 1
fi
# ---------- 解析 isAuth ----------
current_isAuth=""
if command -v jq &>/dev/null; then
current_isAuth=$(jq -r '.isAuth' "$JSON_FILE" 2>>"$LOG_FILE")
if [ $? -ne 0 ] || [ "$current_isAuth" != "true" -a "$current_isAuth" != "false" ]; then
echo "$(date): jq 解析失败或值无效: $current_isAuth" >> "$LOG_FILE"
current_isAuth=""
fi
fi
if [ -z "$current_isAuth" ]; then
# 备用 grep
if grep -q '"isAuth": *true' "$JSON_FILE"; then
current_isAuth="true"
elif grep -q '"isAuth": *false' "$JSON_FILE"; then
current_isAuth="false"
else
echo "$(date): 错误: 无法确定 isAuth 的值,跳过本次检查" >> "$LOG_FILE"
fi
if [ -n "$current_isAuth" ]; then
echo "$(date): 使用 grep 解析到 current_isAuth = $current_isAuth" >> "$LOG_FILE"
fi
else
echo "$(date): 使用 jq 解析到 current_isAuth = $current_isAuth" >> "$LOG_FILE"
fi
# 如果解析失败,跳过本轮处理
if [ -z "$current_isAuth" ]; then
sleep 10
continue
fi
# ---------- 处理 isAuth 变化 ----------
if [ "$current_isAuth" != "$last_isAuth" ]; then
echo "$(date): 检测到 isAuth 从 $last_isAuth 变为 $current_isAuth" >> "$LOG_FILE"
if [ "$current_isAuth" = "true" ]; then
# false -> true:先尝试关闭旧进程(如有),再启动
if pgrep -f "$PROCESS_NAME" > /dev/null; then
stop_edge_server_via_http
fi
start_edge_server
else
# true -> false:尝试关闭进程
if pgrep -f "$PROCESS_NAME" > /dev/null; then
stop_edge_server_via_http
fi
fi
# 更新状态文件
echo "$current_isAuth" > "$STATE_FILE"
last_isAuth="$current_isAuth"
else
echo "$(date): isAuth 无变化 ($current_isAuth),跳过变化处理" >> "$LOG_FILE"
fi
# ---------- 确保进程状态与 isAuth 一致(崩溃恢复/残留清理)----------
if [ "$current_isAuth" = "true" ]; then
if ! pgrep -f "$PROCESS_NAME" > /dev/null; then
echo "$(date): isAuth 为 true 但进程不存在,启动 EdgeServer" >> "$LOG_FILE"
start_edge_server
fi
else # current_isAuth = false
if pgrep -f "$PROCESS_NAME" > /dev/null; then
echo "$(date): isAuth 为 false 但进程仍在运行,尝试通过 HTTP 关闭" >> "$LOG_FILE"
stop_edge_server_via_http
fi
fi
# ---------- 检查 systemd 服务状态并控制 LED ----------
current_service_active=false
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
current_service_active=true
fi
if [ "$current_service_active" != "$last_service_active" ]; then
if [ "$current_service_active" = true ]; then
sudo sh -c "echo 1 > $LED_PATH" 2>>"$LOG_FILE"
echo "$(date): 服务 $SERVICE_NAME 变为 active,LED 设置为 1" >> "$LOG_FILE"
else
sudo sh -c "echo 0 > $LED_PATH" 2>>"$LOG_FILE"
echo "$(date): 服务 $SERVICE_NAME 变为 inactive,LED 设置为 0" >> "$LOG_FILE"
fi
last_service_active=$current_service_active
else
echo "$(date): 服务状态无变化 (active=$current_service_active),LED 不变" >> "$LOG_FILE"
fi
sleep 10
done
最后这个脚本在linux中进行监听。

浙公网安备 33010602011771号