C# SDK 完整示例

以下是完整的 C# 验证客户端实现,无需第三方依赖,使用 .NET 内置库。

完整代码 (AuthClient.cs)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace AuthSDK
{
    public class AuthClient : IDisposable
    {
        private readonly string _appKey;
        private readonly string _appSecret;
        private readonly string _transferKey;
        private readonly string _apiUrl;
        private readonly HttpClient _httpClient;
        
        public string Token { get; private set; } = "";
        public string ExecToken { get; private set; } = "";
        public string Username { get; private set; } = "";
        public string ExpireTime { get; private set; } = "";
        public int Points { get; private set; } = 0;
        public int HeartbeatInterval { get; private set; } = 30;
        public string LastError { get; private set; } = "";
        public int LastCode { get; private set; } = 0;

        public AuthClient(string appKey, string appSecret, string transferKey, string apiUrl)
        {
            _appKey = appKey;
            _appSecret = appSecret;
            _transferKey = transferKey;
            _apiUrl = apiUrl;
            _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        }

        #region 加密算法

        /// <summary>RC4 加密/解密</summary>
        private static byte[] RC4(byte[] data, byte[] key)
        {
            byte[] S = new byte[256];
            for (int i = 0; i < 256; i++) S[i] = (byte)i;

            int j = 0;
            for (int i = 0; i < 256; i++)
            {
                j = (j + S[i] + key[i % key.Length]) % 256;
                (S[i], S[j]) = (S[j], S[i]);
            }

            byte[] result = new byte[data.Length];
            int x = 0, y = 0;
            for (int k = 0; k < data.Length; k++)
            {
                x = (x + 1) % 256;
                y = (y + S[x]) % 256;
                (S[x], S[y]) = (S[y], S[x]);
                result[k] = (byte)(data[k] ^ S[(S[x] + S[y]) % 256]);
            }
            return result;
        }

        /// <summary>HMAC-SHA256 签名</summary>
        private static string HmacSha256(string data, string key)
        {
            using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            return BitConverter.ToString(hash).Replace("-", "").ToLower();
        }

        /// <summary>字节数组转HEX字符串</summary>
        private static string BytesToHex(byte[] bytes)
        {
            return BitConverter.ToString(bytes).Replace("-", "").ToLower();
        }

        /// <summary>HEX字符串转字节数组</summary>
        private static byte[] HexToBytes(string hex)
        {
            byte[] bytes = new byte[hex.Length / 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
            }
            return bytes;
        }

        /// <summary>生成随机字符串</summary>
        private static string GenerateNonce(int length = 16)
        {
            const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var random = new Random();
            return new string(Enumerable.Repeat(chars, length)
                .Select(s => s[random.Next(s.Length)]).ToArray());
        }

        /// <summary>获取Unix时间戳</summary>
        private static long GetTimestamp()
        {
            return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        }

        #endregion

        #region 请求处理

        /// <summary>构建加密请求</summary>
        private string BuildRequest(SortedDictionary<string, string> parameters)
        {
            // 拼接参数
            var data = string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"));
            
            // 计算签名
            var sign = HmacSha256(data, _appSecret);
            data += "&sign=" + sign;
            
            // RC4加密
            var encrypted = RC4(Encoding.UTF8.GetBytes(data), Encoding.UTF8.GetBytes(_transferKey));
            
            return BytesToHex(encrypted);
        }

        /// <summary>解密响应</summary>
        private string DecryptResponse(string response)
        {
            if (string.IsNullOrEmpty(response) || response.StartsWith("{"))
            {
                return response; // 明文响应
            }
            
            // HEX解码并RC4解密
            var bytes = HexToBytes(response);
            var decrypted = RC4(bytes, Encoding.UTF8.GetBytes(_transferKey));
            return Encoding.UTF8.GetString(decrypted);
        }

        /// <summary>发送请求</summary>
        private async Task<JsonDocument?> SendRequestAsync(SortedDictionary<string, string> parameters)
        {
            try
            {
                var encData = BuildRequest(parameters);
                var url = $"{_apiUrl}?app_key={_appKey}&data={encData}";
                
                var response = await _httpClient.GetStringAsync(url);
                var decrypted = DecryptResponse(response);
                
                var json = JsonDocument.Parse(decrypted);
                LastCode = json.RootElement.GetProperty("code").GetInt32();
                LastError = json.RootElement.TryGetProperty("msg", out var msg) ? msg.GetString() ?? "" : "";
                
                return json;
            }
            catch (Exception ex)
            {
                LastError = ex.Message;
                LastCode = -1;
                return null;
            }
        }

        #endregion

        #region API方法

        /// <summary>初始化</summary>
        public async Task<bool> InitAsync(string machineCode, string version = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "init",
                ["machine_code"] = machineCode,
                ["nonce"] = GenerateNonce(),
                ["timestamp"] = GetTimestamp().ToString()
            };
            if (!string.IsNullOrEmpty(version))
                parameters["version"] = version;

            var json = await SendRequestAsync(parameters);
            if (json == null || LastCode != 1) return false;

            var root = json.RootElement;
            HeartbeatInterval = root.TryGetProperty("heartbeat_interval", out var hb) ? hb.GetInt32() : 30;
            if (HeartbeatInterval <= 0) HeartbeatInterval = 30;
            
            return true;
        }

        /// <summary>登录(统一接口)</summary>
        /// <param name="machineCode">机器码</param>
        /// <param name="username">用户名或卡密</param>
        /// <param name="password">密码(卡密登录传"0")</param>
        public async Task<bool> LoginAsync(string machineCode, string username, string password = "0")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "login",
                ["machine_code"] = machineCode,
                ["nonce"] = GenerateNonce(),
                ["password"] = password,
                ["timestamp"] = GetTimestamp().ToString(),
                ["username"] = username
            };

            var json = await SendRequestAsync(parameters);
            if (json == null || LastCode != 2) return false;

            var root = json.RootElement;
            Token = root.GetProperty("token").GetString() ?? "";
            ExecToken = root.TryGetProperty("exec_token", out var et) ? et.GetString() ?? "" : "";
            Username = root.TryGetProperty("username", out var un) ? un.GetString() ?? "" : "";
            ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : "";
            Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : 0;
            
            return true;
        }

        /// <summary>心跳</summary>
        public async Task<bool> HeartbeatAsync(string machineCode = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "heartbeat",
                ["timestamp"] = GetTimestamp().ToString(),
                ["token"] = Token
            };
            if (!string.IsNullOrEmpty(machineCode))
                parameters["machine_code"] = machineCode;

            var json = await SendRequestAsync(parameters);
            if (json == null || LastCode != 3) return false;

            var root = json.RootElement;
            ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : ExpireTime;
            Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
            ExecToken = root.TryGetProperty("exec_token", out var et) ? et.GetString() ?? "" : ExecToken;
            
            return true;
        }

        /// <summary>登出</summary>
        public async Task<bool> LogoutAsync(string machineCode = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "logout",
                ["timestamp"] = GetTimestamp().ToString(),
                ["token"] = Token
            };
            if (!string.IsNullOrEmpty(machineCode))
                parameters["machine_code"] = machineCode;

            await SendRequestAsync(parameters);
            return LastCode == 4;
        }

        /// <summary>扣点</summary>
        public async Task<bool> DeductAsync(int points, string requestId = "", string machineCode = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "deduct",
                ["points"] = points.ToString(),
                ["timestamp"] = GetTimestamp().ToString(),
                ["token"] = Token
            };
            if (!string.IsNullOrEmpty(requestId))
                parameters["request_id"] = requestId;
            if (!string.IsNullOrEmpty(machineCode))
                parameters["machine_code"] = machineCode;

            var json = await SendRequestAsync(parameters);
            if (json == null || LastCode != 8) return false;

            Points = json.RootElement.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
            return true;
        }

        /// <summary>充值</summary>
        public async Task<bool> RechargeAsync(string cardNo, string username = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "recharge",
                ["card_no"] = cardNo,
                ["timestamp"] = GetTimestamp().ToString()
            };
            
            if (!string.IsNullOrEmpty(Token))
                parameters["token"] = Token;
            else if (!string.IsNullOrEmpty(username))
                parameters["username"] = username;

            var json = await SendRequestAsync(parameters);
            if (json == null || LastCode != 5) return false;

            var root = json.RootElement;
            ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : ExpireTime;
            Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
            
            return true;
        }

        /// <summary>验证Token</summary>
        public async Task<bool> VerifyAsync(string machineCode = "")
        {
            var parameters = new SortedDictionary<string, string>
            {
                ["action"] = "verify",
                ["timestamp"] = GetTimestamp().ToString(),
                ["token"] = Token
            };
            if (!string.IsNullOrEmpty(machineCode))
                parameters["machine_code"] = machineCode;

            var json = await SendRequestAsync(parameters);
            return json != null && LastCode == 9;
        }

        #endregion

        public void Dispose()
        {
            _httpClient?.Dispose();
        }
    }
}

