2026-04-28 10:23:58 +08:00
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
using System.Net.Http;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using YY.Admin.Core.Helper;
|
|
|
|
|
|
using YY.Admin.Core.Session;
|
|
|
|
|
|
using YY.Admin.FluentValidation;
|
2026-05-14 11:25:17 +08:00
|
|
|
|
using YY.Admin.Helper;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Services;
|
|
|
|
|
|
using YY.Admin.Services.Service.Auth;
|
|
|
|
|
|
using YY.Admin.Services.Service.Jeecg;
|
|
|
|
|
|
using YY.Admin.Views;
|
|
|
|
|
|
|
|
|
|
|
|
namespace YY.Admin.ViewModels
|
|
|
|
|
|
{
|
|
|
|
|
|
// ViewModels/LoginViewModel.cs
|
|
|
|
|
|
public class LoginWindowViewModel : BaseViewModel
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ISysAuthService _authService;
|
|
|
|
|
|
private readonly IDialogService _dialogService;
|
|
|
|
|
|
private readonly IJeecgLoginLogReportService _loginLogReportService;
|
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
|
private readonly HttpClient _httpClient = new();
|
|
|
|
|
|
private readonly CancellationTokenSource _connectivityCts = new();
|
|
|
|
|
|
private const int ConnectivityCheckIntervalSeconds = 5;
|
|
|
|
|
|
public LoginInput LoginInput { get; set; }
|
|
|
|
|
|
private string _loginMessage = string.Empty;
|
|
|
|
|
|
public DelegateCommand LoginCommand { get; }
|
|
|
|
|
|
public DelegateCommand SyncJeecgUsersCommand { get; }
|
|
|
|
|
|
public DelegateCommand OpenServerSettingsCommand { get; }
|
|
|
|
|
|
public LoginInputValidator LoginInputValidator { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
private bool _isSyncingJeecgUsers;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 正在从 Jeecg 同步用户到本地库
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool IsSyncingJeecgUsers
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isSyncingJeecgUsers;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetProperty(ref _isSyncingJeecgUsers, value))
|
|
|
|
|
|
{
|
|
|
|
|
|
SyncJeecgUsersCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
LoginCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
RaisePropertyChanged(nameof(SyncJeecgUsersButtonText));
|
|
|
|
|
|
RaisePropertyChanged(nameof(CanInteractWithLogin));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string SyncJeecgUsersButtonText => IsSyncingJeecgUsers ? "同步中..." : "同步 Jeecg 用户";
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>登录与同步互斥,用于禁用登录按钮</summary>
|
|
|
|
|
|
public bool CanInteractWithLogin => !IsLoading && !IsSyncingJeecgUsers;
|
|
|
|
|
|
|
|
|
|
|
|
private bool _isBackendConnected;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 后端连接状态:true=连接中,false=已断开
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool IsBackendConnected
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isBackendConnected;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetProperty(ref _isBackendConnected, value))
|
|
|
|
|
|
{
|
|
|
|
|
|
RaisePropertyChanged(nameof(BackendConnectionStatusText));
|
|
|
|
|
|
RaisePropertyChanged(nameof(BackendConnectionStatusBrush));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string BackendConnectionStatusText => IsBackendConnected ? "后端连接中" : "后端已断开";
|
|
|
|
|
|
public Brush BackendConnectionStatusBrush => IsBackendConnected ? Brushes.LimeGreen : Brushes.Red;
|
|
|
|
|
|
|
|
|
|
|
|
public LoginWindowViewModel(
|
|
|
|
|
|
ISysAuthService authService,
|
|
|
|
|
|
IDialogService dialogService,
|
|
|
|
|
|
IJeecgLoginLogReportService loginLogReportService,
|
|
|
|
|
|
IContainerExtension _container,
|
|
|
|
|
|
IRegionManager regionManager
|
|
|
|
|
|
) : base(_container, regionManager)
|
|
|
|
|
|
{
|
|
|
|
|
|
_authService = authService;
|
|
|
|
|
|
_dialogService = dialogService;
|
|
|
|
|
|
_loginLogReportService = loginLogReportService;
|
|
|
|
|
|
_configuration = _container.Resolve<IConfiguration>();
|
|
|
|
|
|
_loginLogReportService.StartBackgroundSync();
|
|
|
|
|
|
Title = "系统登录";
|
|
|
|
|
|
|
|
|
|
|
|
LoginInput = new LoginInput()
|
|
|
|
|
|
{
|
|
|
|
|
|
Username = "admin",
|
|
|
|
|
|
Password = "123456"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
LoginInputValidator = new LoginInputValidator();
|
|
|
|
|
|
|
|
|
|
|
|
LoginCommand = new DelegateCommand(async () => await LoginAsync(), CanLogin)
|
|
|
|
|
|
.ObservesProperty(() => IsLoading)
|
|
|
|
|
|
.ObservesProperty(() => IsSyncingJeecgUsers);
|
|
|
|
|
|
|
|
|
|
|
|
SyncJeecgUsersCommand = new DelegateCommand(async () => await SyncJeecgUsersAsync(), CanSyncJeecgUsers)
|
|
|
|
|
|
.ObservesProperty(() => IsLoading)
|
|
|
|
|
|
.ObservesProperty(() => IsSyncingJeecgUsers);
|
|
|
|
|
|
|
|
|
|
|
|
OpenServerSettingsCommand = new DelegateCommand(OpenServerSettings);
|
|
|
|
|
|
|
|
|
|
|
|
_ = StartBackendConnectivityLoopAsync(_connectivityCts.Token);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string LoginMessage
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _loginMessage;
|
|
|
|
|
|
set => SetProperty(ref _loginMessage, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string LoginButtonText => IsLoading ? "登录中..." : "登录";
|
|
|
|
|
|
|
|
|
|
|
|
protected override void OnIsLoadingChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
base.OnIsLoadingChanged();
|
|
|
|
|
|
RaisePropertyChanged(nameof(LoginButtonText));
|
|
|
|
|
|
RaisePropertyChanged(nameof(CanInteractWithLogin));
|
|
|
|
|
|
LoginCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
SyncJeecgUsersCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool CanLogin()
|
|
|
|
|
|
{
|
|
|
|
|
|
return !IsLoading && !IsSyncingJeecgUsers;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool CanSyncJeecgUsers()
|
|
|
|
|
|
{
|
|
|
|
|
|
return !IsLoading && !IsSyncingJeecgUsers;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SyncJeecgUsersAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
LoginMessage = string.Empty;
|
|
|
|
|
|
IsSyncingJeecgUsers = true;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await UIHelper.WaitForRenderAsync();
|
|
|
|
|
|
var (success, message) = await _authService.SyncJeecgUsersToLocalFromLoginScreenAsync();
|
|
|
|
|
|
LoginMessage = message;
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.Information("登录页一键同步 Jeecg 用户成功");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.Warning($"登录页一键同步 Jeecg 用户:{message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LoginMessage = $"同步出错:{ex.Message}";
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsSyncingJeecgUsers = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task LoginAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = true;
|
|
|
|
|
|
//LoginMessage = string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 让出线程,让UI先渲染
|
|
|
|
|
|
await UIHelper.WaitForRenderAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var response = await _authService.LoginAsync(LoginInput);
|
|
|
|
|
|
|
|
|
|
|
|
if (response.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, true, "登录成功");
|
|
|
|
|
|
_connectivityCts.Cancel();
|
|
|
|
|
|
//设置会话
|
|
|
|
|
|
AppSession.CurrentUser = response.User;
|
|
|
|
|
|
// 设置用户上下文
|
|
|
|
|
|
SetUserContext(response.User!, response.Token);
|
|
|
|
|
|
// 启动定时器
|
|
|
|
|
|
StartTokenCheckTimer();
|
|
|
|
|
|
// 登录成功,打开主窗口
|
|
|
|
|
|
var loginWindow = Application.Current.MainWindow;
|
|
|
|
|
|
var mainWindow = _container.Resolve<MainWindow>();
|
|
|
|
|
|
Application.Current.MainWindow = mainWindow;
|
|
|
|
|
|
//把窗口和RegionManager 绑定一下即可
|
|
|
|
|
|
SetRegionManager(mainWindow);
|
|
|
|
|
|
mainWindow.Show();
|
|
|
|
|
|
loginWindow.Close();
|
|
|
|
|
|
|
|
|
|
|
|
// 不等待异步更新用户登录信息,不阻塞主窗口打开
|
|
|
|
|
|
_ = _authService.UpdateUserLoginInfoAsync(response.User!);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LoginMessage = response.Message;
|
|
|
|
|
|
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, false, response.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LoginMessage = $"登录出错:{ex.Message}";
|
|
|
|
|
|
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, false, ex.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task StartBackendConnectivityLoopAsync(CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool connected = false;
|
2026-05-14 11:25:17 +08:00
|
|
|
|
var settings = ServerSettingsStore.Load();
|
|
|
|
|
|
if (settings.DisconnectConnection)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
IsBackendConnected = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略窗口关闭后的调度异常
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-04-28 10:23:58 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 每轮都重新读取配置,保存服务器设置后可即时生效
|
|
|
|
|
|
var baseUrl = _configuration.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
|
|
|
|
|
|
var userListPath = _configuration.GetValue<string>("JeecgIntegration:UserListPath") ?? "/sys/user/scada/queryUser";
|
|
|
|
|
|
var probeUrl = string.IsNullOrWhiteSpace(baseUrl)
|
|
|
|
|
|
? string.Empty
|
|
|
|
|
|
: $"{baseUrl}{userListPath}?pageNo=1&pageSize=1&includeDetail=false";
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(probeUrl))
|
|
|
|
|
|
{
|
|
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, probeUrl);
|
|
|
|
|
|
using var resp = await _httpClient.SendAsync(req, cancellationToken);
|
|
|
|
|
|
connected = resp.IsSuccessStatusCode;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
connected = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
IsBackendConnected = connected;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略窗口关闭后的调度异常
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OpenServerSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
_dialogService.ShowDialog("ServerSettingsDialog", r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (r.Result == ButtonResult.OK)
|
|
|
|
|
|
{
|
2026-05-14 11:25:17 +08:00
|
|
|
|
var settings = ServerSettingsStore.Load();
|
|
|
|
|
|
if (settings.DisconnectConnection)
|
|
|
|
|
|
{
|
|
|
|
|
|
IsBackendConnected = false;
|
|
|
|
|
|
}
|
2026-04-28 10:23:58 +08:00
|
|
|
|
LoginMessage = "服务器配置已保存";
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|