节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.84 KiB
Stardust
using NewLife.Data;
using Stardust.Data;
using Stardust.Data.Nodes;
using Stardust.Data.Platform;
using System.Threading;
using XCode;

namespace Stardust.McpClientTests;

/// <summary>MCP测试数据辅助类。创建Token、资源授权、测试数据</summary>
public static class McpTestHelper
{
    /// <summary>WAL模式只需强制一次(避免清理/并发读写时出现 database is locked)。
    /// 服务端 TestServer 使用 Stardust.Web 的连接串,未设 WAL 时读会被写阻塞;
    /// 显式执行 PRAGMA 保证数据库进入 WAL 模式(读不阻塞写、写不阻塞读)。</summary>
    private static Int32 _walReady;

    private static void EnsureWalMode()
    {
        if (Interlocked.Exchange(ref _walReady, 1) == 1) return;
        try
        {
            // 通过实体自带的 DAL 执行 PRAGMA(避免直接依赖 XCode.DAL 命名空间)
            var dal = McpToken.Meta.Session.Dal;
            dal.Execute("PRAGMA journal_mode=WAL;");
            dal.Execute("PRAGMA busy_timeout=30000;");
            Console.WriteLine("✅ MCP测试数据库已强制 WAL 模式(避免 database is locked)");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"⚠ EnsureWalMode 失败:{ex.Message}");
        }
    }

    /// <summary>判断是否为 SQLite 数据库锁定异常(并发读写竞争,可重试)</summary>
    private static Boolean IsDatabaseLocked(Exception ex)
    {
        for (var e = ex; e != null; e = e.InnerException)
            if (e.Message != null && e.Message.Contains("database is locked", StringComparison.OrdinalIgnoreCase))
                return true;
        return false;
    }

    /// <summary>创建测试Token(启用、永不过期)</summary>
    public static (McpToken token, String tokenString) CreateTestToken(String name = "test-token")
    {
        EnsureWalMode();

        // 删除同名旧Token
        var existing = McpToken.Search(null, null, DateTime.MinValue, DateTime.MinValue, name, new PageParameter { PageIndex = 1, PageSize = 100 });
        foreach (var e in existing) e.Delete();

        var token = new McpToken
        {
            Name = name,
            Token = McpToken.GenerateToken(),
            Enable = true,
            ExpireTime = DateTime.MinValue,
        };
        token.Insert();
        return (token, token.Token);
    }

    /// <summary>为Token添加全部项目授权</summary>
    public static void AuthorizeAllProjects(Int32 tokenId)
    {
        EnsureWalMode();
        var r = new McpTokenResource
        {
            TokenId = tokenId,
            ResourceType = "Project",
            IsAll = true,
            Enable = true,
        };
        r.Insert();
    }

    /// <summary>为Token添加全部节点授权</summary>
    public static void AuthorizeAllNodes(Int32 tokenId)
    {
        EnsureWalMode();
        var r = new McpTokenResource
        {
            TokenId = tokenId,
            ResourceType = "Node",
            IsAll = true,
            Enable = true,
        };
        r.Insert();
    }

    /// <summary>为Token添加全部应用授权</summary>
    public static void AuthorizeAllApps(Int32 tokenId)
    {
        EnsureWalMode();
        var r = new McpTokenResource
        {
            TokenId = tokenId,
            ResourceType = "App",
            IsAll = true,
            Enable = true,
        };
        r.Insert();
    }

    /// <summary>为Token授权特定项目</summary>
    public static void AuthorizeProject(Int32 tokenId, Int32 projectId)
    {
        EnsureWalMode();
        var r = new McpTokenResource
        {
            TokenId = tokenId,
            ResourceType = "Project",
            ResourceId = projectId,
            IsAll = false,
            Enable = true,
        };
        r.Insert();
    }

    /// <summary>清理Token及其授权。
    /// 清理失败(如并发 database is locked)不应掩盖测试断言结果,故对锁定异常做有限重试,
    /// 重试耗尽仅记录告警并跳过,不抛异常。残留数据由下次 CreateTestToken 按名称清理。</summary>
    public static void CleanupToken(Int32 tokenId)
    {
        const Int32 maxRetry = 25;
        for (var i = 0; i < maxRetry; i++)
        {
            try
            {
                var resources = McpTokenResource.FindAllByToken(tokenId);
                foreach (var r in resources) r.Delete();
                var token = McpToken.FindById(tokenId);
                token?.Delete();
                return;
            }
            catch (Exception ex) when (IsDatabaseLocked(ex))
            {
                if (i == maxRetry - 1)
                {
                    Console.WriteLine($"⚠ CleanupToken 跳过(数据库锁定,已重试{maxRetry}次):{ex.Message}");
                    return;
                }
                Thread.Sleep(150);
            }
        }
    }
}