killapp/Assets/Scripts/Managers/DataManager.cs
“虞渠成” 10ad83d6b0 refactor: 优化密码校验与第三方登录逻辑,新增未设密码引导流程
1. 移除MainScene和UIManager中的首次启动标记逻辑
2. 新增DataManager的HasAnyPassword方法检测用户是否设置过密码
3. 完善HomePage的CheckPasswordPage,新增未设密码引导弹窗
4. 调整SelfVerificationCodePanel支持自定义返回回调
5. 适配苹果第三方登录无需强制设置密码的审核要求
6. 新增多语言词条支持密码引导文案
7. 移除HomePageCtrl在应用暂停时自动锁定按钮的逻辑
2026-09-20 14:04:59 +08:00

594 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Kill.Network;
using Kill.Managers.ResponseModels;
using Kill.UI.Components;
using UnityEngine;
namespace Kill.Managers
{
public class DataManager : MonoBehaviour
{
public string selectedDeviceMac="";
public static DataManager Instance { get; private set; }
public DeviceInfo selectedDevice;
public List<DeviceInfo> OwnedDevices = new List<DeviceInfo>();
public List<DeviceInfo> SharedDevices = new List<DeviceInfo>();
public bool isOwner;
public bool hasWifi;
public bool hasBluetooth;
public FingerprintListResponseData fingerprintListData = new FingerprintListResponseData();
public List<FingerprintUserData> fingerprintUserDatas = new List<FingerprintUserData>();
public List<ScheduleTaskData> scheduleTaskDatas = new List<ScheduleTaskData>();
private void Awake()
{
Instance = this;
}
public async Task Init()
{
InitUser();
GetSelectedDeviceMac();
LoadLockSettings();
}
public string token = "";
public UserInfo userInfo = new UserInfo();
public void SetToken(string token,UserInfo userInfo)
{
this.token = token;
this.userInfo = userInfo;
string userData =JsonUtility.ToJson(userInfo);
Debug.Log(userData);
PlayerPrefs.SetString("token", token);
PlayerPrefs.SetString("userData", userData);
NetworkCtrl.Instance.RemoveGlobalHeader("token");
NetworkCtrl.Instance.AddGlobalHeader("token", token);
}
public void SavaSelectedDeviceMac(string mac)
{
selectedDeviceMac=mac;
PlayerPrefs.SetString("selectedDeviceMac", selectedDeviceMac);
}
public void GetSelectedDeviceMac()
{
selectedDeviceMac=PlayerPrefs.GetString("selectedDeviceMac", "");
}
#region 使
private const string OWNED_DEVICES_KEY = "ownedDevicesCache";
private const string SHARED_DEVICES_KEY = "sharedDevicesCache";
/// <summary>
/// 将当前 OwnedDevices/SharedDevices 持久化到本地(按当前登录用户隔离)
/// </summary>
public void SaveDeviceLists()
{
try
{
string userId = userInfo?.id ?? "";
string ownedJson = JsonUtility.ToJson(OwnedDevices != null ? new DeviceInfoListWrapper(OwnedDevices) : new DeviceInfoListWrapper());
string sharedJson = JsonUtility.ToJson(SharedDevices != null ? new DeviceInfoListWrapper(SharedDevices) : new DeviceInfoListWrapper());
PlayerPrefs.SetString(OWNED_DEVICES_KEY + "_" + userId, ownedJson);
PlayerPrefs.SetString(SHARED_DEVICES_KEY + "_" + userId, sharedJson);
PlayerPrefs.Save();
}
catch (Exception ex)
{
Debug.LogError($"[DataManager] 保存设备列表缓存失败: {ex.Message}");
}
}
/// <summary>
/// 从本地恢复 OwnedDevices/SharedDevices返回是否成功恢复
/// </summary>
public bool LoadDeviceLists()
{
try
{
string userId = userInfo?.id ?? "";
string ownedJson = PlayerPrefs.GetString(OWNED_DEVICES_KEY + "_" + userId, "");
string sharedJson = PlayerPrefs.GetString(SHARED_DEVICES_KEY + "_" + userId, "");
OwnedDevices = string.IsNullOrEmpty(ownedJson) ? new List<DeviceInfo>() : JsonUtility.FromJson<DeviceInfoListWrapper>(ownedJson)?.ToList() ?? new List<DeviceInfo>();
SharedDevices = string.IsNullOrEmpty(sharedJson) ? new List<DeviceInfo>() : JsonUtility.FromJson<DeviceInfoListWrapper>(sharedJson)?.ToList() ?? new List<DeviceInfo>();
return OwnedDevices.Count > 0 || SharedDevices.Count > 0;
}
catch (Exception ex)
{
Debug.LogError($"[DataManager] 读取设备列表缓存失败: {ex.Message}");
OwnedDevices = new List<DeviceInfo>();
SharedDevices = new List<DeviceInfo>();
return false;
}
}
/// <summary>
/// 清空本地缓存的设备列表(登出时调用)
/// </summary>
public void ClearDeviceLists()
{
string userId = userInfo?.id ?? "";
PlayerPrefs.DeleteKey(OWNED_DEVICES_KEY + "_" + userId);
PlayerPrefs.DeleteKey(SHARED_DEVICES_KEY + "_" + userId);
PlayerPrefs.Save();
OwnedDevices = new List<DeviceInfo>();
SharedDevices = new List<DeviceInfo>();
}
[Serializable]
private class DeviceInfoListWrapper
{
public List<DeviceInfo> items = new List<DeviceInfo>();
public DeviceInfoListWrapper() { }
public DeviceInfoListWrapper(List<DeviceInfo> source)
{
if (source != null) items.AddRange(source);
}
public List<DeviceInfo> ToList() => new List<DeviceInfo>(items);
}
#endregion
#region
private const string LOCK_TIME_KEY = "autoLockTime";
/// <summary>自动锁定时间默认值(秒)</summary>
private const int DEFAULT_LOCK_TIME = 180;
/// <summary>永不锁定的哨兵值(秒,极大值)</summary>
public const int NEVER_LOCK = int.MaxValue;
/// <summary>
/// 自动锁定时间(秒),可配置项,按当前登录账号保存在本地
/// </summary>
public int lockTime = DEFAULT_LOCK_TIME;
/// <summary>
/// 上次解锁时间(秒基于Time.realtimeSinceStartup)
/// <para>进入app时重置为0解锁后记录时间0表示进入app后尚未解锁</para>
/// </summary>
public float lastUnlockTime = 0;
/// <summary>进入app时调用重置解锁时间并读取当前账号的自动锁定时间配置</summary>
public void LoadLockSettings()
{
lastUnlockTime = 0;
string userId = userInfo?.id ?? "";
lockTime = PlayerPrefs.GetInt(LOCK_TIME_KEY + "_" + userId, DEFAULT_LOCK_TIME);
}
/// <summary>设置自动锁定时间(秒)并保存到本地(与当前登录账号关联),传 NEVER_LOCK 表示永不锁定</summary>
public void SetLockTime(int seconds)
{
lockTime = seconds;
PlayerPrefs.SetInt(LOCK_TIME_KEY + "_" + (userInfo?.id ?? ""), seconds);
PlayerPrefs.Save();
}
/// <summary>是否启用了永不锁定</summary>
public bool IsNeverLock => lockTime == NEVER_LOCK;
/// <summary>解锁后记录当前时间(作为自动锁定倒计时的起点)</summary>
public void RecordUnlock()
{
lastUnlockTime = Time.realtimeSinceStartup;
}
#endregion
#region
/// <summary>
/// 用户当前账号是否已设置过任何一种密码(用于 LockButtonPlane 决定显示密码输入还是引导)
/// </summary>
public bool HasAnyPassword()
{
return userInfo != null && (userInfo.has_simple_password || userInfo.has_complex_password);
}
#endregion
public void InitUser()
{
token = PlayerPrefs.GetString("token", "");
string userData = PlayerPrefs.GetString("userData", "");
userInfo = JsonUtility.FromJson<UserInfo>(userData);
NetworkCtrl.Instance.RemoveGlobalHeader("token");
NetworkCtrl.Instance.AddGlobalHeader("token",token);
Debug.Log(token);
}
public void ClearInfo()
{
token = "";
userInfo = new UserInfo();
PlayerPrefs.DeleteKey("token");
PlayerPrefs.DeleteKey("userData");
}
public async Task<bool> TokenLogin()
{
LoadingUI.Show();
try
{
// 获取用户详情
var response = await NetworkCtrl.Instance.Post<LoginResponse>("/api/v1/auth/token-login");
LoadingUI.Hide();
if(response.Data.code==200)
{
SetToken(response.Data.data.token,response.Data.data.user);
return true;
}
else
{
ClearInfo();
return false;
}
}
catch (Exception ex)
{
LoadingUI.Hide();
Debug.LogError($"TokenLogin 异常: {ex.Message}");
return false;
}
}
public DeviceConfig deviceConfig;
// 从服务器获取的原始配置快照,用于比对变化字段
private DeviceConfig _originalDeviceConfig;
#region
/// <summary>
/// 获取设备配置
/// </summary>
public async Task GetDeviceConfig(string deviceSn)
{
var response = await NetworkCtrl.Instance.Get<DeviceConfigResponse>(
"/api/v1/device/config",
new Dictionary<string, string> { { "device_sn", deviceSn } }
);
DeviceConfig result = null;
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
result = data.data;
deviceConfig = result;
// 保存原始快照,用于后续差异对比
_originalDeviceConfig = result?.Clone();
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 获取设备配置失败: {msg}");
},
silentErrorTip: true
);
}
/// <summary>
/// 修改设备配置(只提交与原始配置相比发生变化的字段)
/// </summary>
/// <param name="deviceSn">设备 MAC/序列号</param>
/// <param name="config">当前设备配置</param>
/// <param name="original">原始配置快照,传 null 时会提交所有非空字段</param>
public async Task<bool> UpdateDeviceConfig(string deviceSn, DeviceConfig config, DeviceConfig original = null)
{
if (config == null)
{
Debug.LogError("[DataManager] UpdateDeviceConfig: config 为 null");
return false;
}
config.device_sn = deviceSn;
var changedFields = config.GetChangedFields(original);
// device_sn 必须始终携带
changedFields["device_sn"] = deviceSn;
if (changedFields.Count == 0)
{
Debug.Log("[DataManager] UpdateDeviceConfig: 没有字段变化,跳过同步");
return true;
}
var response = await NetworkCtrl.Instance.Put<NoDataResponse>("/api/v1/device/config", changedFields);
bool success = false;
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
success = true;
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 修改设备配置失败: {msg}");
},
silentErrorTip: true
);
return success;
}
/// <summary>
/// 构建设备配置JSON只包含非null字段
/// </summary>
private string BuildDeviceConfigJson(DeviceConfig config)
{
var sb = new System.Text.StringBuilder();
sb.Append("{");
AppendField(sb, "device_sn", config.device_sn);
AppendField(sb, "language", config.language);
AppendField(sb, "is_locked", config.is_locked);
AppendField(sb, "fov_angle", config.fov_angle);
AppendField(sb, "detection_distance", config.detection_distance);
AppendField(sb, "aim_distance", config.aim_distance);
AppendField(sb, "work_mode", config.work_mode);
AppendField(sb, "fill_light_enable", config.fill_light_enable);
AppendField(sb, "fill_light_type", config.fill_light_type);
AppendField(sb, "fill_light_intensity", config.fill_light_intensity);
AppendField(sb, "laser_visible_enable", config.laser_visible_enable);
AppendField(sb, "visual_detect_enable", config.visual_detect_enable);
AppendField(sb, "visual_sensitivity", config.visual_sensitivity);
AppendField(sb, "radar_enable", config.radar_enable);
AppendField(sb, "radar_sensitivity", config.radar_sensitivity);
AppendField(sb, "radar_safe_distance", config.radar_safe_distance);
AppendField(sb, "rgb_enable", config.rgb_enable);
AppendField(sb, "rgb_red", config.rgb_red);
AppendField(sb, "rgb_green", config.rgb_green);
AppendField(sb, "rgb_blue", config.rgb_blue);
AppendField(sb, "rgb_effect", config.rgb_effect);
AppendField(sb, "lcd_auto_brightness", config.lcd_auto_brightness);
AppendField(sb, "lcd_brightness", config.lcd_brightness);
AppendField(sb, "lcd_sleep_enable", config.lcd_sleep_enable);
AppendField(sb, "lcd_sleep_time", config.lcd_sleep_time);
AppendField(sb, "wifi_enable", config.wifi_enable);
AppendField(sb, "wifi_ssid", config.wifi_ssid);
AppendField(sb, "wifi_password", config.wifi_password);
AppendField(sb, "video_record_enable", config.video_record_enable);
AppendField(sb, "record_duration", config.record_duration);
AppendField(sb, "sound_enable", config.sound_enable);
AppendField(sb, "sound_type", config.sound_type);
AppendField(sb, "volume", config.volume);
// 移除末尾逗号
if (sb[sb.Length - 1] == ',')
sb.Length--;
sb.Append("}");
return sb.ToString();
}
private void AppendField(System.Text.StringBuilder sb, string key, object value)
{
if (value == null) return;
sb.Append("\"");
sb.Append(key);
sb.Append("\":");
if (value is string s)
{
sb.Append("\"");
sb.Append(s);
sb.Append("\"");
}
else if (value is bool b)
{
sb.Append(b ? "true" : "false");
}
else
{
sb.Append(value.ToString());
}
sb.Append(",");
}
#endregion
/// <summary>
/// 同步设备配置到服务器WiFi在线时调用由各设置页面Sync方法调用
/// 只提交与原始配置相比发生变化的字段
/// </summary>
public async void SyncDeviceConfigToServer()
{
if (!hasWifi) return;
bool success = await UpdateDeviceConfig(selectedDevice.ble_mac, deviceConfig, _originalDeviceConfig);
if (success)
{
// 同步成功后刷新原始快照
_originalDeviceConfig = deviceConfig?.Clone();
}
}
/// <summary>
/// 更新定时任务到服务器
/// </summary>
public async Task<bool> UpdateScheduleTasksToServer(string deviceSn, List<ScheduleTaskData> tasks)
{
var requestData = new ScheduleTasksUploadRequest
{
device_sn = deviceSn,
tasks = tasks
};
var response = await NetworkCtrl.Instance.Put<NoDataResponse>(
"/api/v1/device/schedule-tasks", requestData);
bool success = false;
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
success = true;
Debug.Log($"[DataManager] 定时任务已同步到服务器,共 {tasks.Count} 条");
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 同步定时任务失败: {msg}");
}
);
return success;
}
/// <summary>
/// 更新指纹列表到服务器
/// </summary>
public async Task<bool> UpdateFingerprintsToServer(string deviceSn, List<FingerprintUserData> fingerprints)
{
var requestData = new FingerprintsUploadRequest
{
device_sn = deviceSn,
fingerprints = fingerprints
};
var response = await NetworkCtrl.Instance.Put<NoDataResponse>(
"/api/v1/device/fingerprints", requestData);
bool success = false;
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
success = true;
Debug.Log($"[DataManager] 指纹列表已同步到服务器,共 {fingerprints.Count} 条");
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 同步指纹列表失败: {msg}");
}
);
return success;
}
/// <summary>
/// 通过WiFi获取指纹列表
/// </summary>
public async Task GetFingerprints()
{
var response = await NetworkCtrl.Instance.Get<FingerprintListResponse>(
"/api/v1/device/fingerprints",
new Dictionary<string, string> { { "device_sn", selectedDevice.ble_mac } }
);
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
fingerprintUserDatas.Clear();
if (data.data?.list != null)
{
fingerprintUserDatas.AddRange(data.data.list);
}
Debug.Log($"[DataManager] 获取到 {fingerprintUserDatas.Count} 条指纹记录");
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 获取指纹列表失败: {msg}");
}
);
}
/// <summary>
/// 通过WiFi获取定时任务列表
/// </summary>
public async Task GetScheduleTasks(string deviceSn)
{
var response = await NetworkCtrl.Instance.Get<ScheduleTaskListDataResponse>(
"/api/v1/device/schedule-tasks",
new Dictionary<string, string> { { "device_sn", deviceSn } }
);
ResponseCodeHandler.HandleResponse(response,
onSuccess: (data) =>
{
scheduleTaskDatas.Clear();
Debug.Log($"[DataManager] 定时任务API返回: total={data.data?.total}, list count={data.data?.list?.Count ?? 0}");
if (data.data?.list != null)
{
scheduleTaskDatas.AddRange(data.data.list);
foreach (var d in scheduleTaskDatas)
{
Debug.Log($"[DataManager] 任务 ID={d.task_id}, enabled={d.enabled}, start={d.start_hour}:{d.start_minute:D2}, end={d.end_hour}:{d.end_minute:D2}, mode={d.mode}, repeat={d.repeat_mask}");
}
}
Debug.Log($"[DataManager] 获取到 {scheduleTaskDatas.Count} 条定时任务");
},
onError: (code, msg) =>
{
Debug.LogError($"[DataManager] 获取定时任务列表失败: {msg}");
}
);
}
}
/// <summary>
/// 指纹用户数据(从服务器获取)
/// </summary>
[System.Serializable]
public class FingerprintUserData
{
public string username;
public int fingerprint_id;
public bool has_fingerprint;
}
}
// 响应模型
namespace Kill.Managers.ResponseModels
{
[System.Serializable]
public class FingerprintListResponse
{
public int code;
public string message;
public FingerprintListResponseData data;
}
[System.Serializable]
public class FingerprintListResponseData
{
public int total;
public int pages;
public int limit;
public int page;
public List<FingerprintUserData> list;
}
[System.Serializable]
public class ScheduleTaskListDataResponse
{
public int code;
public string message;
public ScheduleTaskListData data;
}
[System.Serializable]
public class ScheduleTaskListData
{
public int total;
public int pages;
public int limit;
public int page;
public List<ScheduleTaskData> list;
}
[System.Serializable]
public class ScheduleTaskData
{
public byte task_id;
public bool enabled;
public byte start_hour;
public byte start_minute;
public byte end_hour;
public byte end_minute;
public string mode;
public byte repeat_mask;
}
[System.Serializable]
public class ScheduleTasksUploadRequest
{
public string device_sn;
public List<ScheduleTaskData> tasks;
}
[System.Serializable]
public class FingerprintsUploadRequest
{
public string device_sn;
public List<FingerprintUserData> fingerprints;
}
}