使用示例 (Program.cs)

using System;
using System.Management;
using System.Threading;
using System.Threading.Tasks;
using AuthSDK;

class Program
{
    // 获取机器码(示例)
    static string GetMachineCode()
    {
        try
        {
            // 获取CPU ID
            using var mc = new ManagementClass("Win32_Processor");
            foreach (ManagementObject mo in mc.GetInstances())
            {
                return mo["ProcessorId"]?.ToString() ?? "DEFAULT_MACHINE_CODE";
            }
        }
        catch { }
        return "DEFAULT_MACHINE_CODE";
    }

    static async Task Main(string[] args)
    {
        // 配置
        const string APP_KEY = "your_app_key";
        const string APP_SECRET = "your_app_secret";
        const string TRANSFER_KEY = "your_transfer_key";
        const string API_URL = "https://your-server.com/api/v1/client/enc";

        using var client = new AuthClient(APP_KEY, APP_SECRET, TRANSFER_KEY, API_URL);
        var machineCode = GetMachineCode();
        var cts = new CancellationTokenSource();

        // 1. 初始化
        Console.WriteLine("正在初始化...");
        if (!await client.InitAsync(machineCode, "1.0.0"))
        {
            Console.WriteLine($"初始化失败: {client.LastError}");
            return;
        }
        Console.WriteLine($"初始化成功,心跳间隔: {client.HeartbeatInterval}秒");

        // 2. 登录
        Console.Write("请输入卡密: ");
        var cardNo = Console.ReadLine() ?? "";

        Console.WriteLine("正在登录...");
        if (!await client.LoginAsync(machineCode, cardNo, "0"))
        {
            Console.WriteLine($"登录失败: {client.LastError} (code: {client.LastCode})");
            return;
        }

        Console.WriteLine("登录成功!");
        Console.WriteLine($"用户名: {client.Username}");
        Console.WriteLine($"到期时间: {client.ExpireTime}");
        Console.WriteLine($"剩余点数: {client.Points}");

        // 3. 启动心跳线程
        _ = Task.Run(async () =>
        {
            while (!cts.Token.IsCancellationRequested)
            {
                await Task.Delay(client.HeartbeatInterval * 1000, cts.Token);
                if (cts.Token.IsCancellationRequested) break;
                
                if (!await client.HeartbeatAsync(machineCode))
                {
                    Console.WriteLine($"\n心跳失败: {client.LastError}");
                    cts.Cancel();
                }
            }
        }, cts.Token);

        // 4. 主循环
        Console.WriteLine("\n按 q 退出,按 d 扣点...");
        while (!cts.Token.IsCancellationRequested)
        {
            if (Console.KeyAvailable)
            {
                var key = Console.ReadKey(true).KeyChar;
                if (key == 'q' || key == 'Q') break;
                if (key == 'd' || key == 'D')
                {
                    if (await client.DeductAsync(1, Guid.NewGuid().ToString()))
                    {
                        Console.WriteLine($"扣点成功,剩余: {client.Points}");
                    }
                    else
                    {
                        Console.WriteLine($"扣点失败: {client.LastError}");
                    }
                }
            }
            await Task.Delay(100);
        }

        // 5. 退出
        cts.Cancel();
        await client.LogoutAsync(machineCode);
        Console.WriteLine("已退出登录");
    }
}

项目文件 (.csproj)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
此SDK兼容 .NET 6.0+,如需支持 .NET Framework 4.x,请将 System.Text.Json 替换为 Newtonsoft.Json。