killapp/Assets/Scripts/Managers/DataManager.cs
“虞渠成” 61cbbf80a6 fix: 修复蓝牙扫描、自动锁屏和绑定失败的问题
1. 蓝牙扫描时改用ForceInitialize保证系统蓝牙关闭时能提示开启
2. 移除重复的ForceInitialize调用并避免重复初始化导致iOS扫描失效
3. 进入应用时设置不休眠并修正解锁计时起点为当前时间
4. 绑定失败时清空目标MAC防止无限重试循环
5. 调整个人信息页预制体的控件顺序
2026-09-22 11:25:21 +08:00

597 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();
// 应用内不自动息屏
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
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()
{
// 进入app时记录当前时间作为上锁计时起点实现首次进入自动上锁
lastUnlockTime = Time.realtimeSinceStartup;
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;
}
}