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中进行监听。

相关推荐
郝学胜-神的一滴2 小时前
解锁CS数据存储的核心逻辑:从结构选择到表单设计的全解析
linux·服务器·数据库·c++·后端·oracle
孟章豪2 小时前
如何优雅封装.NET数据库访问层(彻底告别拼接SQL)
数据库·sql·.net
geBR OTTE2 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis
2401_840192272 小时前
数据库连接池和java servlet
java·数据库·servlet
honortech2 小时前
docker 配置 MySQL 主从数据库
数据库·mysql·docker
爬山算法2 小时前
MongoDB(75)如何配置TLS/SSL加密?
数据库·mongodb·ssl
不会写DN2 小时前
Git 开发中最常用的命令与场景
大数据·git·elasticsearch
源码之家2 小时前
计算机毕业设计:Python 共享单车数据分析可视化系统 Flask框架 可视化 大数据 机器学习 深度学习 数据挖掘(建议收藏)✅
大数据·python·数据挖掘·数据分析·汽车·课程设计·美食