Add MySQL-backed HRMS repositories and DB integration configuration.
1) Introduces DB connectivity, repository abstractions, and new config flags/env overrides. 2) Keeps architecture clean and isolated.main
parent
fbd4d1d313
commit
93b2bf9bd7
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
namespace HikvisionAttendanceService.Data;
|
||||
|
||||
internal sealed class AttendanceMachineRow
|
||||
{
|
||||
public string MachineId { get; set; } = "";
|
||||
public string MachineIp { get; set; } = "";
|
||||
public int PortNumber { get; set; } = 8000;
|
||||
public string MachineName { get; set; } = "";
|
||||
public int? SiteId { get; set; }
|
||||
public string MachineStatus { get; set; } = "";
|
||||
public string MachineType { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
public DateTime? LastSyncDate { get; set; }
|
||||
public int TotalUsers { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class AttendanceMachineUserRow
|
||||
{
|
||||
public string MachineId { get; set; } = "";
|
||||
public string SerialNumber { get; set; } = "";
|
||||
public string EmployeeName { get; set; } = "";
|
||||
public bool IsDeleted { get; set; }
|
||||
public bool IsDeletionRequested { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class AttendanceMachineFaceTemplateRow
|
||||
{
|
||||
public string SerialNo { get; set; } = "";
|
||||
public byte[] Template { get; set; } = Array.Empty<byte>();
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HikvisionAttendanceService.Data;
|
||||
|
||||
internal interface IHrmsDbConnectionFactory
|
||||
{
|
||||
string BuildConnectionStringMasked();
|
||||
bool TryBuildConnectionString(out string connectionString, out string error);
|
||||
}
|
||||
|
||||
internal interface IAttendanceMachineRepository
|
||||
{
|
||||
List<AttendanceMachineRow> GetActiveMachines(string? machineTypeFilter, out string error);
|
||||
void UpdateMachineSyncState(string machineIp, string status, DateTime? lastSyncDate, int? totalUsers, out string error);
|
||||
bool TryGetMachineLastSyncDate(string machineIp, out DateTime? lastSyncDate, out string error);
|
||||
bool UpdateMachineLastSyncDate(string machineIp, DateTime lastSyncDate, out string error);
|
||||
}
|
||||
|
||||
internal interface IAttendanceLogRepository
|
||||
{
|
||||
bool UpsertAttendance(
|
||||
string acNo,
|
||||
DateTime checkTime,
|
||||
int processed,
|
||||
string machineId,
|
||||
int inOutTypeId,
|
||||
string machineIp,
|
||||
DateTime dateOnly,
|
||||
out string error);
|
||||
}
|
||||
|
||||
internal interface IAttendanceMachineUserRepository
|
||||
{
|
||||
List<AttendanceMachineUserRow> GetActiveUsersByMachine(string machineId, out string error);
|
||||
List<AttendanceMachineUserRow> GetPendingDeletionUsersByMachine(string machineId, out string error);
|
||||
bool UpsertMachineUser(string machineId, string serialNumber, string employeeName, out string error);
|
||||
bool MarkDeleted(string machineId, string serialNumber, string updatedBy, out string error);
|
||||
}
|
||||
|
||||
internal interface IAttendanceMachineFaceTemplateRepository
|
||||
{
|
||||
bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, out string error);
|
||||
bool TryGetActiveFaceTemplateBySerial(string serialNo, out AttendanceMachineFaceTemplateRow? row, out string error);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace HikvisionAttendanceService.Data;
|
||||
|
||||
internal sealed class MySqlHrmsDbConnectionFactory : IHrmsDbConnectionFactory
|
||||
{
|
||||
private readonly HikvisionAttendanceWindowsService.HikvisionServiceConfig _config;
|
||||
|
||||
public MySqlHrmsDbConnectionFactory(HikvisionAttendanceWindowsService.HikvisionServiceConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public string BuildConnectionStringMasked()
|
||||
{
|
||||
if (TryBuildConnectionString(out var cs, out _))
|
||||
{
|
||||
var b = new MySqlConnectionStringBuilder(cs);
|
||||
b.Password = "***";
|
||||
return b.ConnectionString;
|
||||
}
|
||||
return "(invalid)";
|
||||
}
|
||||
|
||||
public bool TryBuildConnectionString(out string connectionString, out string error)
|
||||
{
|
||||
connectionString = "";
|
||||
error = "";
|
||||
if (!string.IsNullOrWhiteSpace(_config.DbConnectionString))
|
||||
{
|
||||
connectionString = _config.DbConnectionString.Trim();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_config.DbHost) ||
|
||||
string.IsNullOrWhiteSpace(_config.DbUsername) ||
|
||||
string.IsNullOrWhiteSpace(_config.DbName))
|
||||
{
|
||||
error = "DbHost/DbUsername/DbName are required when DbConnectionString is empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var b = new MySqlConnectionStringBuilder
|
||||
{
|
||||
Server = _config.DbHost.Trim(),
|
||||
Port = (uint)(_config.DbPort > 0 ? _config.DbPort : 3306),
|
||||
Database = _config.DbName.Trim(),
|
||||
UserID = _config.DbUsername.Trim(),
|
||||
Password = _config.DbPassword ?? "",
|
||||
DefaultCommandTimeout = 30
|
||||
};
|
||||
connectionString = b.ConnectionString;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace HikvisionAttendanceService.Data;
|
||||
|
||||
internal sealed class MySqlAttendanceMachineRepository : IAttendanceMachineRepository
|
||||
{
|
||||
private readonly IHrmsDbConnectionFactory _factory;
|
||||
public MySqlAttendanceMachineRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||
|
||||
public List<AttendanceMachineRow> GetActiveMachines(string? machineTypeFilter, out string error)
|
||||
{
|
||||
error = "";
|
||||
var rows = new List<AttendanceMachineRow>();
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return rows;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
string sql = "SELECT machine_id,machine_ip,port_number,machine_name,site_id,machine_status,machine_type,status,last_sync_date,total_users " +
|
||||
"FROM attendance_machine WHERE (machine_status='active' OR machine_status='1' OR machine_status=1)";
|
||||
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
||||
sql += " AND machine_type=@machineType";
|
||||
using var cmd = new MySqlCommand(sql, conn);
|
||||
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
||||
cmd.Parameters.AddWithValue("@machineType", machineTypeFilter);
|
||||
using var rd = cmd.ExecuteReader();
|
||||
while (rd.Read())
|
||||
{
|
||||
rows.Add(new AttendanceMachineRow
|
||||
{
|
||||
MachineId = rd["machine_id"]?.ToString() ?? "",
|
||||
MachineIp = rd["machine_ip"]?.ToString() ?? "",
|
||||
PortNumber = rd["port_number"] == DBNull.Value ? 8000 : Convert.ToInt32(rd["port_number"]),
|
||||
MachineName = rd["machine_name"]?.ToString() ?? "",
|
||||
SiteId = rd["site_id"] == DBNull.Value ? (int?)null : Convert.ToInt32(rd["site_id"]),
|
||||
MachineStatus = rd["machine_status"]?.ToString() ?? "",
|
||||
MachineType = rd["machine_type"]?.ToString() ?? "",
|
||||
Status = rd["status"]?.ToString() ?? "",
|
||||
LastSyncDate = rd["last_sync_date"] == DBNull.Value ? (DateTime?)null : Convert.ToDateTime(rd["last_sync_date"]),
|
||||
TotalUsers = rd["total_users"] == DBNull.Value ? 0 : Convert.ToInt32(rd["total_users"])
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; }
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void UpdateMachineSyncState(string machineIp, string status, DateTime? lastSyncDate, int? totalUsers, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"UPDATE attendance_machine SET status=@status,last_sync_date=COALESCE(@last_sync_date,last_sync_date),total_users=COALESCE(@total_users,total_users) WHERE machine_ip=@machine_ip",
|
||||
conn);
|
||||
cmd.Parameters.AddWithValue("@status", status ?? "");
|
||||
cmd.Parameters.AddWithValue("@last_sync_date", (object?)lastSyncDate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@total_users", (object?)totalUsers ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? "");
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; }
|
||||
}
|
||||
|
||||
public bool TryGetMachineLastSyncDate(string machineIp, out DateTime? lastSyncDate, out string error)
|
||||
{
|
||||
lastSyncDate = null;
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"SELECT last_sync_date FROM attendance_machine WHERE machine_ip=@machine_ip LIMIT 1",
|
||||
conn);
|
||||
cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? "");
|
||||
var obj = cmd.ExecuteScalar();
|
||||
if (obj == null || obj == DBNull.Value)
|
||||
return true;
|
||||
lastSyncDate = Convert.ToDateTime(obj);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateMachineLastSyncDate(string machineIp, DateTime lastSyncDate, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"UPDATE attendance_machine SET last_sync_date=@last_sync_date WHERE machine_ip=@machine_ip",
|
||||
conn);
|
||||
cmd.Parameters.AddWithValue("@last_sync_date", lastSyncDate);
|
||||
cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? "");
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MySqlAttendanceLogRepository : IAttendanceLogRepository
|
||||
{
|
||||
private readonly IHrmsDbConnectionFactory _factory;
|
||||
public MySqlAttendanceLogRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||
|
||||
public bool UpsertAttendance(string acNo, DateTime checkTime, int processed, string machineId, int inOutTypeId, string machineIp, DateTime dateOnly, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"REPLACE INTO attendance_log (ac_no,checktime,processed,machine_id,in_out_type_id,machine_ip,date) " +
|
||||
"VALUES (@ac_no,@checktime,@processed,@machine_id,@in_out_type_id,@machine_ip,@date)",
|
||||
conn);
|
||||
cmd.Parameters.AddWithValue("@ac_no", acNo ?? "");
|
||||
cmd.Parameters.AddWithValue("@checktime", checkTime);
|
||||
cmd.Parameters.AddWithValue("@processed", processed);
|
||||
cmd.Parameters.AddWithValue("@machine_id", machineId ?? "");
|
||||
cmd.Parameters.AddWithValue("@in_out_type_id", inOutTypeId);
|
||||
cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? "");
|
||||
cmd.Parameters.AddWithValue("@date", dateOnly.Date);
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; return false; }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MySqlAttendanceMachineUserRepository : IAttendanceMachineUserRepository
|
||||
{
|
||||
private readonly IHrmsDbConnectionFactory _factory;
|
||||
public MySqlAttendanceMachineUserRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||
|
||||
public List<AttendanceMachineUserRow> GetActiveUsersByMachine(string machineId, out string error) =>
|
||||
QueryUsers(machineId, false, out error);
|
||||
|
||||
public List<AttendanceMachineUserRow> GetPendingDeletionUsersByMachine(string machineId, out string error) =>
|
||||
QueryUsers(machineId, true, out error);
|
||||
|
||||
private List<AttendanceMachineUserRow> QueryUsers(string machineId, bool deletionRequested, out string error)
|
||||
{
|
||||
error = "";
|
||||
var rows = new List<AttendanceMachineUserRow>();
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return rows;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"SELECT machine_id,serial_number,employee_name,is_deleted,is_deletion_requested FROM attendance_machine_user " +
|
||||
"WHERE machine_id=@machine_id AND is_deleted=0 AND is_deletion_requested=@is_deletion_requested",
|
||||
conn);
|
||||
cmd.Parameters.AddWithValue("@machine_id", machineId ?? "");
|
||||
cmd.Parameters.AddWithValue("@is_deletion_requested", deletionRequested ? 1 : 0);
|
||||
using var rd = cmd.ExecuteReader();
|
||||
while (rd.Read())
|
||||
{
|
||||
rows.Add(new AttendanceMachineUserRow
|
||||
{
|
||||
MachineId = rd["machine_id"]?.ToString() ?? "",
|
||||
SerialNumber = rd["serial_number"]?.ToString() ?? "",
|
||||
EmployeeName = rd["employee_name"]?.ToString() ?? "",
|
||||
IsDeleted = Convert.ToInt32(rd["is_deleted"]) != 0,
|
||||
IsDeletionRequested = Convert.ToInt32(rd["is_deletion_requested"]) != 0
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; }
|
||||
return rows;
|
||||
}
|
||||
|
||||
public bool UpsertMachineUser(string machineId, string serialNumber, string employeeName, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"REPLACE INTO attendance_machine_user (machine_id,serial_number,employee_name,is_deleted,is_deletion_requested,updated_at) " +
|
||||
"VALUES (@machine_id,@serial_number,@employee_name,0,0,NOW())", conn);
|
||||
cmd.Parameters.AddWithValue("@machine_id", machineId ?? "");
|
||||
cmd.Parameters.AddWithValue("@serial_number", serialNumber ?? "");
|
||||
cmd.Parameters.AddWithValue("@employee_name", employeeName ?? "");
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; return false; }
|
||||
}
|
||||
|
||||
public bool MarkDeleted(string machineId, string serialNumber, string updatedBy, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"UPDATE attendance_machine_user SET is_deleted=1,is_deletion_requested=0,updated_by=@updated_by,updated_at=NOW() " +
|
||||
"WHERE machine_id=@machine_id AND serial_number=@serial_number", conn);
|
||||
cmd.Parameters.AddWithValue("@updated_by", updatedBy ?? "hikvision-service");
|
||||
cmd.Parameters.AddWithValue("@machine_id", machineId ?? "");
|
||||
cmd.Parameters.AddWithValue("@serial_number", serialNumber ?? "");
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; return false; }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MySqlAttendanceMachineFaceTemplateRepository : IAttendanceMachineFaceTemplateRepository
|
||||
{
|
||||
private readonly IHrmsDbConnectionFactory _factory;
|
||||
public MySqlAttendanceMachineFaceTemplateRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||
|
||||
public bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, out string error)
|
||||
{
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"REPLACE INTO attendance_machine_face_templates (serial_no,template,created_date,is_active) " +
|
||||
"VALUES (@serial_no,@template,@created_date,@is_active)", conn);
|
||||
cmd.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||
cmd.Parameters.Add("@template", MySqlDbType.Blob).Value = (object?)template ?? Array.Empty<byte>();
|
||||
cmd.Parameters.AddWithValue("@created_date", createdDate);
|
||||
cmd.Parameters.AddWithValue("@is_active", isActive ? 1 : 0);
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; return false; }
|
||||
}
|
||||
|
||||
public bool TryGetActiveFaceTemplateBySerial(string serialNo, out AttendanceMachineFaceTemplateRow? row, out string error)
|
||||
{
|
||||
row = null;
|
||||
error = "";
|
||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using var conn = new MySqlConnection(cs);
|
||||
conn.Open();
|
||||
using var cmd = new MySqlCommand(
|
||||
"SELECT serial_no,template,created_date,is_active FROM attendance_machine_face_templates " +
|
||||
"WHERE serial_no=@serial_no AND is_active=1 ORDER BY created_date DESC LIMIT 1", conn);
|
||||
cmd.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||
using var rd = cmd.ExecuteReader();
|
||||
if (!rd.Read())
|
||||
return false;
|
||||
row = new AttendanceMachineFaceTemplateRow
|
||||
{
|
||||
SerialNo = rd["serial_no"]?.ToString() ?? "",
|
||||
Template = rd["template"] == DBNull.Value ? Array.Empty<byte>() : (byte[])rd["template"],
|
||||
CreatedDate = rd["created_date"] == DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(rd["created_date"]),
|
||||
IsActive = rd["is_active"] != DBNull.Value && Convert.ToInt32(rd["is_active"]) != 0
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) { error = ex.Message; return false; }
|
||||
}
|
||||
}
|
||||
|
|
@ -22,13 +22,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<!-- Hikvision interop (imported directly from the SDK demo source files) -->
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\8-ACS_Optimization_ALL\EventByDeploy\EventByDeploy\HCNetSDK.cs"
|
||||
Link="Interop\EventByDeploy\HCNetSDK.cs" />
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\8-ACS_Optimization_ALL\EventByDeploy\EventByDeploy\TypeMap.cs"
|
||||
Link="Interop\EventByDeploy\TypeMap.cs" />
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\8-ACS_Optimization_ALL\EventByDeploy\EventByDeploy\HCNetSDK.cs" Link="Interop\EventByDeploy\HCNetSDK.cs" />
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\8-ACS_Optimization_ALL\EventByDeploy\EventByDeploy\TypeMap.cs" Link="Interop\EventByDeploy\TypeMap.cs" />
|
||||
<!-- Login_V30 + core callback helpers -->
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\9-AppsDemo_build20201230191326\CommonBase\Head\HCNetSDK.cs"
|
||||
Link="Interop\AppsDemo\CommonBase\HCNetSDK_LoginV30.cs" />
|
||||
<Compile Include="D:\EN-HCNetSDKV6.1.9.48_build20230410_win32\EN-HCNetSDKV6.1.9.48_build20230410_win32\C# demo\9-AppsDemo_build20201230191326\CommonBase\Head\HCNetSDK.cs" Link="Interop\AppsDemo\CommonBase\HCNetSDK_LoginV30.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -51,4 +48,10 @@
|
|||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.Data">
|
||||
<Version>9.6.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,48 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
[DataMember]
|
||||
public string SqlConnectionString { get; set; } = "";
|
||||
|
||||
[DataMember]
|
||||
public bool EnableDbIntegration { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool EnableDbMachineLoading { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool PreferDbMachinesOverConfig { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool EnableAttendanceDbPersistence { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool EnableTemplateDbPersistence { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool EnableTemplateDbToDeviceSync { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool KeepAttendanceFileExport { get; set; } = true;
|
||||
|
||||
[DataMember]
|
||||
public bool KeepTemplateFiles { get; set; } = true;
|
||||
|
||||
[DataMember]
|
||||
public string DbHost { get; set; } = "";
|
||||
|
||||
[DataMember]
|
||||
public int DbPort { get; set; } = 3306;
|
||||
|
||||
[DataMember]
|
||||
public string DbName { get; set; } = "hrms";
|
||||
|
||||
[DataMember]
|
||||
public string DbUsername { get; set; } = "";
|
||||
|
||||
[DataMember]
|
||||
public string DbPassword { get; set; } = "";
|
||||
|
||||
[DataMember]
|
||||
public string DbConnectionString { get; set; } = "";
|
||||
|
||||
/// <summary>When true and SqlConnectionString is set, attendance rows are INSERTed. Default false until schema is finalized.</summary>
|
||||
[DataMember]
|
||||
public bool EnableDatabasePersistence { get; set; }
|
||||
|
|
@ -177,6 +219,20 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
{
|
||||
LogDirectory = @"C:\Users\Public\HikvisionAttendanceService",
|
||||
SqlConnectionString = "",
|
||||
EnableDbIntegration = false,
|
||||
EnableDbMachineLoading = false,
|
||||
PreferDbMachinesOverConfig = false,
|
||||
EnableAttendanceDbPersistence = false,
|
||||
EnableTemplateDbPersistence = false,
|
||||
EnableTemplateDbToDeviceSync = false,
|
||||
KeepAttendanceFileExport = true,
|
||||
KeepTemplateFiles = true,
|
||||
DbHost = "",
|
||||
DbPort = 3306,
|
||||
DbName = "hrms",
|
||||
DbUsername = "",
|
||||
DbPassword = "",
|
||||
DbConnectionString = "",
|
||||
EnableDatabasePersistence = false,
|
||||
AttendanceTextFilePath = "",
|
||||
AttendanceTableName = "dbo.HikvisionAttendanceEvents",
|
||||
|
|
@ -211,6 +267,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(raw)))
|
||||
{
|
||||
var cfg = (HikvisionServiceConfig)serializer.ReadObject(ms);
|
||||
ApplyEnvironmentOverrides(cfg);
|
||||
NormalizeLoadedDeviceConfig(cfg);
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -222,6 +279,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(sanitized)))
|
||||
{
|
||||
var cfg = (HikvisionServiceConfig)serializer.ReadObject(ms);
|
||||
ApplyEnvironmentOverrides(cfg);
|
||||
NormalizeLoadedDeviceConfig(cfg);
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -233,6 +291,20 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
{
|
||||
LogDirectory = @"C:\Users\Public\HikvisionAttendanceService",
|
||||
SqlConnectionString = "",
|
||||
EnableDbIntegration = false,
|
||||
EnableDbMachineLoading = false,
|
||||
PreferDbMachinesOverConfig = false,
|
||||
EnableAttendanceDbPersistence = false,
|
||||
EnableTemplateDbPersistence = false,
|
||||
EnableTemplateDbToDeviceSync = false,
|
||||
KeepAttendanceFileExport = true,
|
||||
KeepTemplateFiles = true,
|
||||
DbHost = "",
|
||||
DbPort = 3306,
|
||||
DbName = "hrms",
|
||||
DbUsername = "",
|
||||
DbPassword = "",
|
||||
DbConnectionString = "",
|
||||
EnableDatabasePersistence = false,
|
||||
AttendanceTextFilePath = "",
|
||||
AttendanceTableName = "dbo.HikvisionAttendanceEvents",
|
||||
|
|
@ -347,6 +419,34 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void ApplyEnvironmentOverrides(HikvisionServiceConfig cfg)
|
||||
{
|
||||
if (cfg == null)
|
||||
return;
|
||||
|
||||
string ReadEnv(string key) => Environment.GetEnvironmentVariable(key) ?? "";
|
||||
|
||||
var cs = ReadEnv("HIKVISION_DB_CONNECTION_STRING");
|
||||
var host = ReadEnv("HIKVISION_DB_HOST");
|
||||
var user = ReadEnv("HIKVISION_DB_USERNAME");
|
||||
var pass = ReadEnv("HIKVISION_DB_PASSWORD");
|
||||
var dbName = ReadEnv("HIKVISION_DB_NAME");
|
||||
var dbPort = ReadEnv("HIKVISION_DB_PORT");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cs))
|
||||
cfg.DbConnectionString = cs.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(host))
|
||||
cfg.DbHost = host.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(user))
|
||||
cfg.DbUsername = user.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(pass))
|
||||
cfg.DbPassword = pass;
|
||||
if (!string.IsNullOrWhiteSpace(dbName))
|
||||
cfg.DbName = dbName.Trim();
|
||||
if (int.TryParse(dbPort, out var parsedPort) && parsedPort > 0)
|
||||
cfg.DbPort = parsedPort;
|
||||
}
|
||||
|
||||
private static void NormalizeLoadedDeviceConfig(HikvisionServiceConfig? cfg)
|
||||
{
|
||||
if (cfg == null)
|
||||
|
|
|
|||
Loading…
Reference in New Issue