C# 软件授权最佳实践
面向 C# / .NET 桌面软件(WinForms / WPF) 的授权落地建议。代码片段基于 C# SDK。
一、登录流程(异步)
界面启动即异步登录,避免阻塞 UI 线程:
csharp
public async Task<bool> TryLoginAsync(string card, string mac)
{
var client = new A7Client("150018", "YOUR_APP_KEY");
try
{
var resp = await client.CallAsync("SingleLogin", new Dictionary<string, string>
{
["Card"] = card, ["Mac"] = mac, ["DeviceName"] = Environment.MachineName
});
if (resp.GetProperty("code").GetInt32() == 200)
{
_token = resp.GetProperty("data").GetProperty("token").GetString();
_loginType = "card";
StartHeartbeat(client);
return true;
}
ShowError(resp.GetProperty("message").GetString());
return false;
}
catch (Exception ex)
{
ShowError("网络异常:" + ex.Message);
return false;
}
}二、心跳线程
登录成功后启动一个 Task 每 5 分钟心跳:
csharp
private void StartHeartbeat(A7Client client)
{
_ = Task.Run(async () =>
{
while (_token != null)
{
var hb = await client.CallAsync("UserHeartbeat", new Dictionary<string, string>
{
["Token"] = _token, ["Type"] = _loginType
});
int code = hb.GetProperty("code").GetInt32();
if (code is 1005 or 1006 or 1007)
{
_token = null;
OnSessionExpired();
break;
}
if (hb.GetProperty("data").TryGetProperty("msg", out var m) && m.GetString() is { Length: > 0 } msg)
ShowBulletin(msg);
await Task.Delay(TimeSpan.FromMinutes(5));
}
});
}三、换绑检测
用户换电脑登录时 UserLogin 返回 1022,引导调用 ChangeBind:
csharp
if (code == 1022)
{
var old = resp.GetProperty("data").GetProperty("remark").GetString();
if (MessageBox.Show($"检测到更换设备(原 {old}),是否换绑?", "换绑",
MessageBoxButtons.YesNo) == DialogResult.Yes)
{
await client.CallAsync("ChangeBind", new Dictionary<string, string>
{
["UserName"] = user, ["Password"] = pwd, ["Mac"] = DeviceId.Get()
});
}
}四、AppId / AppKey 安全
不要把 AppKey 明文写进代码
AppKey 只在 Init 阶段使用一次。建议:
- 用资源文件 + 轻量混淆,或
- 登录时由你的下发服务临时提供,避免被逆向直接提取。
ApiPassword 仅存内存(SDK 已如此实现),不要写入 app.config 或注册表。
五、离线宽限
断网时按 离线授权与宽限期 给 30 分钟宽限,期间允许使用,网络恢复后立即补心跳。
六、机器码
参考 设备指纹与硬件绑定 的 C# 实现采集 DeviceId,在 SingleLogin / UserLogin / ChangeBind 中统一传入。