Browse Source

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

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

refactor(chat_storage): 使用串行队列保护数据库访问
确保所有数据库操作线程安全,避免并发问题
weicu
liwei1dao 9 months ago
parent
commit
cae3352a42
  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
}
// 关键修复:如果正在结束的是通话模式,则立即将会话重置为 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)
let success = azureAsrHelper?.stopContinuousRecognition() ?? false
if !success && isStartingRecognition {
if success {
// 识别结束后,立即停用 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
stopRequestedDuringStart = true
// 即使底层 stop 失败,但因为我们已经记录了停止请求,所以对调用方来说,可以认为是成功的
// 后续的 onSessionStarted 会处理真正的停止逻辑
}
stopIdleCheck()
// 只有之前在播放时才恢复播放
if wasMusicPlayingBeforeRecognition {
// QPlayAutoManager.shared.play()
wasMusicPlayingBeforeRecognition = false
MusicService.shared.resume()
}
// 重置识别模式
// currentRecognitionMode = "normal"
// 启动中但已请求停止,也视为已接受停止请求
return success || isStartingRecognition
return success || stopRequestedDuringStart
}
func 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() {
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)
}
while pcmPendingData.count >= streamChunkBytes {
let chunk = pcmPendingData.prefix(streamChunkBytes)
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)
if #available(iOS 18.0, *) {
// iOS 18及以上版本,使用原始逻辑:先调度,后播放
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)
}
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()
while pcmPendingData.count >= streamChunkBytes {
let chunk = pcmPendingData.prefix(streamChunkBytes)
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)
}
})
}
if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty {
if pcmPendingData.count % 2 != 0 {
pcmPendingData.removeLast()
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 !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) {
pcmPendingData.removeAll(keepingCapacity: true)
if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty {
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
if !hasNotifiedPlaybackStartedForStream {
hasNotifiedPlaybackStartedForStream = true
@ -889,10 +926,27 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate {
}
})
}
}
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()
if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty {
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()
}
})
}
}
}
}
@ -1367,7 +1421,11 @@ private func applyAudioSessionForTTS() -> Bool {
desiredCategory = .playAndRecord
if isHeadphonesConnected {
desiredMode = .voiceChat
desiredOptions = [.allowBluetooth, .duckOthers]
if #available(iOS 18.0, *) {
desiredOptions = [.allowBluetooth, .duckOthers]
} else {
desiredOptions = [.allowBluetooth, .allowBluetoothA2DP]
}
} else {
desiredMode = .videoChat
desiredOptions = [.allowBluetooth, .defaultToSpeaker]
@ -1404,7 +1462,14 @@ private func applyAudioSessionForTTS() -> Bool {
} else {
desiredCategory = .playback
desiredMode = .default
desiredOptions = [.duckOthers]
if #available(iOS 18.0, *) {
desiredOptions = []
} else {
desiredOptions = [.defaultToSpeaker]
}
if otherAudioPlaying {
desiredOptions.insert(.duckOthers)
}
}
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 logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper")
private let dbQueue = DispatchQueue(label: "com.yunqiinnovation.chat_storage.dbQueue")
private init() {
// 获取文档目录路径(不变)
let fileURL = try! FileManager.default
@ -23,39 +25,43 @@ public class ChatStorageHelper {
dbPath = fileURL.path
// 打开数据库(不变)
if sqlite3_open(dbPath, &db) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg)
return
}
dbQueue.sync {
// 打开数据库(不变)
if sqlite3_open(dbPath, &db) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg)
return
}
// 关键修改:新增 UNIQUE (agent_id, session_id, sender) 联合唯一索引
let createTableString = """
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL,
session_id TEXT NOT NULL,
message TEXT NOT NULL,
sender TEXT NOT NULL,
metadata TEXT,
created_at INTEGER NOT NULL,
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_session_id ON messages (session_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at);
"""
if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg)
// 关键修改:新增 UNIQUE (agent_id, session_id, sender) 联合唯一索引
let createTableString = """
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL,
session_id TEXT NOT NULL,
message TEXT NOT NULL,
sender TEXT NOT NULL,
metadata TEXT,
created_at INTEGER NOT NULL,
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_session_id ON messages (session_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at);
"""
if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg)
}
}
}
deinit {
if db != nil {
sqlite3_close(db)
dbQueue.sync {
if db != nil {
sqlite3_close(db)
}
}
}
@ -68,48 +74,48 @@ public class ChatStorageHelper {
* @return 插入的消息ID,失败则返回-1
*/
public func saveMessage(agentId: String, sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
// 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新
let insertStatementString = """
INSERT OR REPLACE INTO messages
(agent_id, session_id, message, sender, metadata, created_at)
VALUES (?, ?, ?, ?, ?, ?);
"""
var insertStatement: OpaquePointer?
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
// 绑定参数(逻辑不变,确保三个唯一字段正确传入)
sqlite3_bind_text(insertStatement, 1, (agentId 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)
sqlite3_bind_text(insertStatement, 4, (sender as NSString).utf8String, -1, nil)
if let metadata = metadata {
sqlite3_bind_text(insertStatement, 5, (metadata as NSString).utf8String, -1, nil)
} else {
sqlite3_bind_null(insertStatement, 5)
}
var result: Int64 = -1
dbQueue.sync {
// 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新
let insertStatementString = """
INSERT OR REPLACE INTO messages
(agent_id, session_id, message, sender, metadata, created_at)
VALUES (?, ?, ?, ?, ?, ?);
"""
var insertStatement: OpaquePointer?
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
// 绑定参数(逻辑不变,确保三个唯一字段正确传入)
sqlite3_bind_text(insertStatement, 1, (agentId 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)
sqlite3_bind_text(insertStatement, 4, (sender as NSString).utf8String, -1, nil)
if let metadata = metadata {
sqlite3_bind_text(insertStatement, 5, (metadata as NSString).utf8String, -1, nil)
} else {
sqlite3_bind_null(insertStatement, 5)
}
let currentTime = Int(Date().timeIntervalSince1970)
sqlite3_bind_int(insertStatement, 6, Int32(currentTime))
let currentTime = Int(Date().timeIntervalSince1970)
sqlite3_bind_int(insertStatement, 6, Int32(currentTime))
// 执行语句(冲突时会自动替换,返回新的 rowid)
if sqlite3_step(insertStatement) == SQLITE_DONE {
let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除)
// 执行语句(冲突时会自动替换,返回新的 rowid)
if sqlite3_step(insertStatement) == SQLITE_DONE {
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)
os_log("消息保存成功(新增/更新),id: %{public}lld", log: logger, type: .info, id)
return id
} else {
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 -1
return result
}
/**
@ -120,122 +126,106 @@ public class ChatStorageHelper {
* @return 消息列表的JSON字符串
*/
public func getMessages(agentId: String, page: Int, pageSize: Int) -> String {
let offset = (page - 1) * pageSize
var messagesArray: [[String: Any]] = []
// 首先获取总记录数
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer?
var totalCount = 0
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
totalCount = Int(sqlite3_column_int(countStatement, 0))
var result = "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}"
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
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
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 = """
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
"""
var queryStatement: OpaquePointer?
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 {
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))
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 5)
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格式的字符串
let date = Date(timeIntervalSince1970: TimeInterval(createdAt))
let formatter = ISO8601DateFormatter()
let timestamp = formatter.string(from: date)
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .info, timestamp)
var messageDict: [String: Any] = [
"id": id,
"agentId": agentId, // 添加agentId字段
"sessionId": sessionId, // 添加sessionId字段
"message": message,
"sender": sender,
"timestamp": timestamp // 使用timestamp而不是created_at
]
var messageDict: [String: Any] = [
"id": id,
"agentId": agentId, // 添加agentId字段
"sessionId": sessionId, // 添加sessionId字段
"message": message,
"sender": sender,
"timestamp": timestamp // 使用timestamp而不是created_at
]
if let metadata = metadata {
messageDict["metadata"] = metadata
}
if let metadata = metadata {
messageDict["metadata"] = metadata
messagesArray.append(messageDict)
}
messagesArray.append(messageDict)
}
sqlite3_finalize(queryStatement)
sqlite3_finalize(queryStatement)
// 构建与Android版本一致的返回格式
let result: [String: Any] = [
"messages": messagesArray,
"page": page,
"pageSize": pageSize,
"totalCount": totalCount,
"totalPages": totalPages
]
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray)
do {
let jsonData = try JSONSerialization.data(withJSONObject: result, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
// 构建与Android版本一致的返回格式
let resultDict: [String: Any] = [
"messages": messagesArray,
"page": page,
"pageSize": pageSize,
"totalCount": totalCount,
"totalPages": totalPages
]
os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray)
do {
let jsonData = try JSONSerialization.data(withJSONObject: resultDict, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) {
result = jsonString
}
} catch {
os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
} catch {
os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
} 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
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg)
}
} catch {
os_log("getMessages: 空结果JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
return "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}"
return result
}
/**
@ -245,56 +235,55 @@ public class ChatStorageHelper {
* @return 是否删除成功
*/
public func deleteMessages(agentId: String?, messageIds: [Int]?) -> Bool {
if let agentId = agentId {
let deleteString = "DELETE FROM messages WHERE agent_id = ?;"
var deleteStatement: OpaquePointer?
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
sqlite3_bind_text(deleteStatement, 1, (agentId as NSString).utf8String, -1, nil)
var result = false
dbQueue.sync {
if let agentId = agentId {
let deleteString = "DELETE FROM messages WHERE agent_id = ?;"
var deleteStatement: OpaquePointer?
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)
return true
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
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))
os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
if sqlite3_step(deleteStatement) == SQLITE_DONE {
} 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))
}
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)
return true
} else {
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)
os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error)
}
} else {
os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error)
return false
}
return false
return result
}
/**
@ -302,15 +291,18 @@ public class ChatStorageHelper {
* @return 是否清空成功
*/
public func clearDatabase() -> Bool {
let deleteString = "DELETE FROM messages;"
if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK {
return true
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg)
return false
var result = false
dbQueue.sync {
let deleteString = "DELETE FROM messages;"
if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK {
result = true
} else {
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]] {
var messages: [[String: Any]] = []
// 首先检查数据库中是否有该会话的消息
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
_ = Int(sqlite3_column_int(countStatement, 0))
dbQueue.sync {
// 首先检查数据库中是否有该会话的消息
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
_ = Int(sqlite3_column_int(countStatement, 0))
}
sqlite3_finalize(countStatement)
}
sqlite3_finalize(countStatement)
}
// 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
let queryString = """
SELECT * FROM (
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ?
) tmp ORDER BY created_at ASC
"""
// 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
let queryString = """
SELECT * FROM (
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ?
) tmp ORDER BY created_at ASC
"""
var queryStatement: OpaquePointer?
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(limit))
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(limit))
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let metadataPtr = sqlite3_column_text(queryStatement, 4)
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] = [
"id": id,
"sessionId":sessionId,
"message": message,
"sender": sender,
"created_at": createdAt
]
var messageDict: [String: Any] = [
"id": id,
"sessionId":sessionId,
"message": message,
"sender": sender,
"created_at": createdAt
]
if let metadata = metadata {
messageDict["metadata"] = metadata
if let 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
}
}

Loading…
Cancel
Save