984 lines
35 KiB
C#
984 lines
35 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Configuration;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Web.Script.Serialization;
|
|
|
|
namespace HanvonF710XAttendanceService
|
|
{
|
|
/// <summary>
|
|
/// F710X / AI Biometric web API client (POST http://{ip}/api).
|
|
/// Auth: each request includes the device web UI password.
|
|
/// </summary>
|
|
internal sealed class HanvonHttpApiClient
|
|
{
|
|
private readonly string _baseUrl;
|
|
private readonly string _password;
|
|
private readonly int _timeoutMs;
|
|
private readonly JavaScriptSerializer _json = new JavaScriptSerializer { MaxJsonLength = int.MaxValue };
|
|
|
|
public HanvonHttpApiClient(string machineIp, int httpPort = 80, string password = null, int timeoutMs = 60000)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(machineIp)) throw new ArgumentException("machineIp required");
|
|
int port = httpPort > 0 ? httpPort : 80;
|
|
_baseUrl = "http://" + machineIp.Trim() + (port == 80 ? "" : ":" + port) + "/api";
|
|
_password = password ?? MachineScope.GetDevicePassword() ?? "";
|
|
_timeoutMs = timeoutMs > 0 ? timeoutMs : 60000;
|
|
}
|
|
|
|
public static HanvonHttpApiClient ForMachine(AttendanceMachine machine)
|
|
{
|
|
int httpPort = GetIntSetting("DEVICE_HTTP_PORT", 80);
|
|
int timeoutMs = MachineUserDeleteSettings.GetHttpTimeoutMs();
|
|
return new HanvonHttpApiClient(machine.MachineIp, httpPort, MachineScope.GetDevicePassword(), timeoutMs);
|
|
}
|
|
|
|
public bool TryLogin(out string error)
|
|
{
|
|
error = null;
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "login" },
|
|
{ "username", MachineScope.GetDeviceUsername() },
|
|
{ "password", _password },
|
|
{ "rememberMe", false }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "login failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public List<HttpLogRecord> GetLogs(DateTime? from, DateTime? to, out string error)
|
|
{
|
|
error = null;
|
|
var all = new List<HttpLogRecord>();
|
|
try
|
|
{
|
|
int index = 0;
|
|
for (int guard = 0; guard < 10000; guard++)
|
|
{
|
|
var req = new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getlog" },
|
|
{ "password", _password },
|
|
{ "index", index }
|
|
};
|
|
if (from.HasValue) req["from"] = from.Value.ToString("yyyy-MM-dd HH:mm:ss");
|
|
if (to.HasValue) req["to"] = to.Value.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
var resp = Post(req);
|
|
if (!IsSuccess(resp))
|
|
{
|
|
error = GetMsg(resp) ?? "getlog failed";
|
|
return all;
|
|
}
|
|
|
|
var batch = ParseLogRecords(resp);
|
|
all.AddRange(batch);
|
|
|
|
int count = GetInt(resp, "count");
|
|
int toIdx = GetInt(resp, "to");
|
|
if (count <= 0 || toIdx <= 0 || count <= toIdx || batch.Count == 0)
|
|
{
|
|
break;
|
|
}
|
|
index = toIdx + 1;
|
|
}
|
|
return all;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return all;
|
|
}
|
|
}
|
|
|
|
public List<HttpUserRecord> GetUserList(out string error)
|
|
{
|
|
error = null;
|
|
var all = new List<HttpUserRecord>();
|
|
try
|
|
{
|
|
int stn = 1;
|
|
for (int guard = 0; guard < 10000; guard++)
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserlist" },
|
|
{ "password", _password },
|
|
{ "stn", stn }
|
|
});
|
|
if (!IsSuccess(resp))
|
|
{
|
|
error = GetMsg(resp) ?? "getuserlist failed";
|
|
return all;
|
|
}
|
|
|
|
var batch = ParseUserRecords(resp);
|
|
all.AddRange(batch);
|
|
|
|
int count = GetInt(resp, "count");
|
|
int toIdx = GetInt(resp, "to");
|
|
if (count <= 0 || batch.Count == 0 || (toIdx > 0 && toIdx >= count))
|
|
{
|
|
break;
|
|
}
|
|
stn = 0;
|
|
}
|
|
return all;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return all;
|
|
}
|
|
}
|
|
|
|
public bool DeleteUsers(IEnumerable<string> enrollIds, out string error)
|
|
{
|
|
error = null;
|
|
try
|
|
{
|
|
var list = new List<object>();
|
|
foreach (var id in enrollIds)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(id)) continue;
|
|
if (int.TryParse(id.Trim(), out int n)) list.Add(n);
|
|
else list.Add(id.Trim());
|
|
}
|
|
if (list.Count == 0) return true;
|
|
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "deleteusers" },
|
|
{ "password", _password },
|
|
{ "list", list }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "deleteusers failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool CleanLog(out string error)
|
|
{
|
|
error = null;
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "cleanlog" },
|
|
{ "password", _password }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "cleanlog failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static string NormalizeFaceTemplatePayload(byte[] blob, out string pushMode)
|
|
{
|
|
pushMode = "record";
|
|
if (blob == null || blob.Length == 0) return null;
|
|
|
|
if (blob.Length >= 3 && blob[0] == 0xFF && blob[1] == 0xD8 && blob[2] == 0xFF)
|
|
{
|
|
pushMode = "face";
|
|
return Convert.ToBase64String(blob);
|
|
}
|
|
|
|
string text = Encoding.UTF8.GetString(blob).Trim();
|
|
if (string.IsNullOrWhiteSpace(text)) return null;
|
|
|
|
if (text.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
int comma = text.IndexOf(',');
|
|
if (comma >= 0) text = text.Substring(comma + 1);
|
|
}
|
|
|
|
text = text.Replace("\r", "").Replace("\n", "").Trim();
|
|
if (string.IsNullOrWhiteSpace(text)) return null;
|
|
|
|
try
|
|
{
|
|
byte[] decoded = Convert.FromBase64String(text);
|
|
if (decoded.Length >= 3 && decoded[0] == 0xFF && decoded[1] == 0xD8 && decoded[2] == 0xFF)
|
|
{
|
|
pushMode = "face";
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Biometric templates from getuserinfo may not decode as standard base64.
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
public bool TryEnsureUser(int enrollId, string name, out string error)
|
|
{
|
|
error = null;
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "name", name ?? enrollId.ToString() },
|
|
{ "department", "" },
|
|
{ "shiftid", 1 },
|
|
{ "admin", 0 },
|
|
{ "pwd", 0 },
|
|
{ "card", 0 },
|
|
{ "zoneid", 0 },
|
|
{ "groupid", 0 },
|
|
{ "access_times", 0 },
|
|
{ "verifymode", 0 },
|
|
{ "birthday", "" },
|
|
{ "starttime", "" },
|
|
{ "endtime", "" },
|
|
{ "userprofile", "" }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "setuserinfo failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool TrySetFaceTemplateRecord(int enrollId, string templatePayload, string pushMode, out string error)
|
|
{
|
|
error = null;
|
|
if (string.IsNullOrWhiteSpace(templatePayload))
|
|
{
|
|
error = "template record is empty";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var payload = new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId }
|
|
};
|
|
|
|
if (string.Equals(pushMode, "face", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
payload["face"] = templatePayload;
|
|
}
|
|
else
|
|
{
|
|
payload["backupnum"] = GetIntSetting("DEVICE_FACE_BACKUPNUM", 50);
|
|
payload["record"] = templatePayload;
|
|
}
|
|
|
|
var resp = Post(payload);
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "setuserinfo template push failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool TrySetFacePhotoOnly(int enrollId, string faceBase64, out string error)
|
|
{
|
|
error = null;
|
|
if (string.IsNullOrWhiteSpace(faceBase64))
|
|
{
|
|
error = "face photo is empty";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "face", faceBase64 }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "setuserinfo face push failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool TrySetUserWithFacePhoto(int enrollId, string name, string faceBase64, out string error)
|
|
{
|
|
error = null;
|
|
if (string.IsNullOrWhiteSpace(faceBase64))
|
|
{
|
|
error = "face photo is empty";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "name", name ?? enrollId.ToString() },
|
|
{ "department", "" },
|
|
{ "shiftid", 1 },
|
|
{ "admin", 0 },
|
|
{ "pwd", 0 },
|
|
{ "card", 0 },
|
|
{ "zoneid", 0 },
|
|
{ "groupid", 0 },
|
|
{ "access_times", 0 },
|
|
{ "verifymode", 8 },
|
|
{ "birthday", "" },
|
|
{ "starttime", "" },
|
|
{ "endtime", "" },
|
|
{ "userprofile", "" },
|
|
{ "face", faceBase64 }
|
|
});
|
|
if (IsSuccess(resp)) return true;
|
|
error = GetMsg(resp) ?? "setuserinfo face push failed";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool TryGetFaceRecord(int enrollId, out string recordBase64, out string name, out string error)
|
|
{
|
|
recordBase64 = null;
|
|
name = null;
|
|
error = null;
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "backupnum", GetIntSetting("DEVICE_FACE_BACKUPNUM", 50) }
|
|
});
|
|
if (!IsSuccess(resp))
|
|
{
|
|
error = GetMsg(resp) ?? "getuserinfo failed";
|
|
return false;
|
|
}
|
|
name = GetString(resp, "name");
|
|
recordBase64 = GetString(resp, "record");
|
|
if (string.IsNullOrWhiteSpace(recordBase64))
|
|
{
|
|
error = "no face/record payload";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool TryGetUserProfile(int enrollId, out string faceflag, out string photourl, out string name, out string error)
|
|
{
|
|
faceflag = null;
|
|
photourl = null;
|
|
name = null;
|
|
error = null;
|
|
try
|
|
{
|
|
var resp = Post(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId }
|
|
});
|
|
if (!IsSuccess(resp))
|
|
{
|
|
error = GetMsg(resp) ?? "getuserinfo failed";
|
|
return false;
|
|
}
|
|
|
|
name = GetString(resp, "name");
|
|
faceflag = GetString(resp, "faceflag");
|
|
photourl = GetString(resp, "photourl");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static bool ProfileIndicatesFaceEnrolled(string faceflag, string photourl)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(photourl))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(faceflag))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (string.Equals(faceflag, "0", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(faceflag, "false", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (int.TryParse(faceflag.Trim(), out int flagValue))
|
|
{
|
|
return flagValue > 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool UserHasFacePhoto(int enrollId, out string faceflag, out string photourl, out string error)
|
|
{
|
|
faceflag = null;
|
|
photourl = null;
|
|
error = null;
|
|
if (!TryGetUserProfile(enrollId, out faceflag, out photourl, out _, out error))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ProfileIndicatesFaceEnrolled(faceflag, photourl))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
error = "no face enrolled";
|
|
return false;
|
|
}
|
|
|
|
public bool TryUploadDbToDeviceTemplate(int enrollId, string name, DbToDeviceUploadPlan plan, out string error)
|
|
{
|
|
error = null;
|
|
if (plan == null || !plan.HasUploadPayload)
|
|
{
|
|
error = "template payload is empty";
|
|
return false;
|
|
}
|
|
|
|
int delayMs = GetIntSetting("TEMPLATE_DEVICE_DELAY_MS", 300);
|
|
|
|
if (string.Equals(plan.PushMode, "face", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (!TryEnsureUser(enrollId, name, out error))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (delayMs > 0)
|
|
{
|
|
System.Threading.Thread.Sleep(delayMs);
|
|
}
|
|
|
|
return TrySetFacePhotoOnly(enrollId, plan.Payload, out error);
|
|
}
|
|
|
|
if (!TryEnsureUser(enrollId, name, out error))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (delayMs > 0)
|
|
{
|
|
System.Threading.Thread.Sleep(delayMs);
|
|
}
|
|
|
|
if (!TrySetFaceTemplateRecord(enrollId, plan.Payload, plan.PushMode, out error))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool VerifyDbToDeviceTemplate(int enrollId, DbToDeviceUploadPlan plan, out string faceflag, out string photourl, out string error)
|
|
{
|
|
faceflag = null;
|
|
photourl = null;
|
|
error = null;
|
|
if (plan != null && plan.UseProfileVerification)
|
|
{
|
|
return UserHasFacePhoto(enrollId, out faceflag, out photourl, out error);
|
|
}
|
|
|
|
if (UserHasFaceTemplate(enrollId, out error))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool TryRestoreUserWithFaceTemplate(int enrollId, string name, string templatePayload, string pushMode, out string error)
|
|
{
|
|
error = null;
|
|
if (string.IsNullOrWhiteSpace(templatePayload))
|
|
{
|
|
error = "template record is empty";
|
|
return false;
|
|
}
|
|
|
|
if (!TryEnsureUser(enrollId, name, out error))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int delayMs = GetIntSetting("TEMPLATE_DEVICE_DELAY_MS", 300);
|
|
if (delayMs > 0)
|
|
{
|
|
System.Threading.Thread.Sleep(delayMs);
|
|
}
|
|
|
|
if (!TrySetFaceTemplateRecord(enrollId, templatePayload, pushMode, out error))
|
|
{
|
|
string altMode = string.Equals(pushMode, "face", StringComparison.OrdinalIgnoreCase) ? "record" : "face";
|
|
if (!TrySetFaceTemplateRecord(enrollId, templatePayload, altMode, out error))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool UserHasFaceTemplate(int enrollId, out string error)
|
|
{
|
|
return TryGetFaceRecord(enrollId, out _, out _, out error);
|
|
}
|
|
|
|
public HttpExchangeResult PostDetailed(Dictionary<string, object> payload)
|
|
{
|
|
var result = new HttpExchangeResult
|
|
{
|
|
Url = _baseUrl,
|
|
Method = "POST",
|
|
RequestContentType = "application/json; charset=utf-8"
|
|
};
|
|
|
|
string body = _json.Serialize(payload ?? new Dictionary<string, object>());
|
|
result.RequestBodySanitized = HanvonHttpDiagnostics.SanitizeJsonBody(body);
|
|
|
|
try
|
|
{
|
|
var req = (HttpWebRequest)WebRequest.Create(_baseUrl);
|
|
req.Method = "POST";
|
|
req.ContentType = result.RequestContentType;
|
|
req.Timeout = _timeoutMs;
|
|
req.ReadWriteTimeout = _timeoutMs;
|
|
req.KeepAlive = false;
|
|
byte[] bytes = Encoding.UTF8.GetBytes(body);
|
|
req.ContentLength = bytes.Length;
|
|
using (var stream = req.GetRequestStream())
|
|
{
|
|
stream.Write(bytes, 0, bytes.Length);
|
|
}
|
|
|
|
using (var resp = (HttpWebResponse)req.GetResponse())
|
|
using (var reader = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
|
|
{
|
|
result.StatusCode = (int)resp.StatusCode;
|
|
result.ResponseHeaders = FormatResponseHeaders(resp);
|
|
string text = reader.ReadToEnd();
|
|
result.ResponseBody = HanvonHttpDiagnostics.SanitizeJsonBody(text);
|
|
result.ParsedResponse = ParseResponseBody(text);
|
|
}
|
|
}
|
|
catch (WebException wex)
|
|
{
|
|
result.Error = wex.Message;
|
|
if (wex.Response is HttpWebResponse errResp)
|
|
{
|
|
result.StatusCode = (int)errResp.StatusCode;
|
|
result.ResponseHeaders = FormatResponseHeaders(errResp);
|
|
using (var reader = new StreamReader(errResp.GetResponseStream() ?? Stream.Null, Encoding.UTF8))
|
|
{
|
|
string text = reader.ReadToEnd();
|
|
result.ResponseBody = HanvonHttpDiagnostics.SanitizeJsonBody(text);
|
|
result.ParsedResponse = ParseResponseBody(text);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Error = ex.Message;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void DiagnoseDbToDeviceEmployee(
|
|
int enrollId,
|
|
string empId,
|
|
string displayName,
|
|
string templatePayload,
|
|
string pushMode,
|
|
TextWriter logWriter)
|
|
{
|
|
if (logWriter == null) throw new ArgumentNullException(nameof(logWriter));
|
|
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] DIAG emp={empId} enrollid={enrollId} target={_baseUrl}");
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] DIAG template {HanvonHttpDiagnostics.DescribeTemplatePayload(templatePayload, pushMode)}");
|
|
|
|
var login = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "login" },
|
|
{ "username", MachineScope.GetDeviceUsername() },
|
|
{ "password", _password },
|
|
{ "rememberMe", false }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "LOGIN", empId, login);
|
|
|
|
var listBefore = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserlist" },
|
|
{ "password", _password },
|
|
{ "stn", 1 }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "USERLIST_BEFORE", empId, listBefore);
|
|
|
|
var profileBefore = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "VERIFY_PROFILE_BEFORE", empId, profileBefore);
|
|
|
|
var recordBefore = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "backupnum", GetIntSetting("DEVICE_FACE_BACKUPNUM", 50) }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "VERIFY_RECORD_BEFORE", empId, recordBefore);
|
|
|
|
var ensurePayload = new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "name", displayName ?? enrollId.ToString() },
|
|
{ "department", "" },
|
|
{ "shiftid", 1 },
|
|
{ "admin", 0 },
|
|
{ "pwd", 0 },
|
|
{ "card", 0 },
|
|
{ "zoneid", 0 },
|
|
{ "groupid", 0 },
|
|
{ "access_times", 0 },
|
|
{ "verifymode", 0 },
|
|
{ "birthday", "" },
|
|
{ "starttime", "" },
|
|
{ "endtime", "" },
|
|
{ "userprofile", "" }
|
|
};
|
|
var ensure = PostDetailed(ensurePayload);
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "SEND ensure_user setuserinfo", empId, ensure);
|
|
|
|
var templatePayloadReq = new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "setuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId }
|
|
};
|
|
string templateField;
|
|
if (string.Equals(pushMode, "face", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
templatePayloadReq["face"] = templatePayload;
|
|
templateField = "face";
|
|
}
|
|
else
|
|
{
|
|
templatePayloadReq["backupnum"] = GetIntSetting("DEVICE_FACE_BACKUPNUM", 50);
|
|
templatePayloadReq["record"] = templatePayload;
|
|
templateField = "record";
|
|
}
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] emp={empId} SEND template_field={templateField} enrollid={enrollId} employee_number={displayName}");
|
|
var push = PostDetailed(templatePayloadReq);
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "SEND template setuserinfo", empId, push);
|
|
|
|
int delayMs = GetIntSetting("TEMPLATE_DEVICE_DELAY_MS", 300);
|
|
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
|
|
|
|
var profileAfter = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "VERIFY profile getuserinfo (no backupnum)", empId, profileAfter);
|
|
|
|
var recordAfter = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserinfo" },
|
|
{ "password", _password },
|
|
{ "enrollid", enrollId },
|
|
{ "backupnum", GetIntSetting("DEVICE_FACE_BACKUPNUM", 50) }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "VERIFY record getuserinfo backupnum=50", empId, recordAfter);
|
|
|
|
var listAfter = PostDetailed(new Dictionary<string, object>
|
|
{
|
|
{ "cmd", "getuserlist" },
|
|
{ "password", _password },
|
|
{ "stn", 1 }
|
|
});
|
|
HanvonHttpDiagnostics.WriteExchange(logWriter, "USERLIST_AFTER", empId, listAfter);
|
|
|
|
var users = GetUserList(out string listErr);
|
|
var match = users.FirstOrDefault(u =>
|
|
string.Equals(u.DeviceUserId, empId, StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(u.SerialNumber, empId, StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(u.Name, empId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
string recordValue = GetString(recordAfter.ParsedResponse, "record");
|
|
HanvonHttpDiagnostics.WriteResultSummary(
|
|
logWriter,
|
|
empId,
|
|
match != null,
|
|
match?.DeviceUserId,
|
|
match?.Name,
|
|
null,
|
|
IsSuccess(profileAfter.ParsedResponse),
|
|
GetString(profileAfter.ParsedResponse, "faceflag"),
|
|
GetString(profileAfter.ParsedResponse, "photourl"),
|
|
!string.IsNullOrWhiteSpace(recordValue),
|
|
recordValue?.Length ?? 0,
|
|
HanvonHttpDiagnostics.DescribeTemplatePayload(templatePayload, pushMode));
|
|
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] emp={empId} SEND_OK_MEANS ensure_result={IsSuccess(ensure.ParsedResponse)} template_result={IsSuccess(push.ParsedResponse)} (Hanvon JSON result=true only, not proof of stored face)");
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] emp={empId} VERIFY_FAIL_REASON={(string.IsNullOrWhiteSpace(recordValue) ? "getuserinfo backupnum=50 returned empty record field" : "record present")}");
|
|
if (!string.IsNullOrWhiteSpace(listErr))
|
|
{
|
|
logWriter.WriteLine($"[DB_TO_DEVICE] getuserlist_err={listErr}");
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, object> Post(Dictionary<string, object> payload)
|
|
{
|
|
var detailed = PostDetailed(payload);
|
|
if (detailed.ParsedResponse != null && detailed.ParsedResponse.Count > 0)
|
|
{
|
|
return detailed.ParsedResponse;
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(detailed.Error) && detailed.StatusCode == 0)
|
|
{
|
|
throw new IOException(detailed.Error);
|
|
}
|
|
return detailed.ParsedResponse ?? new Dictionary<string, object>();
|
|
}
|
|
|
|
private static Dictionary<string, object> ParseResponseBody(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
{
|
|
return new Dictionary<string, object>();
|
|
}
|
|
try
|
|
{
|
|
return new JavaScriptSerializer { MaxJsonLength = int.MaxValue }
|
|
.Deserialize<Dictionary<string, object>>(text) ?? new Dictionary<string, object>();
|
|
}
|
|
catch
|
|
{
|
|
return new Dictionary<string, object>();
|
|
}
|
|
}
|
|
|
|
private static string FormatResponseHeaders(HttpWebResponse resp)
|
|
{
|
|
if (resp?.Headers == null) return "";
|
|
var pairs = new List<string>();
|
|
foreach (string key in resp.Headers.AllKeys)
|
|
{
|
|
pairs.Add(key + "=" + resp.Headers[key]);
|
|
}
|
|
return string.Join("; ", pairs);
|
|
}
|
|
|
|
private static bool IsSuccess(Dictionary<string, object> resp)
|
|
{
|
|
if (resp == null) return false;
|
|
if (!resp.TryGetValue("result", out object r) || r == null) return false;
|
|
if (r is bool b) return b;
|
|
return string.Equals(r.ToString(), "true", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static string GetMsg(Dictionary<string, object> resp)
|
|
{
|
|
return GetString(resp, "msg") ?? GetString(resp, "reason");
|
|
}
|
|
|
|
private static string GetString(Dictionary<string, object> resp, string key)
|
|
{
|
|
if (resp == null || !resp.TryGetValue(key, out object v) || v == null) return null;
|
|
return v.ToString();
|
|
}
|
|
|
|
private static int GetInt(Dictionary<string, object> resp, string key)
|
|
{
|
|
if (resp == null || !resp.TryGetValue(key, out object v) || v == null) return 0;
|
|
if (v is int i) return i;
|
|
if (v is long l) return (int)l;
|
|
if (v is decimal d) return (int)d;
|
|
int.TryParse(v.ToString(), out int n);
|
|
return n;
|
|
}
|
|
|
|
private static List<HttpLogRecord> ParseLogRecords(Dictionary<string, object> resp)
|
|
{
|
|
var list = new List<HttpLogRecord>();
|
|
if (resp == null || !resp.TryGetValue("record", out object rec) || rec == null) return list;
|
|
if (rec is ArrayList arr)
|
|
{
|
|
foreach (var item in arr) AddLog(list, item as Dictionary<string, object>);
|
|
}
|
|
else if (rec is object[] oa)
|
|
{
|
|
foreach (var item in oa) AddLog(list, item as Dictionary<string, object>);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static void AddLog(List<HttpLogRecord> list, Dictionary<string, object> row)
|
|
{
|
|
if (row == null) return;
|
|
string enroll = GetString(row, "enrollid") ?? "";
|
|
string name = GetString(row, "name") ?? "";
|
|
string timeStr = GetString(row, "time") ?? "";
|
|
if (!DateTime.TryParse(timeStr, out DateTime time)) return;
|
|
string acNo = enroll.Trim();
|
|
if (string.IsNullOrWhiteSpace(acNo)) return;
|
|
list.Add(new HttpLogRecord
|
|
{
|
|
EnrollId = enroll.Trim(),
|
|
Name = name.Trim(),
|
|
AcNo = acNo,
|
|
CheckTime = time,
|
|
Mode = GetInt(row, "mode"),
|
|
InOut = GetInt(row, "inout"),
|
|
Event = GetInt(row, "event")
|
|
});
|
|
}
|
|
|
|
private static List<HttpUserRecord> ParseUserRecords(Dictionary<string, object> resp)
|
|
{
|
|
var list = new List<HttpUserRecord>();
|
|
if (resp == null || !resp.TryGetValue("record", out object rec) || rec == null) return list;
|
|
|
|
IEnumerable items = null;
|
|
if (rec is ArrayList arr) items = arr;
|
|
else if (rec is object[] oa) items = oa;
|
|
if (items == null) return list;
|
|
|
|
foreach (var item in items)
|
|
{
|
|
var row = item as Dictionary<string, object>;
|
|
if (row == null) continue;
|
|
string id = GetString(row, "id") ?? "";
|
|
string name = GetString(row, "name") ?? "";
|
|
string serial = !string.IsNullOrWhiteSpace(name) ? name.Trim() : id.Trim();
|
|
if (string.IsNullOrWhiteSpace(serial)) continue;
|
|
list.Add(new HttpUserRecord
|
|
{
|
|
DeviceUserId = id.Trim(),
|
|
Name = name.Trim(),
|
|
SerialNumber = serial
|
|
});
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static int GetIntSetting(string key, int defaultValue)
|
|
{
|
|
try
|
|
{
|
|
var raw = ConfigurationManager.AppSettings[key];
|
|
if (string.IsNullOrWhiteSpace(raw)) return defaultValue;
|
|
return int.TryParse(raw.Trim(), out var v) && v > 0 ? v : defaultValue;
|
|
}
|
|
catch { return defaultValue; }
|
|
}
|
|
}
|
|
|
|
internal sealed class HttpLogRecord
|
|
{
|
|
public string EnrollId { get; set; }
|
|
public string Name { get; set; }
|
|
public string AcNo { get; set; }
|
|
public DateTime CheckTime { get; set; }
|
|
public int Mode { get; set; }
|
|
public int InOut { get; set; }
|
|
public int Event { get; set; }
|
|
}
|
|
|
|
internal sealed class HttpUserRecord
|
|
{
|
|
public string DeviceUserId { get; set; }
|
|
public string Name { get; set; }
|
|
public string SerialNumber { get; set; }
|
|
}
|
|
|
|
internal static class DeviceProtocol
|
|
{
|
|
public static bool UseHttp()
|
|
{
|
|
var p = ConfigurationManager.AppSettings["DEVICE_PROTOCOL"] ?? "HTTP";
|
|
return string.Equals(p.Trim(), "HTTP", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(p.Trim(), "WEB", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(p.Trim(), "API", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|