feat.在线安装器,更好的Issue与pull request模板。

This commit is contained in:
lincube
2026-06-03 00:50:52 +08:00
parent 29bd47986c
commit 28b06031f7
38 changed files with 2976 additions and 123 deletions

View File

@@ -0,0 +1,6 @@
namespace LanMountainDesktop.Shared.Contracts.Privacy;
public interface IPrivacyDeviceIdentityProvider
{
string GetOrCreateDeviceId();
}

View File

@@ -0,0 +1,105 @@
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace LanMountainDesktop.Shared.Contracts.Privacy;
public sealed partial class PrivacyDeviceIdentityProvider : IPrivacyDeviceIdentityProvider
{
public const string DefaultIdentityFileName = "privacy-device.identity.json";
private readonly string _identityPath;
private readonly object _gate = new();
public PrivacyDeviceIdentityProvider(string? identityPath = null)
{
_identityPath = string.IsNullOrWhiteSpace(identityPath)
? GetDefaultIdentityPath()
: Path.GetFullPath(identityPath);
}
public string GetOrCreateDeviceId()
{
lock (_gate)
{
var existing = TryLoad();
if (!string.IsNullOrWhiteSpace(existing?.DeviceId))
{
return existing.DeviceId;
}
var created = new PrivacyDeviceIdentityDocument(
SchemaVersion: 1,
DeviceId: GenerateDeviceId(),
CreatedAtUtc: DateTimeOffset.UtcNow);
Save(created);
return created.DeviceId;
}
}
public static string GetDefaultIdentityPath()
{
var root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (string.IsNullOrWhiteSpace(root))
{
root = AppContext.BaseDirectory;
}
return Path.Combine(root, "LanMountainDesktop", DefaultIdentityFileName);
}
private PrivacyDeviceIdentityDocument? TryLoad()
{
try
{
if (!File.Exists(_identityPath))
{
return null;
}
var json = File.ReadAllText(_identityPath);
return JsonSerializer.Deserialize(
json,
PrivacyDeviceIdentityJsonContext.Default.PrivacyDeviceIdentityDocument);
}
catch
{
return null;
}
}
private void Save(PrivacyDeviceIdentityDocument document)
{
var directory = Path.GetDirectoryName(_identityPath);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(directory);
}
var tempPath = $"{_identityPath}.{Guid.NewGuid():N}.tmp";
var json = JsonSerializer.Serialize(
document,
PrivacyDeviceIdentityJsonContext.Default.PrivacyDeviceIdentityDocument);
File.WriteAllText(tempPath, json);
File.Move(tempPath, _identityPath, overwrite: true);
}
private static string GenerateDeviceId()
{
Span<byte> bytes = stackalloc byte[32];
RandomNumberGenerator.Fill(bytes);
return Convert.ToHexString(bytes).ToLowerInvariant();
}
private sealed record PrivacyDeviceIdentityDocument(
int SchemaVersion,
string DeviceId,
DateTimeOffset CreatedAtUtc);
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(PrivacyDeviceIdentityDocument))]
private sealed partial class PrivacyDeviceIdentityJsonContext : JsonSerializerContext;
}