Browse Source

fix(agent_service): 修复通话模式结束后音频会话未重置的问题

fix(azure_speech): 适配iOS 18以下版本的音频会话配置和播放逻辑
修复iOS 17及以下版本流式播放可能卡住的问题

refactor(chat_storage): 使用串行队列保护数据库访问
确保所有数据库操作线程安全,避免并发问题
newdev_shunjiawei
liwei1dao 9 months ago
parent
commit
03e0d94b5a
  1. 28
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  2. 125
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
  3. 506
      local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift

28
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -576,29 +576,43 @@ class AgentServiceImpl: NSObject {
return true return true
} }
// 关键修复:如果正在结束的是通话模式,则立即将会话重置为 normal 状态
if self.currentRecognitionMode == "phone_call" {
os_log("通话模式结束,强制重置音频会话至 'normal' 状态", log: logger, type: .info)
self.currentRecognitionMode = "normal"
// 使用 normal 模式的配置来清理和重置音频会话
self.checkAdnSetAudioSession()
}
os_log("停止语音识别,当前模式: %{public}@", log: logger, type: .info, currentRecognitionMode) os_log("停止语音识别,当前模式: %{public}@", log: logger, type: .info, currentRecognitionMode)
let success = azureAsrHelper?.stopContinuousRecognition() ?? false let success = azureAsrHelper?.stopContinuousRecognition() ?? false
if success {
if !success && isStartingRecognition { // 识别结束后,立即停用 AudioSession,以便其他模块(如TTS)可以接管
// 这是解决“按住说话”后无法播报问题的关键
do {
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
os_log("Audio session deactivated successfully after recognition.", log: logger, type: .info)
} catch {
os_log("Failed to deactivate audio session: %{public}@", log: logger, type: .error, error.localizedDescription)
}
} else if isStartingRecognition {
// 启动尚未完成,先记录一次待停止请求,onSessionStarted 到来后立即 stop // 启动尚未完成,先记录一次待停止请求,onSessionStarted 到来后立即 stop
stopRequestedDuringStart = true stopRequestedDuringStart = true
// 即使底层 stop 失败,但因为我们已经记录了停止请求,所以对调用方来说,可以认为是成功的
// 后续的 onSessionStarted 会处理真正的停止逻辑
} }
stopIdleCheck() stopIdleCheck()
// 只有之前在播放时才恢复播放 // 只有之前在播放时才恢复播放
if wasMusicPlayingBeforeRecognition { if wasMusicPlayingBeforeRecognition {
// QPlayAutoManager.shared.play()
wasMusicPlayingBeforeRecognition = false wasMusicPlayingBeforeRecognition = false
MusicService.shared.resume() MusicService.shared.resume()
} }
// 重置识别模式
// currentRecognitionMode = "normal"
// 启动中但已请求停止,也视为已接受停止请求 // 启动中但已请求停止,也视为已接受停止请求
return success || isStartingRecognition return success || stopRequestedDuringStart
} }
func disableBluetoothAudio() { func disableBluetoothAudio() {
azureAsrHelper?.disableBluetoothAudio() azureAsrHelper?.disableBluetoothAudio()

125
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift

@ -848,33 +848,70 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate {
*/ */
private func scheduleAvailablePcmBuffers() { private func scheduleAvailablePcmBuffers() {
guard ensureStreamingEngineIfNeeded() else { return } guard ensureStreamingEngineIfNeeded() else { return }
if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes {
// os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount) if #available(iOS 18.0, *) {
} // iOS 18及以上版本,使用原始逻辑:先调度,后播放
while pcmPendingData.count >= streamChunkBytes { if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes {
let chunk = pcmPendingData.prefix(streamChunkBytes) // os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount)
pcmPendingData.removeFirst(streamChunkBytes)
guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue }
scheduledBufferCount += 1
if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true
// os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid)
notifyEvent(eventType: .playbackStarted)
} }
playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in while pcmPendingData.count >= streamChunkBytes {
guard let self = self else { return } let chunk = pcmPendingData.prefix(streamChunkBytes)
self.audioPlaybackQueue.async { pcmPendingData.removeFirst(streamChunkBytes)
self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1) guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue }
self.checkStreamPlaybackCompletedIfNeeded() scheduledBufferCount += 1
if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true
// os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid)
notifyEvent(eventType: .playbackStarted)
} }
}) playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in
} guard let self = self else { return }
if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty { self.audioPlaybackQueue.async {
if pcmPendingData.count % 2 != 0 { self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1)
pcmPendingData.removeLast() self.checkStreamPlaybackCompletedIfNeeded()
}
})
} }
if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) { if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty {
pcmPendingData.removeAll(keepingCapacity: true) if pcmPendingData.count % 2 != 0 {
pcmPendingData.removeLast()
}
if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) {
pcmPendingData.removeAll(keepingCapacity: true)
scheduledBufferCount += 1
if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true
// os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid)
notifyEvent(eventType: .playbackStarted)
}
playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in
guard let self = self else { return }
self.audioPlaybackQueue.async {
self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1)
self.checkStreamPlaybackCompletedIfNeeded()
}
})
}
}
if playerNode?.isPlaying == false, scheduledBufferCount > 0 {
// os_log("调用链: playerNode.play scheduled=%{public}d pendingPcm=%{public}d", log: log, type: .debug, scheduledBufferCount, pcmPendingData.count)
playerNode?.play()
}
} else {
// iOS 17及以下版本,使用修复逻辑:先播放,后调度
if playerNode?.isPlaying == false {
// os_log("调用链: playerNode.play 提前启动播放器", log: log, type: .debug)
playerNode?.play()
}
if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes {
// os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount)
}
while pcmPendingData.count >= streamChunkBytes {
let chunk = pcmPendingData.prefix(streamChunkBytes)
pcmPendingData.removeFirst(streamChunkBytes)
guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue }
scheduledBufferCount += 1 scheduledBufferCount += 1
if !hasNotifiedPlaybackStartedForStream { if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true hasNotifiedPlaybackStartedForStream = true
@ -889,10 +926,27 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate {
} }
}) })
} }
} if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty {
if playerNode?.isPlaying == false, scheduledBufferCount > 0 { if pcmPendingData.count % 2 != 0 {
// os_log("调用链: playerNode.play scheduled=%{public}d pendingPcm=%{public}d", log: log, type: .debug, scheduledBufferCount, pcmPendingData.count) pcmPendingData.removeLast()
playerNode?.play() }
if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) {
pcmPendingData.removeAll(keepingCapacity: true)
scheduledBufferCount += 1
if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true
// os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid)
notifyEvent(eventType: .playbackStarted)
}
playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in
guard let self = self else { return }
self.audioPlaybackQueue.async {
self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1)
self.checkStreamPlaybackCompletedIfNeeded()
}
})
}
}
} }
} }
@ -1367,7 +1421,11 @@ private func applyAudioSessionForTTS() -> Bool {
desiredCategory = .playAndRecord desiredCategory = .playAndRecord
if isHeadphonesConnected { if isHeadphonesConnected {
desiredMode = .voiceChat desiredMode = .voiceChat
desiredOptions = [.allowBluetooth, .duckOthers] if #available(iOS 18.0, *) {
desiredOptions = [.allowBluetooth, .duckOthers]
} else {
desiredOptions = [.allowBluetooth, .allowBluetoothA2DP]
}
} else { } else {
desiredMode = .videoChat desiredMode = .videoChat
desiredOptions = [.allowBluetooth, .defaultToSpeaker] desiredOptions = [.allowBluetooth, .defaultToSpeaker]
@ -1404,7 +1462,14 @@ private func applyAudioSessionForTTS() -> Bool {
} else { } else {
desiredCategory = .playback desiredCategory = .playback
desiredMode = .default desiredMode = .default
desiredOptions = [.duckOthers] if #available(iOS 18.0, *) {
desiredOptions = []
} else {
desiredOptions = [.defaultToSpeaker]
}
if otherAudioPlaying {
desiredOptions.insert(.duckOthers)
}
} }
do { do {

506
local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift

@ -15,6 +15,8 @@ public class ChatStorageHelper {
private let dbPath: String private let dbPath: String
private let logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper") private let logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper")
private let dbQueue = DispatchQueue(label: "com.yunqiinnovation.chat_storage.dbQueue")
private init() { private init() {
// 获取文档目录路径(不变) // 获取文档目录路径(不变)
let fileURL = try! FileManager.default let fileURL = try! FileManager.default
@ -23,39 +25,43 @@ public class ChatStorageHelper {
dbPath = fileURL.path dbPath = fileURL.path
// 打开数据库(不变) dbQueue.sync {
if sqlite3_open(dbPath, &db) != SQLITE_OK { // 打开数据库(不变)
let errmsg = String(cString: sqlite3_errmsg(db)!) if sqlite3_open(dbPath, &db) != SQLITE_OK {
os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg) let errmsg = String(cString: sqlite3_errmsg(db)!)
return os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg)
} return
}
// 关键修改:新增 UNIQUE (agent_id, session_id, sender) 联合唯一索引 // 关键修改:新增 UNIQUE (agent_id, session_id, sender) 联合唯一索引
let createTableString = """ let createTableString = """
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL, agent_id TEXT NOT NULL,
session_id TEXT NOT NULL, session_id TEXT NOT NULL,
message TEXT NOT NULL, message TEXT NOT NULL,
sender TEXT NOT NULL, sender TEXT NOT NULL,
metadata TEXT, metadata TEXT,
created_at INTEGER NOT NULL, created_at INTEGER NOT NULL,
UNIQUE (agent_id, session_id, sender) ON CONFLICT REPLACE UNIQUE (agent_id, session_id, sender) ON CONFLICT REPLACE
); );
CREATE INDEX IF NOT EXISTS idx_agent_id ON messages (agent_id); CREATE INDEX IF NOT EXISTS idx_agent_id ON messages (agent_id);
CREATE INDEX IF NOT EXISTS idx_session_id ON messages (session_id); CREATE INDEX IF NOT EXISTS idx_session_id ON messages (session_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at); CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at);
""" """
if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK { if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!) let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg) os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg)
}
} }
} }
deinit { deinit {
if db != nil { dbQueue.sync {
sqlite3_close(db) if db != nil {
sqlite3_close(db)
}
} }
} }
@ -68,48 +74,48 @@ public class ChatStorageHelper {
* @return 插入的消息ID,失败则返回-1 * @return 插入的消息ID,失败则返回-1
*/ */
public func saveMessage(agentId: String, sessionId: String, message: String, sender: String, metadata: String?) -> Int64 { public func saveMessage(agentId: String, sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
// 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新 var result: Int64 = -1
let insertStatementString = """ dbQueue.sync {
INSERT OR REPLACE INTO messages // 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新
(agent_id, session_id, message, sender, metadata, created_at) let insertStatementString = """
VALUES (?, ?, ?, ?, ?, ?); INSERT OR REPLACE INTO messages
""" (agent_id, session_id, message, sender, metadata, created_at)
var insertStatement: OpaquePointer? VALUES (?, ?, ?, ?, ?, ?);
"""
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK { var insertStatement: OpaquePointer?
// 绑定参数(逻辑不变,确保三个唯一字段正确传入)
sqlite3_bind_text(insertStatement, 1, (agentId as NSString).utf8String, -1, nil) if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
sqlite3_bind_text(insertStatement, 2, (sessionId as NSString).utf8String, -1, nil) // 绑定参数(逻辑不变,确保三个唯一字段正确传入)
sqlite3_bind_text(insertStatement, 3, (message as NSString).utf8String, -1, nil) sqlite3_bind_text(insertStatement, 1, (agentId as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 4, (sender as NSString).utf8String, -1, nil) sqlite3_bind_text(insertStatement, 2, (sessionId as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 3, (message as NSString).utf8String, -1, nil)
if let metadata = metadata { sqlite3_bind_text(insertStatement, 4, (sender as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 5, (metadata as NSString).utf8String, -1, nil)
} else { if let metadata = metadata {
sqlite3_bind_null(insertStatement, 5) sqlite3_bind_text(insertStatement, 5, (metadata as NSString).utf8String, -1, nil)
} } else {
sqlite3_bind_null(insertStatement, 5)
}
let currentTime = Int(Date().timeIntervalSince1970) let currentTime = Int(Date().timeIntervalSince1970)
sqlite3_bind_int(insertStatement, 6, Int32(currentTime)) sqlite3_bind_int(insertStatement, 6, Int32(currentTime))
// 执行语句(冲突时会自动替换,返回新的 rowid) // 执行语句(冲突时会自动替换,返回新的 rowid)
if sqlite3_step(insertStatement) == SQLITE_DONE { if sqlite3_step(insertStatement) == SQLITE_DONE {
let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除) let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除)
os_log("消息保存成功(新增/更新),id: %{public}lld", log: logger, type: .info, id)
result = id
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入/更新消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(insertStatement) sqlite3_finalize(insertStatement)
os_log("消息保存成功(新增/更新),id: %{public}lld", log: logger, type: .info, id)
return id
} else { } else {
let errmsg = String(cString: sqlite3_errmsg(db)!) let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入/更新消息失败: %{public}@", log: logger, type: .error, errmsg) os_log("插入/更新消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
} }
sqlite3_finalize(insertStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入/更新消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
} }
return result
return -1
} }
/** /**
@ -120,122 +126,106 @@ public class ChatStorageHelper {
* @return 消息列表的JSON字符串 * @return 消息列表的JSON字符串
*/ */
public func getMessages(agentId: String, page: Int, pageSize: Int) -> String { public func getMessages(agentId: String, page: Int, pageSize: Int) -> String {
let offset = (page - 1) * pageSize var result = "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}"
var messagesArray: [[String: Any]] = [] dbQueue.sync {
let offset = (page - 1) * pageSize
// 首先获取总记录数 var messagesArray: [[String: Any]] = []
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer? // 首先获取总记录数
var totalCount = 0 let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { var totalCount = 0
sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW { if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
totalCount = Int(sqlite3_column_int(countStatement, 0)) sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
totalCount = Int(sqlite3_column_int(countStatement, 0))
}
sqlite3_finalize(countStatement)
} }
sqlite3_finalize(countStatement)
}
// 计算总页数 // 计算总页数
let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize
let queryString = """
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
"""
let queryString = """ var queryStatement: OpaquePointer?
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
"""
var queryStatement: OpaquePointer? if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil)
sqlite3_bind_int(queryStatement, 2, Int32(pageSize))
sqlite3_bind_int(queryStatement, 3, Int32(offset))
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { while sqlite3_step(queryStatement) == SQLITE_ROW {
sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) let id = sqlite3_column_int(queryStatement, 0)
sqlite3_bind_int(queryStatement, 2, Int32(pageSize))
sqlite3_bind_int(queryStatement, 3, Int32(offset))
while sqlite3_step(queryStatement) == SQLITE_ROW { let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let id = sqlite3_column_int(queryStatement, 0) let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let sessionIdPtr = sqlite3_column_text(queryStatement, 1) let messagePtr = sqlite3_column_text(queryStatement, 2)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : "" let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2) let senderPtr = sqlite3_column_text(queryStatement, 3)
let message = messagePtr != nil ? String(cString: messagePtr!) : "" let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3) let metadataPtr = sqlite3_column_text(queryStatement, 4)
let sender = senderPtr != nil ? String(cString: senderPtr!) : "" let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let metadataPtr = sqlite3_column_text(queryStatement, 4) let createdAt = sqlite3_column_int(queryStatement, 5)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 5) // 将时间戳转换为ISO 8601格式的字符串
let date = Date(timeIntervalSince1970: TimeInterval(createdAt))
let formatter = ISO8601DateFormatter()
let timestamp = formatter.string(from: date)
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .info, timestamp)
// 将时间戳转换为ISO 8601格式的字符串 var messageDict: [String: Any] = [
let date = Date(timeIntervalSince1970: TimeInterval(createdAt)) "id": id,
let formatter = ISO8601DateFormatter() "agentId": agentId, // 添加agentId字段
let timestamp = formatter.string(from: date) "sessionId": sessionId, // 添加sessionId字段
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .info, timestamp) "message": message,
"sender": sender,
"timestamp": timestamp // 使用timestamp而不是created_at
]
var messageDict: [String: Any] = [ if let metadata = metadata {
"id": id, messageDict["metadata"] = metadata
"agentId": agentId, // 添加agentId字段 }
"sessionId": sessionId, // 添加sessionId字段
"message": message,
"sender": sender,
"timestamp": timestamp // 使用timestamp而不是created_at
]
if let metadata = metadata { messagesArray.append(messageDict)
messageDict["metadata"] = metadata
} }
messagesArray.append(messageDict) sqlite3_finalize(queryStatement)
}
sqlite3_finalize(queryStatement) // 构建与Android版本一致的返回格式
let resultDict: [String: Any] = [
// 构建与Android版本一致的返回格式 "messages": messagesArray,
let result: [String: Any] = [ "page": page,
"messages": messagesArray, "pageSize": pageSize,
"page": page, "totalCount": totalCount,
"pageSize": pageSize, "totalPages": totalPages
"totalCount": totalCount, ]
"totalPages": totalPages os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray)
] do {
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray) let jsonData = try JSONSerialization.data(withJSONObject: resultDict, options: [])
do { if let jsonString = String(data: jsonData, encoding: .utf8) {
let jsonData = try JSONSerialization.data(withJSONObject: result, options: []) result = jsonString
if let jsonString = String(data: jsonData, encoding: .utf8) { }
return jsonString } catch {
os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
} }
} catch { } else {
os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription) let errmsg = String(cString: sqlite3_errmsg(db)!)
} os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg)
}
// 返回空结果,但保持格式一致
let emptyResult: [String: Any] = [
"messages": [],
"page": page,
"pageSize": pageSize,
"totalCount": 0,
"totalPages": 0
]
do {
let jsonData = try JSONSerialization.data(withJSONObject: emptyResult, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
} }
} catch {
os_log("getMessages: 空结果JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
} }
return result
return "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}"
} }
/** /**
@ -245,56 +235,55 @@ public class ChatStorageHelper {
* @return 是否删除成功 * @return 是否删除成功
*/ */
public func deleteMessages(agentId: String?, messageIds: [Int]?) -> Bool { public func deleteMessages(agentId: String?, messageIds: [Int]?) -> Bool {
if let agentId = agentId { var result = false
let deleteString = "DELETE FROM messages WHERE agent_id = ?;" dbQueue.sync {
var deleteStatement: OpaquePointer? if let agentId = agentId {
let deleteString = "DELETE FROM messages WHERE agent_id = ?;"
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK { var deleteStatement: OpaquePointer?
sqlite3_bind_text(deleteStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
sqlite3_bind_text(deleteStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(deleteStatement) == SQLITE_DONE {
sqlite3_finalize(deleteStatement)
result = true
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除会话消息失败: %{public}@", log: logger, type: .error, errmsg)
}
if sqlite3_step(deleteStatement) == SQLITE_DONE {
sqlite3_finalize(deleteStatement) sqlite3_finalize(deleteStatement)
return true
} else { } else {
let errmsg = String(cString: sqlite3_errmsg(db)!) let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除会话消息失败: %{public}@", log: logger, type: .error, errmsg) os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(deleteStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
} else if let messageIds = messageIds, !messageIds.isEmpty {
// 构建参数占位符
let placeholders = String(repeating: "?,", count: messageIds.count).dropLast()
let deleteString = "DELETE FROM messages WHERE id IN (\(placeholders));"
var deleteStatement: OpaquePointer?
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
for (index, id) in messageIds.enumerated() {
sqlite3_bind_int(deleteStatement, Int32(index + 1), Int32(id))
} }
} else if let messageIds = messageIds, !messageIds.isEmpty {
if sqlite3_step(deleteStatement) == SQLITE_DONE { // 构建参数占位符
let placeholders = String(repeating: "?,", count: messageIds.count).dropLast()
let deleteString = "DELETE FROM messages WHERE id IN (\(placeholders));"
var deleteStatement: OpaquePointer?
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
for (index, id) in messageIds.enumerated() {
sqlite3_bind_int(deleteStatement, Int32(index + 1), Int32(id))
}
if sqlite3_step(deleteStatement) == SQLITE_DONE {
result = true
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除特定消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(deleteStatement) sqlite3_finalize(deleteStatement)
return true
} else { } else {
let errmsg = String(cString: sqlite3_errmsg(db)!) let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除特定消息失败: %{public}@", log: logger, type: .error, errmsg) os_log("删除特定消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
} }
sqlite3_finalize(deleteStatement)
} else { } else {
let errmsg = String(cString: sqlite3_errmsg(db)!) os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error)
os_log("删除特定消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
} }
} else {
os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error)
return false
} }
return result
return false
} }
/** /**
@ -302,15 +291,18 @@ public class ChatStorageHelper {
* @return 是否清空成功 * @return 是否清空成功
*/ */
public func clearDatabase() -> Bool { public func clearDatabase() -> Bool {
let deleteString = "DELETE FROM messages;" var result = false
dbQueue.sync {
if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK { let deleteString = "DELETE FROM messages;"
return true
} else { if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!) result = true
os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg) } else {
return false let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg)
}
} }
return result
} }
/** /**
@ -321,75 +313,75 @@ public class ChatStorageHelper {
*/ */
public func getRecentMessages(agentId: String, limit: Int) -> [[String: Any]] { public func getRecentMessages(agentId: String, limit: Int) -> [[String: Any]] {
var messages: [[String: Any]] = [] var messages: [[String: Any]] = []
dbQueue.sync {
// 首先检查数据库中是否有该会话的消息 // 首先检查数据库中是否有该会话的消息
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?" let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer? var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil) sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW { if sqlite3_step(countStatement) == SQLITE_ROW {
_ = Int(sqlite3_column_int(countStatement, 0)) _ = Int(sqlite3_column_int(countStatement, 0))
}
sqlite3_finalize(countStatement)
} }
sqlite3_finalize(countStatement)
}
// 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序 // 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
let queryString = """ let queryString = """
SELECT * FROM ( SELECT * FROM (
SELECT id, session_id, message, sender, metadata, created_at SELECT id, session_id, message, sender, metadata, created_at
FROM messages FROM messages
WHERE agent_id = ? WHERE agent_id = ?
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT ? LIMIT ?
) tmp ORDER BY created_at ASC ) tmp ORDER BY created_at ASC
""" """
var queryStatement: OpaquePointer? var queryStatement: OpaquePointer?
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil)
sqlite3_bind_int(queryStatement, 2, Int32(limit)) sqlite3_bind_int(queryStatement, 2, Int32(limit))
while sqlite3_step(queryStatement) == SQLITE_ROW { while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0) let id = sqlite3_column_int(queryStatement, 0)
let sessionIdPtr = sqlite3_column_text(queryStatement, 1) let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : "" let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2) let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : "" let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3) let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : "" let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 4) let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 5) let createdAt = sqlite3_column_int(queryStatement, 5)
var messageDict: [String: Any] = [ var messageDict: [String: Any] = [
"id": id, "id": id,
"sessionId":sessionId, "sessionId":sessionId,
"message": message, "message": message,
"sender": sender, "sender": sender,
"created_at": createdAt "created_at": createdAt
] ]
if let metadata = metadata { if let metadata = metadata {
messageDict["metadata"] = metadata messageDict["metadata"] = metadata
}
messages.append(messageDict)
} }
messages.append(messageDict) sqlite3_finalize(queryStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("查询最近消息失败: %{public}@", log: logger, type: .error, errmsg)
} }
sqlite3_finalize(queryStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("查询最近消息失败: %{public}@", log: logger, type: .error, errmsg)
} }
return messages return messages
} }
} }

Loading…
Cancel
Save