Commit 10f6e30a authored by Alex朱枝文's avatar Alex朱枝文

机器人消息上报

parent d526fc42
......@@ -484,6 +484,7 @@
047AA3F82C5CC7B0009C4554 /* YHShareStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047AA3F72C5CC7B0009C4554 /* YHShareStepView.swift */; };
047AA3FA2C60739E009C4554 /* YHInvitationWithGiftsSelectItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047AA3F92C60739E009C4554 /* YHInvitationWithGiftsSelectItemView.swift */; };
047AA3FC2C61EE7F009C4554 /* YHSignAppendStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047AA3FB2C61EE7F009C4554 /* YHSignAppendStepView.swift */; };
047AB5E62CD083CE002A3573 /* YHButlerMessageReportHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047AB5E52CD083CE002A3573 /* YHButlerMessageReportHandler.swift */; };
0480582F2C7CA77900502CAA /* YHDisappointHKAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0480582E2C7CA77900502CAA /* YHDisappointHKAlertView.swift */; };
048058312C7DBDC900502CAA /* YHTravelCertificateTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048058302C7DBDC900502CAA /* YHTravelCertificateTipsView.swift */; };
048058332C7DC0CF00502CAA /* YHTravelCertificateTipsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048058322C7DC0CF00502CAA /* YHTravelCertificateTipsCell.swift */; };
......@@ -1452,6 +1453,7 @@
047AA3F72C5CC7B0009C4554 /* YHShareStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHShareStepView.swift; sourceTree = "<group>"; };
047AA3F92C60739E009C4554 /* YHInvitationWithGiftsSelectItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHInvitationWithGiftsSelectItemView.swift; sourceTree = "<group>"; };
047AA3FB2C61EE7F009C4554 /* YHSignAppendStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHSignAppendStepView.swift; sourceTree = "<group>"; };
047AB5E52CD083CE002A3573 /* YHButlerMessageReportHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHButlerMessageReportHandler.swift; sourceTree = "<group>"; };
0480582E2C7CA77900502CAA /* YHDisappointHKAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHDisappointHKAlertView.swift; sourceTree = "<group>"; };
048058302C7DBDC900502CAA /* YHTravelCertificateTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHTravelCertificateTipsView.swift; sourceTree = "<group>"; };
048058322C7DC0CF00502CAA /* YHTravelCertificateTipsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YHTravelCertificateTipsCell.swift; sourceTree = "<group>"; };
......@@ -1965,6 +1967,7 @@
0414708C2C895A1800B7B688 /* VM */,
0414708A2C88627B00B7B688 /* YHButlerServiceManager.swift */,
04B4B8D62C8AE77C00ED82BC /* YHButlerServiceMessageHandler.swift */,
047AB5E52CD083CE002A3573 /* YHButlerMessageReportHandler.swift */,
);
path = "ButlerServiceManager(银河管家)";
sourceTree = "<group>";
......@@ -5491,6 +5494,7 @@
0430E6582C7434F2000511E2 /* YHAdopterNewPeopleViewController.swift in Sources */,
A582B2452BBA4CF9009D098C /* YHHKPlanDocModel.swift in Sources */,
045EEF142B9F171A0022A143 /* YHItemModel.swift in Sources */,
047AB5E62CD083CE002A3573 /* YHButlerMessageReportHandler.swift in Sources */,
045EEED72B9F171A0022A143 /* YHEducationInfo.swift in Sources */,
047AA3D92C4A4A91009C4554 /* YHInvatationShareViewController.swift in Sources */,
045EEEDE2B9F171A0022A143 /* YHEducationInfoCell.swift in Sources */,
......
......@@ -42,4 +42,22 @@ class YHButlerServiceViewModel {
}
return nil
}
// 当收到机器人非招呼消息时,发送用户id给后端,后端触发发送企业微信
func sendUserIdInChat(_ userId: String, callBackBlock: @escaping (_ success: Bool, _ error: YHErrorModel?) -> Void) {
let params: [String: Any] = ["yh_id": userId]
let strUrl = YHBaseUrlManager.shared.curURL() + YHAllApiName.QiYu.sendUserIdInChat
_ = YHNetRequest.postRequest(url: strUrl, params: params) { json, code in
// 1. json字符串 转 对象
printLog("model 是 ==> \(json)")
if json.code == 200 {
callBackBlock(true, nil)
} else {
let err = YHErrorModel(errorCode: Int32(json.code), errorMsg: json.msg.isEmpty ? "" : json.msg)
callBackBlock(false, err)
}
} failBlock: { err in
callBackBlock(false, err)
}
}
}
//
// YHButlerMessageReportHandler.swift
// galaxy
//
// Created by alexzzw on 2024/10/29.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
// 包含重试机制和详细日志
class YHButlerMessageReportHandler {
// MARK: - Properties
private lazy var greetings: [String] = {
["尊敬的客户,您好!感谢您选择银河。我是您的智能助手小鲸,时刻准备为您解答各类疑问,如果您有任何疑惑或问题,欢迎随时向我发问,我将竭诚为您服务。", "热门问题", "[银河VIP客服 from customer service is here to help you]", "[客服银河VIP客服为您服务]"]
}()
private lazy var patterns: [String] = {
[
"^尊敬的.*您好", // 匹配开头的问候语
"我是.*智能助手", // 匹配自我介绍
"^热门问题", // 匹配开头的固定文本
"from customer service is here to help you]$", // 匹配结尾的固定文本
"客服为您服务]$", // 匹配结尾的固定文本
]
}()
private static let messageReportDateKey = "butler_message_report_date"
// QIYU_ROBOT: 用这个代表是机器人消息
private static let robotId = "QIYU_ROBOT"
private var messageReportKey: String? {
guard let userId = YHLoginManager.shared.userModel?.id, userId.count > 0 else {
return nil
}
return YHButlerMessageReportHandler.messageReportDateKey + "_\(userId)"
}
private lazy var getUserInfoViewModel: YHButlerServiceViewModel = YHButlerServiceViewModel()
private let messageQueue = DispatchQueue(label: "com.galaxy.butler.messageQueue")
private let reportQueue = DispatchQueue(label: "com.galaxy.butler.reportQueue")
private let reportSemaphore = DispatchSemaphore(value: 1)
private lazy var lastReportTime: Date? = {
return getLastReportDate()
}()
private var nightReported = false
private var dailyReportCount = 0
private let oneHourInterval: TimeInterval = 3600
private let maxRetryCount = 3
private let retryInterval: TimeInterval = 2.0
// MARK: - Public Methods
func handleMessage(_ message: QYSessionInfo) {
messageQueue.async { [weak self] in
guard let self = self else { return }
self.logCurrentStatus()
let timeAllowed = self.shouldReport()
if timeAllowed, let content = filterMessageContent(message), content.count > 0, let userId = YHLoginManager.shared.userModel?.id, userId.count > 0 {
self.reportWithRetry(message, userId, retryCount: 0)
} else {
self.logSkipReason()
}
}
}
func reset() {
messageQueue.async { [weak self] in
guard let self = self else { return }
self.lastReportTime = getLastReportDate()
self.nightReported = false
self.dailyReportCount = 0
printLog("###处理器状态已重置")
}
}
// MARK: - Private Methods
private func getLastReportDate() -> Date? {
guard let messageReportKey = messageReportKey, messageReportKey.count > 0 else {
return nil
}
return UserDefaults.standard.value(forKey: messageReportKey) as? Date
}
private func shouldReport() -> Bool {
let now = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: now)
// 检查日期变更
if let lastReport = lastReportTime {
let lastReportDay = calendar.component(.day, from: lastReport)
let currentDay = calendar.component(.day, from: now)
if lastReportDay != currentDay {
resetDailyStatus()
} else {
// 检查是否夜间已经上报了
let lastReportHour = calendar.component(.hour, from: lastReport)
nightReported = lastReportHour >= 0 && lastReportHour < 8
}
}
// 检查时间间隔
if let lastReport = lastReportTime {
let timeSinceLastReport = now.timeIntervalSince(lastReport)
if timeSinceLastReport < oneHourInterval {
return false
}
}
// 夜间规则(0-8点)
let isNightTime = hour >= 0 && hour < 8
if isNightTime && nightReported {
return false
}
return true
}
private func filterMessageContent(_ message: QYSessionInfo) -> String? {
guard let nimMessage = message.lastMessage, let from = nimMessage.from, from == YHButlerMessageReportHandler.robotId, let text = message.lastMessageText, text.count > 0 else {
return nil
}
// 完全匹配
if greetings.contains(text) {
return nil
}
// 模糊匹配
if patterns.contains(where: { pattern in
guard let regex = try? NSRegularExpression(pattern: pattern) else { return false }
let range = NSRange(text.startIndex..., in: text)
return regex.firstMatch(in: text, range: range) != nil
}) {
return nil
}
return text
}
private func resetDailyStatus() {
nightReported = false
dailyReportCount = 0
}
private func reportWithRetry(_ message: QYSessionInfo, _ userId: String, retryCount: Int) {
reportQueue.async { [weak self] in
guard let self = self else { return }
self.reportSemaphore.wait()
guard self.shouldReport() else {
self.reportSemaphore.signal()
return
}
self.getUserInfoViewModel.sendUserIdInChat(userId, callBackBlock: { [weak self] success, error in
guard let self = self else { return }
if success {
self.handleReportSuccess(message)
} else if retryCount < self.maxRetryCount {
printLog("###上报失败,准备重试 #\(retryCount + 1)")
self.reportQueue.asyncAfter(deadline: .now() + self.retryInterval) {
self.reportWithRetry(message, userId, retryCount: retryCount + 1)
}
} else {
printLog("###上报失败,已达到最大重试次数")
self.handleReportFailure(message)
}
self.reportSemaphore.signal()
})
}
}
private func handleReportSuccess(_ message: QYSessionInfo) {
messageQueue.async { [weak self] in
guard let self = self else { return }
self.lastReportTime = Date()
if let messageReportKey = self.messageReportKey, messageReportKey.count > 0 {
UserDefaults.standard.set(Date(), forKey: messageReportKey)
UserDefaults.standard.synchronize()
}
self.dailyReportCount += 1
let hour = Calendar.current.component(.hour, from: Date())
if hour >= 0 && hour < 8 {
self.nightReported = true
}
// DispatchQueue.main.async {
// self.notifyReportCompletion(message, success: true)
// }
self.logReportSuccess(message)
}
}
private func handleReportFailure(_ message: QYSessionInfo) {
// DispatchQueue.main.async {
// self.notifyReportCompletion(message, success: false)
// }
printLog("###上报失败:")
printLog("###消息ID: \(message.lastMessage?.messageId ?? "")")
printLog("###消息内容: \(message.lastMessageText ?? "")")
}
private func notifyReportCompletion(_ message: QYSessionInfo, success: Bool) {
NotificationCenter.default.post(
name: Notification.Name("MessageReported"),
object: nil,
userInfo: [
"messageId": message.lastMessage?.messageId ?? "",
"success": success,
]
)
}
// MARK: - Logging Methods
private func getTimeString(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone.current // 设置当前时区
formatter.locale = Locale.current
let localTime = formatter.string(from: date)
return localTime
}
private func logCurrentStatus() {
let now = Date()
let localTime = getTimeString(now)
printLog("###当前状态:")
printLog("###时间: \(localTime)")
if let lastReportTime = lastReportTime {
let lastTime = getTimeString(lastReportTime)
printLog("###上次上报: \(lastTime)")
} else {
printLog("###上次上报: 从未")
}
printLog("###夜间已上报: \(nightReported)")
printLog("###今日上报次数: \(dailyReportCount)")
}
private func logSkipReason() {
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
var reason = "跳过上报原因: "
if let last = lastReportTime {
let interval = now.timeIntervalSince(last)
if interval < oneHourInterval {
reason += "距离上次上报未满1小时 (间隔: \(Int(interval))秒)"
}
}
if hour >= 0 && hour < 8 && nightReported {
reason += "夜间已进行过上报"
}
printLog("###\(reason)")
}
private func logReportSuccess(_ message: QYSessionInfo) {
printLog("###上报成功:")
printLog("###消息ID: \(message.lastMessage?.messageId ?? "")")
printLog("###消息内容: \(message.lastMessageText ?? "")")
let timeString = getTimeString(Date())
printLog("###上报时间: \(timeString)")
printLog("###今日上报次数: \(dailyReportCount)")
}
}
......@@ -26,6 +26,8 @@ class YHButlerServiceManager: NSObject {
private var curentPresentedVC: UIViewController?
private lazy var reportHandler: YHButlerMessageReportHandler = YHButlerMessageReportHandler()
var lastMessage: YHButlerServiceMessage? {
getLastMessage()
}
......@@ -237,6 +239,7 @@ extension YHButlerServiceManager {
guard isUatAllowed() else {
return
}
reportHandler.reset()
initSDKAccountAndLastMessage { _ in
// 为了更新状态
DispatchQueue.main.async {
......@@ -348,13 +351,15 @@ extension YHButlerServiceManager: QYConversationManagerDelegate {
/// 会话列表变化;非平台电商用户,只有一个会话项,平台电商用户,有多个会话项 用这个方法可以拿到最新的完整的消息内容
func onSessionListChanged(_ sessionList: [QYSessionInfo]!) {
printLog("$$$$onSessionListChanged \(sessionList.first?.lastMessageText ?? "") \(sessionList.first?.lastMessageType.rawValue ?? 0) \(sessionList.first?.lastMessageTimeStamp ?? 0) \(sessionList.first?.lastMessage?.messageId ?? "")")
NotificationCenter.default.post(name: YhConstant.YhNotification.didQiYuReceiveNewMsgNotification, object: nil)
printLog("$$$$onSessionListChanged \(sessionList.first?.lastMessage?.from ?? "") \(sessionList.first?.lastMessageText ?? "") \(sessionList.first?.lastMessageType.rawValue ?? 0) \(sessionList.first?.lastMessageTimeStamp ?? 0) \(sessionList.first?.lastMessage?.messageId ?? "")")
}
/// 接收消息(进聊天页面会刷新, 目前不用这个方法打点记录)
func onReceiveMessage(_ message: QYMessageInfo!) {
printLog("$$$$onReceiveMessage \(message?.text ?? "") \(message?.type.rawValue ?? 0) \(message?.timeStamp ?? 0)")
if let latestMessage = getSessionList().last {
reportHandler.handleMessage(latestMessage)
}
}
/// 会话未读数变化(进聊天页面会刷新)
......
......@@ -360,7 +360,6 @@ private extension YHHomePageViewController {
func setupNotification() {
// 改为不监听消息到来,而是未读消息数量改变
// NotificationCenter.default.addObserver(self, selector: #selector(didQiYuReceiveNewMsg), name: YhConstant.YhNotification.didQiYuReceiveNewMsgNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didQiYuReceiveNewMsg), name: YhConstant.YhNotification.didQiYuUnReadMsgCountChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didRevUatParam), name: YhConstant.YhNotification.didRevUatParam, object: nil)
}
......
......@@ -287,9 +287,6 @@ extension YhConstant {
// 改变底部tabbar上 分享View的展示状态
public static let changeShareViewStatusOnTabBarNotifiction = Notification.Name(rawValue: "com.yinhe.change.shareView.status")
// 七鱼有新消息
public static let didQiYuReceiveNewMsgNotification = Notification.Name(rawValue: "com.yinhe.qiyu.receiveNewMsg")
// 七鱼未读消息数量变化
public static let didQiYuUnReadMsgCountChangeNotification = Notification.Name(rawValue: "com.yinhe.qiyu.unReadMsgCountChange")
......
......@@ -518,6 +518,8 @@ class YHAllApiName {
struct QiYu {
// 获取个人信息
static let userInfomation = "super-app/qiyu/user/information"
// 当收到机器人非招呼消息时,发送用户id给后端,后端触发发送企业微信
static let sendUserIdInChat = "super-app/approval/send-wechat-message"
}
// 续签资料
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment