diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index c91c43c66..8bf262c98 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 60;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index ca4a8bf61..aff31ef06 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -35,7 +35,7 @@
Editor
CFBundleURLSchemes
- wx5bbb7be9324f3e2d
+ wx4b5ec8957ad5e425
diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart
index b55927d5a..a793411f3 100644
--- a/lib/data/services/ble_manager.dart
+++ b/lib/data/services/ble_manager.dart
@@ -519,7 +519,11 @@ class BleManager extends GetxService {
Future startAgentService() async {
try {
// 从.env文件直接读取环境变量
- final serverurl = dotenv.env['SERVER_URL'] ?? '';
+ var serverurl = dotenv.env['SERVER_URL'] ?? '';
+ if (Get.locale?.countryCode != 'CN') {
+ serverurl = dotenv.env['HW_SERVER_URL'] ?? '';
+ }
+
var jsonString = _storage.read("logintoken");
Logger.info('启动BLE服务,serverurl: ${serverurl}');
// final azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? 'eastasia';
@@ -549,6 +553,40 @@ class BleManager extends GetxService {
// 从Flutter资源包中加载.mcp.json
final jsonData = AppConfig.mcpConfig();
+ // 根据APP语言动态配置语音识别语言
+ List supportedLanguages = [];
+ try {
+ // 使用 GetX 获取 APP 当前语言环境 (Locale)
+ final appLocale = Get.locale;
+
+ if (appLocale != null) {
+ final lang = appLocale.languageCode; // e.g., 'en'
+ final country = appLocale.countryCode; // e.g., 'US'
+
+ // 构造 Azure SDK 需要的格式 (e.g., 'en-US')
+ if (country != null && country.isNotEmpty) {
+ supportedLanguages.add('$lang-$country');
+ } else {
+ supportedLanguages.add(lang); // fallback only language code
+ }
+ }
+
+ // 添加 en-US 作为备选
+ if (!supportedLanguages.contains('en-US')) {
+ supportedLanguages.add('en-US');
+ }
+
+ // 最终兜底
+ if (supportedLanguages.isEmpty) {
+ supportedLanguages.add('en-US');
+ }
+
+ Logger.info('动态配置语音识别语言: ${supportedLanguages.join(', ')}');
+ } catch (e) {
+ Logger.error('获取APP语言配置失败,使用默认语言: $e');
+ supportedLanguages = ['zh-CN', 'en-US'];
+ }
+
final config = {
// 语音代理配置参数
'azureSpeechKey': azureSpeechKey,
@@ -563,7 +601,7 @@ class BleManager extends GetxService {
'volcanoToken': 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En',
'serverurl': serverurl,
'usertoken': jsonString,
- 'supportedLanguages': ['zh-CN'],
+ 'supportedLanguages': supportedLanguages,
};
// 记录日志(不记录敏感信息)
diff --git a/lib/data/services/network/dio_manager.dart b/lib/data/services/network/dio_manager.dart
index abdb7850f..0496c11f9 100644
--- a/lib/data/services/network/dio_manager.dart
+++ b/lib/data/services/network/dio_manager.dart
@@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:get/get.dart' hide Response;
import 'auth_interceptor.dart';
import 'nw_method.dart';
@@ -9,7 +10,18 @@ class DioManager {
factory DioManager() => _shared;
late Dio dio;
- final baseUrl = dotenv.env['SERVER_URL'] ?? "";
+ // 根据地区获取服务器URL
+ String get baseUrl {
+ final countryCode = Get.deviceLocale?.countryCode?.toUpperCase();
+ final isChinaMainland = countryCode == 'CN';
+
+ if (!isChinaMainland) {
+ return dotenv.env['HW_SERVER_URL'] ?? dotenv.env['SERVER_URL'] ?? '';
+ } else {
+ return dotenv.env['SERVER_URL'] ?? '';
+ }
+ }
+
DioManager._internal() {
BaseOptions options = BaseOptions(
baseUrl: baseUrl,
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index c29675888..dda9ed2c4 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -153,7 +153,7 @@ class AgentController extends GetxController {
_setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器
- _configureSpeechRecognitionLanguages(); //配置语音识别语言
+
startVoiceInput(); //开启语音输入
}
@@ -1013,49 +1013,7 @@ class AgentController extends GetxController {
}
}
-//根据APP语言配置语音识别语言
- Future _configureSpeechRecognitionLanguages() async {
- try {
- // 1. 使用 GetX 获取 APP 当前语言环境 (Locale)
- final appLocale = Get.locale;
- List languagesToSupport = [];
-
- if (appLocale != null) {
- final lang = appLocale.languageCode; // e.g., 'en'
- final country = appLocale.countryCode; // e.g., 'US'
-
- // 2. 构造 Azure SDK 需要的格式 (e.g., 'en-US')
- if (country != null && country.isNotEmpty) {
- languagesToSupport.add('$lang-$country');
- } else {
- languagesToSupport.add(lang); // fallback only language code
- }
- }
- // 3. 添加 en-US 作为备选
- if (!languagesToSupport.contains('en-US')) {
- languagesToSupport.add('en-US');
- }
-
- // 4. 最终兜底
- if (languagesToSupport.isEmpty) {
- languagesToSupport.add('en-US');
- }
-
- // 5. 调用原生设置
- Logger.i(TAG, '支持的语言: ${languagesToSupport.join(', ')}');
- AgentService.setSupportedLanguages(languagesToSupport).then((success) {
- if (success) {
- Logger.i(TAG, 'asr的语言已根据 APP 设置语言: ${languagesToSupport.join(', ')}');
- } else {
- Logger.w(TAG, '根据 APP 设置asr语言失败');
- }
- });
- } catch (e) {
- Logger.e(TAG, "获取 APP 语言并设置失败: $e");
- AgentService.setSupportedLanguages(['zh-CN', 'en-US']);
- }
- }
// 开始语音输入
Future startVoiceInput() async {
diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart
index 1a1fe15a8..ba32f0c98 100644
--- a/lib/modules/devices/controllers/devices_controller.dart
+++ b/lib/modules/devices/controllers/devices_controller.dart
@@ -28,9 +28,8 @@ class DevicesController extends GetxController {
for (var device in User.instance.devices) {
if (!devices.containsKey(device.id)) {
devices[device.devicetype] = [];
- } else {
- devices[device.devicetype]!.add(device);
}
+ devices[device.devicetype]!.add(device);
}
}
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index eb488a73a..a8f0e8acc 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -613,8 +613,11 @@ class PairingController extends GetxController {
final names = AppConfig.allproductNames();
// 检查是否已有绑定的设备
print("device.address${device.address}");
- final existingDevice = User.instance.devices.firstWhere(
- (d) => d.devicetype == 1 && d.devicemac == device.address,
+ var existingDevice = User.instance.devices.firstWhere(
+ (d) =>
+ d.devicetype == 1 &&
+ d.devicename == device.name &&
+ d.devicemac == device.address,
orElse: () => UserDevice(
id: -1,
uid: '',
@@ -627,7 +630,28 @@ class PairingController extends GetxController {
// 已经绑定
return 9;
}
-
+ if (Platform.isAndroid) {
+ // 检查是否已有BLE绑定的设备
+ existingDevice = User.instance.devices.firstWhere(
+ (d) =>
+ d.devicetype == 2 &&
+ d.devicename == device.name &&
+ d.devicemac == device.address,
+ orElse: () => UserDevice(
+ id: -1,
+ uid: '',
+ devicetype: -1,
+ devicename: '',
+ devicemac: '',
+ license: ''),
+ );
+ if (existingDevice.id != -1) {
+ _verifiedDeviceName = device.name;
+ _verifiedDeviceAddress = device.address;
+ // 已经绑定
+ return 10;
+ }
+ }
print(
'获取到 deviceName: $deviceName address: ${device.address} names:$names');
if (names.contains(deviceName)) {
diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
index 8229f4863..60b919843 100644
--- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
+++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
@@ -74,6 +74,19 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
config["serverurl"]?.toString()?.let { intent.putExtra("serverurl", it) }
config["usertoken"]?.toString()?.let { intent.putExtra("usertoken", it) }
+ // 添加 supportedLanguages 参数处理
+ config["supportedLanguages"]?.let { languages ->
+ when (languages) {
+ is List<*> -> {
+ // 将 List 转换为 ArrayList 以便传递给 Intent
+ val languageList = languages.filterIsInstance()
+ if (languageList.isNotEmpty()) {
+ intent.putStringArrayListExtra("supportedLanguages", ArrayList(languageList))
+ }
+ }
+ }
+ }
+
// 启动前台服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
index 93c016657..9eb03b1f3 100644
--- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
+++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
@@ -51,6 +51,7 @@ class BleMediaService : Service(), CoroutineScope {
private const val KEY_SERVER_URL = "serverurl"
private const val KEY_USER_TOKEN = "usertoken"
+ private const val KEY_SUPPORTED_LANGUAGES = "supportedLanguages"
// 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false)
@@ -130,7 +131,10 @@ class BleMediaService : Service(), CoroutineScope {
Log.d(TAG, "当前配置参数: $config")
// 检查必要的配置是否存在
- if (config[KEY_AZURE_SPEECH_KEY].isNullOrEmpty() || config[KEY_OPENAI_API_KEY].isNullOrEmpty()) {
+ val azureSpeechKey = config[KEY_AZURE_SPEECH_KEY]?.toString() ?: ""
+ val openaiApiKey = config[KEY_OPENAI_API_KEY]?.toString() ?: ""
+
+ if (azureSpeechKey.isEmpty() || openaiApiKey.isEmpty()) {
Log.w(TAG, "语音代理初始化失败: API密钥未配置")
return
}
@@ -159,8 +163,8 @@ class BleMediaService : Service(), CoroutineScope {
/**
* 从Intent中提取配置参数
*/
- private fun extractConfigFromIntent(intent: Intent?): Map {
- val config = mutableMapOf()
+ private fun extractConfigFromIntent(intent: Intent?): Map {
+ val config = mutableMapOf()
intent?.let {
// 提取可能存在的配置参数
it.getStringExtra(KEY_AZURE_SPEECH_KEY)?.let { value -> config[KEY_AZURE_SPEECH_KEY] = value }
@@ -175,6 +179,13 @@ class BleMediaService : Service(), CoroutineScope {
it.getStringExtra(KEY_VOLCANO_TOKEN)?.let { value -> config[KEY_VOLCANO_TOKEN] = value }
it.getStringExtra(KEY_SERVER_URL)?.let { value -> config[KEY_SERVER_URL] = value }
it.getStringExtra(KEY_USER_TOKEN)?.let { value -> config[KEY_USER_TOKEN] = value }
+
+ // 提取 supportedLanguages 参数
+ it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages ->
+ if (languages.isNotEmpty()) {
+ config[KEY_SUPPORTED_LANGUAGES] = languages
+ }
+ }
}
Log.i(TAG, "从Intent中提取到配置参数: $config")
return config
@@ -183,11 +194,25 @@ class BleMediaService : Service(), CoroutineScope {
/**
* 将配置参数保存到SharedPreferences
*/
- private fun saveConfigToPreferences(config: Map, prefs: SharedPreferences) {
+ private fun saveConfigToPreferences(config: Map, prefs: SharedPreferences) {
prefs.edit().apply {
config.forEach { (key, value) ->
- if (value.isNotEmpty()) {
- putString(key, value)
+ when (value) {
+ is String -> {
+ if (value.isNotEmpty()) {
+ putString(key, value)
+ }
+ }
+ is List<*> -> {
+ // 处理 supportedLanguages 列表
+ if (key == KEY_SUPPORTED_LANGUAGES) {
+ val languageList = value.filterIsInstance()
+ if (languageList.isNotEmpty()) {
+ // 将语言列表转换为逗号分隔的字符串保存
+ putString(key, languageList.joinToString(","))
+ }
+ }
+ }
}
}
apply()
@@ -197,21 +222,34 @@ class BleMediaService : Service(), CoroutineScope {
/**
* 从SharedPreferences获取配置
*/
- private fun getConfigFromPreferences(prefs: SharedPreferences): Map {
- return mapOf(
- KEY_AZURE_SPEECH_KEY to getConfigString(prefs, KEY_AZURE_SPEECH_KEY, ""),
- KEY_AZURE_SPEECH_REGION to getConfigString(prefs, KEY_AZURE_SPEECH_REGION, "eastasia"),
- KEY_OPENAI_API_KEY to getConfigString(prefs, KEY_OPENAI_API_KEY, ""),
- KEY_OPENAI_BASE_URL to getConfigString(prefs, KEY_OPENAI_BASE_URL, "https://api.openai.com/v1"),
- KEY_OPENAI_MODEL to getConfigString(prefs, KEY_OPENAI_MODEL, "gpt-3.5-turbo"),
- KEY_OPENAI_VISION_MODEL to getConfigString(prefs, KEY_OPENAI_VISION_MODEL, "gpt-4-vision-preview"),
- KEY_SYSTEM_PROMPT to getConfigString(prefs, KEY_SYSTEM_PROMPT, "gpt-3.5-turbo"),
- KEY_MCP_SERVER to getConfigString(prefs, KEY_MCP_SERVER, ""),
- KEY_VOLCANO_APP_ID to getConfigString(prefs, KEY_VOLCANO_APP_ID, ""),
- KEY_VOLCANO_TOKEN to getConfigString(prefs, KEY_VOLCANO_TOKEN, ""),
- KEY_SERVER_URL to getConfigString(prefs, KEY_SERVER_URL, ""),
- KEY_USER_TOKEN to getConfigString(prefs, KEY_USER_TOKEN, "")
- )
+ private fun getConfigFromPreferences(prefs: SharedPreferences): Map {
+ val config = mutableMapOf()
+
+ // 添加字符串类型的配置参数
+ config[KEY_AZURE_SPEECH_KEY] = getConfigString(prefs, KEY_AZURE_SPEECH_KEY, "")
+ config[KEY_AZURE_SPEECH_REGION] = getConfigString(prefs, KEY_AZURE_SPEECH_REGION, "eastasia")
+ config[KEY_OPENAI_API_KEY] = getConfigString(prefs, KEY_OPENAI_API_KEY, "")
+ config[KEY_OPENAI_BASE_URL] = getConfigString(prefs, KEY_OPENAI_BASE_URL, "https://api.openai.com/v1")
+ config[KEY_OPENAI_MODEL] = getConfigString(prefs, KEY_OPENAI_MODEL, "gpt-3.5-turbo")
+ config[KEY_OPENAI_VISION_MODEL] = getConfigString(prefs, KEY_OPENAI_VISION_MODEL, "gpt-4-vision-preview")
+ config[KEY_SYSTEM_PROMPT] = getConfigString(prefs, KEY_SYSTEM_PROMPT, "gpt-3.5-turbo")
+ config[KEY_MCP_SERVER] = getConfigString(prefs, KEY_MCP_SERVER, "")
+ config[KEY_VOLCANO_APP_ID] = getConfigString(prefs, KEY_VOLCANO_APP_ID, "")
+ config[KEY_VOLCANO_TOKEN] = getConfigString(prefs, KEY_VOLCANO_TOKEN, "")
+ config[KEY_SERVER_URL] = getConfigString(prefs, KEY_SERVER_URL, "")
+ config[KEY_USER_TOKEN] = getConfigString(prefs, KEY_USER_TOKEN, "")
+
+ // 添加 supportedLanguages 参数处理
+ val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "")
+ if (supportedLanguagesStr.isNotEmpty()) {
+ // 将逗号分隔的字符串转换回语言列表
+ val languageList = supportedLanguagesStr.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ if (languageList.isNotEmpty()) {
+ config[KEY_SUPPORTED_LANGUAGES] = languageList
+ }
+ }
+
+ return config
}
/**
diff --git a/local_plugins/agent_service/ios/agent_service/Package.swift b/local_plugins/agent_service/ios/agent_service/Package.swift
index 278e827c5..cc30a9ad6 100644
--- a/local_plugins/agent_service/ios/agent_service/Package.swift
+++ b/local_plugins/agent_service/ios/agent_service/Package.swift
@@ -14,7 +14,9 @@ let package = Package(
.package(path: "../../../azure_speech/ios/azure_speech"),
.package(path: "../../../chat_api/ios/chat_api"),
.package(path: "../../../chat_storage/ios/chat_storage"),
- .package(path: "../../../music_service/ios/music_service")
+ .package(path: "../../../music_service/ios/music_service"),
+ .package(path: "../../../music_service/ios/user_portrait")
+
],
targets: [
.target(
@@ -26,7 +28,8 @@ let package = Package(
.product(name: "azure-speech", package: "azure_speech"),
.product(name: "chat-api", package: "chat_api"),
.product(name: "chat-storage", package: "chat_storage"),
- .product(name: "music-service", package: "music_service")
+ .product(name: "music-service", package: "music_service"),
+ .product(name: "user-portrait", package: "user_portrait"),
],
path: "Sources/agent_service",
resources: [
diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
index 29dd1d360..ce456f1e3 100644
--- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
+++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
@@ -8,6 +8,7 @@ import os.log
import chat_api
import chat_storage
import music_service
+import user_portrait
protocol AgentServiceListener: AnyObject {
func onEvent(eventName: String, data: [String: Any])
@@ -78,7 +79,7 @@ class AgentServiceImpl: NSObject {
chatApiService = ChatApiService()
// chatStorageHelper 现在是单例,不需要初始化
-
+ UserPortrait.shared.initialize()
BleService.shared.setDelegate(self)
}
@@ -473,7 +474,7 @@ audioStream.saveAudioDataTo(data: audioData)
if !systemPrompt.isEmpty {
- let info: [String: String] = self.locationInfo ?? ["user_location": "还未获取到位置信息", "user_lat": "未开启定位", "user_lng": "未开启定位"]
+ let info: [String: String] = UserPortrait.shared.getAll()
let _systemPrompt = fillTemplate(systemPrompt, with: info)
messages.append(chatApiService.createSystemMessage(content: _systemPrompt))
os_log("添加系统提示消息", log: logger, type: .info)
@@ -825,11 +826,34 @@ private func processNavigationCard(_ cardNavigation: [String: Any]) {
}
}
}
- func fillTemplate(_ template: String, with values: [String: String]) -> String {
+ /**
+ * 填充模板字符串,使用正则表达式匹配 {key} 格式的占位符
+ * 对应 Kotlin 版本的 fillTemplate 方法
+ */
+ func fillTemplate(_ template: String, with data: [String: String]) -> String {
+ // 使用正则表达式匹配 {key} 格式的占位符
+ let regex = try! NSRegularExpression(pattern: "\\{([a-zA-Z0-9_]+)\\}", options: [])
+ let range = NSRange(location: 0, length: template.utf16.count)
+
var result = template
- for (key, value) in values {
- result = result.replacingOccurrences(of: "{\(key)}", with: value)
+ let matches = regex.matches(in: template, options: [], range: range).reversed()
+
+ // 从后往前替换,避免位置偏移问题
+ for match in matches {
+ if match.numberOfRanges >= 2 {
+ let keyRange = match.range(at: 1)
+ if let swiftKeyRange = Range(keyRange, in: template) {
+ let key = String(template[swiftKeyRange])
+ let value = data[key] ?? "未知"
+
+ let fullMatchRange = match.range(at: 0)
+ if let swiftFullRange = Range(fullMatchRange, in: result) {
+ result.replaceSubrange(swiftFullRange, with: value)
+ }
+ }
+ }
}
+
return result
}
func clearChatHistory() -> Bool {
diff --git a/local_plugins/agent_service/lib/agent_service.dart b/local_plugins/agent_service/lib/agent_service.dart
index ede6b56dc..5871bd8fe 100644
--- a/local_plugins/agent_service/lib/agent_service.dart
+++ b/local_plugins/agent_service/lib/agent_service.dart
@@ -332,20 +332,7 @@ class AgentService {
}
}
- /// 设置支持的语音识别语言
- ///
- /// [languages] 支持的语言列表,如 ['zh-CN', 'en-US']
- /// 返回是否设置成功
- static Future setSupportedLanguages(List languages) async {
- try {
- final bool result = await _channel.invokeMethod('setSupportedLanguages', {
- 'languages': languages,
- });
- return result;
- } on PlatformException catch (e) {
- throw AgentServiceException(e.code, e.message ?? '设置支持语言失败', e.details);
- }
- }
+
/// 停止语音合成
static Future stopAiSteam() async {
diff --git a/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortrait.swift b/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortrait.swift
new file mode 100644
index 000000000..e39ae8e5d
--- /dev/null
+++ b/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortrait.swift
@@ -0,0 +1,156 @@
+import Foundation
+import UIKit
+
+public class UserPortrait: NSObject {
+ private static let TAG = "UserPortrait"
+ private static let PREF_NAME = "deep_voice_user"
+
+ // 单例实例
+ public static let shared = UserPortrait()
+
+ private var appContext: UIApplication?
+ private var userDefaults: UserDefaults?
+ private var portrait: [String: Any] = [:]
+
+ // 私有初始化方法,防止外部创建实例
+ private override init() {
+ super.init()
+ }
+
+ /**
+ * 初始化用户画像
+ */
+ public func initialize() {
+ do {
+ // 注册通知监听器
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleUpdatePortrait(_:)),
+ name: NSNotification.Name("com.yunqiinnovation.user_portrait.UPDATE_PORTRAIT"),
+ object: nil
+ )
+
+ // 获取 UserDefaults
+ appContext = UIApplication.shared
+ userDefaults = UserDefaults(suiteName: UserPortrait.PREF_NAME) ?? UserDefaults.standard
+
+ // 加载已保存的画像数据
+ loadPortraitFromDefaults()
+
+ print("\(UserPortrait.TAG): 用户画像初始化成功")
+ } catch {
+ print("\(UserPortrait.TAG): 初始化用户画像时发生异常: \(error.localizedDescription)")
+ }
+ }
+
+ /**
+ * 处理更新画像通知
+ */
+ @objc private func handleUpdatePortrait(_ notification: Notification) {
+ print("\(UserPortrait.TAG): 接收到通知事件 \(notification.name.rawValue)")
+
+ guard let userInfo = notification.userInfo,
+ let key = userInfo["key"] as? String,
+ let value = userInfo["value"] as? String else {
+ print("\(UserPortrait.TAG): 通知数据格式错误")
+ return
+ }
+
+ print("\(UserPortrait.TAG): 收到 UPDATE_PORTRAIT 通知 \(key):\(value)")
+
+ if !key.isEmpty {
+ print("\(UserPortrait.TAG): 接收到画像更新:\(key) = \(value)")
+ set(key: key, value: value)
+ }
+ }
+
+ /**
+ * 从 UserDefaults 加载画像数据
+ */
+ private func loadPortraitFromDefaults() {
+ guard let defaults = userDefaults else { return }
+
+ // 获取所有保存的键值对
+ let allKeys = defaults.dictionaryRepresentation().keys
+ for key in allKeys {
+ if let value = defaults.object(forKey: key) {
+ portrait[key] = value
+ }
+ }
+ }
+
+ /**
+ * 获取所有画像数据
+ */
+ public func getAll() -> [String: String] {
+ return portrait.compactMapValues { value in
+ return String(describing: value)
+ }
+ }
+
+ /**
+ * 设置画像字段
+ */
+ public func set(key: String, value: String) {
+ userDefaults?.set(value, forKey: key)
+ userDefaults?.synchronize() // 立即同步到磁盘
+
+ // 更新内存中的画像数据
+ portrait[key] = value
+
+ print("\(UserPortrait.TAG): 设置画像字段 \(key) = \(value)")
+ }
+
+ /**
+ * 获取指定键的画像值
+ */
+ public func get(key: String) -> String? {
+ return portrait[key] as? String
+ }
+
+ /**
+ * 清空所有画像数据
+ */
+ public func clean() {
+ portrait.removeAll()
+
+ // 清空 UserDefaults 中的数据
+ if let defaults = userDefaults {
+ let allKeys = defaults.dictionaryRepresentation().keys
+ for key in allKeys {
+ defaults.removeObject(forKey: key)
+ }
+ defaults.synchronize()
+ }
+
+ print("\(UserPortrait.TAG): 已清空所有画像数据")
+ }
+
+ /**
+ * 发送更新画像通知(用于其他模块触发更新)
+ */
+ public func sendUpdateNotification(key: String, value: String) {
+ let userInfo: [String: Any] = [
+ "key": key,
+ "value": value
+ ]
+
+ NotificationCenter.default.post(
+ name: NSNotification.Name("com.yunqiinnovation.user_portrait.UPDATE_PORTRAIT"),
+ object: nil,
+ userInfo: userInfo
+ )
+ }
+
+ /**
+ * 移除通知监听器(在应用退出时调用)
+ */
+ public func deinitialize() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: NSNotification.Name("com.yunqiinnovation.user_portrait.UPDATE_PORTRAIT"),
+ object: nil
+ )
+ print("\(UserPortrait.TAG): 用户画像已清理")
+ }
+}