using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.Http; using System.Security.Cryptography; using System.Threading; using System.Collections.Concurrent; using Sog; using ProtoCSStruct; using LitJson; using System.Text.Json; using static System.Net.Mime.MediaTypeNames; namespace Chat { public class HeroSentimentSvc { public static string m_baseUrl; public static int gameId; public static string secretKey; private static MD5 m_md5; public static Object m_locker = new Object(); public static Object m_Banlocker = new Object(); public static JsonData m_ChatList; public static JsonData m_BanLogList; // 实时检测 public static string validateUrl = "/v2/api/content/validate"; // 上报聊天数据 public static string reportUrl = "/v2/api/message/receive"; // 上报封禁数据 public static string banLogUrl = "/v2/api/log/receive"; private static ConcurrentQueue<(PlayerOnChat, CSChatReq)> m_chatMsgWaitQueue = new ConcurrentQueue<(PlayerOnChat, CSChatReq)>(); private static ConcurrentQueue m_checkMsgReqQueue = new ConcurrentQueue(); private static bool m_isStart = false; private static int m_threadCount = 3; private static Thread[] m_threads = null; private static long lastReportTime = 0; private static long lastReportBanLogTime = 0; public static void Init() { m_md5 = MD5.Create(); m_baseUrl = ChatServerUtils.GetServerConfig().sentimentUrl; gameId = ChatServerUtils.GetServerConfig().sentimentGameId; secretKey = ChatServerUtils.GetServerConfig().sentimentSecretKey; m_ChatList = new JsonData(); m_ChatList.SetJsonType(JsonType.Array); m_BanLogList = new JsonData(); m_BanLogList.SetJsonType(JsonType.Array); if (ChatServerUtils.GetServerConfig().isUseHeroSentiment == 1) { Start(); } } private static string GetSign(string timeStamp) { if (m_md5 == null) { m_md5 = MD5.Create(); } string singStr = secretKey + timeStamp; byte[] strbyte = Encoding.UTF8.GetBytes(singStr); byte[] md5Hash = m_md5.ComputeHash(strbyte); string md5Str = BitConverter.ToString(md5Hash).Replace("-", "").ToLower(); return md5Str; } public static void OnHandleChatRealtimeValidate(PlayerOnChat player, ref CSChatReq req) { //将消息加入队列 m_chatMsgWaitQueue.Enqueue((player, req)); try { AddChatToList(player, ref req); } catch (Exception e) { TraceLog.Error("OnHandleChatRealtimeValidate.AddChatToList - error: ", e.Message); } } public static void OnHandleDirtyChatCheck(ref SSCheckDirtyStringReq req) { m_checkMsgReqQueue.Enqueue(req); } private static void Start() { if (!m_isStart) { m_isStart = true; m_threads = new Thread[m_threadCount]; for (int i = 0; i < m_threadCount; i++) { m_threads[i] = new Thread(WorkThreadFun); //设置成后台线程,如果不是后台线程,线程不是主动关闭,则会一直运行,那怕进程主线程退出也没用 m_threads[i].IsBackground = true; m_threads[i].Start(); } var reportThread = new Thread(ThreadReport); //设置成后台线程,如果不是后台线程,线程不是主动关闭,则会一直运行,那怕进程主线程退出也没用 reportThread.IsBackground = true; reportThread.Start(); var reportBanLogThread = new Thread(ThreadReportBanLog); //设置成后台线程,如果不是后台线程,线程不是主动关闭,则会一直运行,那怕进程主线程退出也没用 reportBanLogThread.IsBackground = true; reportBanLogThread.Start(); } } private static void WorkThreadFun() { while (m_isStart) { bool haveData = false; try { if (false == m_checkMsgReqQueue.IsEmpty && m_checkMsgReqQueue.TryDequeue(out var checkReq)) { haveData = true; HandleDirtyStringCheck(ref checkReq); } if (false == m_chatMsgWaitQueue.IsEmpty && m_chatMsgWaitQueue.TryDequeue(out var record)) { haveData = true; HandleChatRealtimeValidate(record.Item1, ref record.Item2); } } catch (Exception ex) { TraceLog.Exception(ex); } if (!haveData) { Thread.Sleep(10); } } } /// /// 脏词检查 /// /// public static void HandleDirtyStringCheck(ref SSCheckDirtyStringReq req) { if (ChatServerUtils.GetServerConfig().useWechatCheck) { bool pass = WechatCheckSvc.CheckDirtyName(req.Content.GetString()); SSCheckDirtyStringRes res = new SSCheckDirtyStringRes(); res.Result = pass ? 0 : 1; res.Uid = req.Uid; res.CheckSeq = req.CheckSeq; res.ServerId = req.ServerId; if (res.Result >= 0) { res.NewContent.SetString(req.Content.GetString()); } ChatServerUtils.GetPacketSender().SendToServerByID(res.ServerId, (int)SSGameMsgID.CheckDirtyStringRes, ref res, res.Uid); } else { string newContent = GetDirtyStringResult(req.Content.GetString(), out int checkResult); SSCheckDirtyStringRes res = new SSCheckDirtyStringRes(); res.Result = checkResult; res.Uid = req.Uid; res.CheckSeq = req.CheckSeq; res.ServerId = req.ServerId; if (res.Result >= 0) { res.NewContent.SetString(newContent); } ChatServerUtils.GetPacketSender().SendToServerByID(res.ServerId, (int)SSGameMsgID.CheckDirtyStringRes, ref res, res.Uid); } } /// /// 实时监测聊天 /// /// public static void HandleChatRealtimeValidate(PlayerOnChat player, ref CSChatReq req) { string newContent = GetDirtyStringResult(req.Message.GetString(), out int checkResult); if (checkResult == 0) { ChatSvc.SendToChatMsg(player, ref req); } else if (checkResult == 1) { //有错误不让发 //req.Message.SetString(newContent); //ChatSvc.SendToChatMsg(player, ref req); CSChatRes res = new CSChatRes(); res.Ret = (int)CSErrCode.IncludeIllegalWord; PlayerPacketSender.SendToPlayer(player, (int)CSGameMsgID.ChatRes, ref res); TraceLog.Trace("HeroSentimentSvc.HandleChatRealtimeValidate send world player:{0} mess:{1} ret:{2}", player.UserID, req.Message.GetString(), checkResult); } else { CSChatRes res = new CSChatRes(); res.Ret = (int)CSErrCode.SysFailure; PlayerPacketSender.SendToPlayer(player, (int)CSGameMsgID.ChatRes, ref res); TraceLog.Error("HeroSentimentSvc.HandleChatRealtimeValidate error ret {0}", checkResult); } } public static string GetDirtyStringResult(string oldStr, out int checkResult) { string str = ""; try { str = DealDirtyString(oldStr, out checkResult); } catch (Exception ex) { checkResult = -1; str = ""; TraceLog.Exception(ex); } return str; } private static string DealDirtyString(string oldStr, out int checkResult) { string replace_content = ""; if (oldStr.Length == 0) { checkResult = 0; return replace_content; } if (m_baseUrl == "" || m_baseUrl == null) { checkResult = -1; TraceLog.Error("HeroSentimentSvc.DealDirtyString error baseUrl"); return replace_content; } var heardParams = new List>(); string timeStamp = ChatServerUtils.GetTimeStamp(DateTime.Now).ToString(); heardParams.Add(new KeyValuePair("gameId", Convert.ToString(gameId))); heardParams.Add(new KeyValuePair("sign", GetSign(timeStamp))); heardParams.Add(new KeyValuePair("timeStamp", timeStamp)); var contentParams = new Dictionary(); contentParams["content"] = oldStr; var content = new StringContent( JsonSerializer.Serialize(contentParams), Encoding.UTF8, Application.Json); string postUrl = m_baseUrl + validateUrl; string ret = HttpUtils.HttpPost(postUrl, content, heardParams, out bool exception); if (ret == null) { checkResult = -1; TraceLog.Error("HeroSentimentSvc.DealDirtyString error HttpPost ret"); return replace_content; } JsonData jsonData = JsonMapper.ToObject(ret); JsonData codeData = jsonData["code"]; int code = codeData != null ? (int)codeData : -1; if (code != 0) { checkResult = -1; TraceLog.Error("HeroSentimentSvc.DealDirtyString error code {0}", code); return replace_content; } JsonData data = jsonData["data"]; checkResult = Convert.ToInt16(data["result"].ToString()); replace_content = data["replace_content"].ToString(); TraceLog.Trace("HeroSentimentSvc.DealDirtyString checkResult {0} str {1} ret {2} ", ret, oldStr, checkResult); return replace_content; } public static void ThreadReport() { while (m_isStart) { long nowSecond = ChatServerUtils.GetTimeSecond(); if (m_ChatList.Count >= 50 || (nowSecond - lastReportTime) >= 5) { lastReportTime = nowSecond; try { OnReportChatData(); } catch (Exception e) { TraceLog.Error("WorkThreadFun.OnReportChatData.error: ", e.Message); } } else { Thread.Sleep(10); } } } private static void AddChatToList(PlayerOnChat player, ref CSChatReq chatReq) { var chatMsg = new JsonData(); chatMsg["role_id"] = chatReq.Uid.ToString(); chatMsg["server_id"] = (int)player.RealmID; chatMsg["role_name"] = player.Nick; chatMsg["type"] = (int)chatReq.ChatChannelType; chatMsg["message"] = chatReq.Message.GetString(); chatMsg["vip_level"] = player.roleBase.VipLevel; chatMsg["level"] = player.roleBase.Level; var extend_items = new JsonData(); extend_items["test"] = "testValue"; chatMsg["extend_items"] = extend_items; lock (m_locker) { m_ChatList.Add(chatMsg); } } private static void OnReportChatData() { if (m_ChatList.Count == 0) { return; } JsonData reportData = new JsonData(); string reportDataStr; lock (m_locker) { reportData["chat_items"] = m_ChatList; reportDataStr = reportData.ToJson(); m_ChatList.Clear(); } var heardParams = new List>(); string timeStamp = ChatServerUtils.GetTimeStamp(DateTime.Now).ToString(); heardParams.Add(new KeyValuePair("gameId", Convert.ToString(gameId))); heardParams.Add(new KeyValuePair("sign", GetSign(timeStamp))); heardParams.Add(new KeyValuePair("timeStamp", timeStamp)); //var contentParams = new Dictionary(); //contentParams["chat_items"] = reportData; var content = new StringContent( reportDataStr, Encoding.UTF8, Application.Json); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); string postUrl = m_baseUrl + reportUrl; string ret = HttpUtils.HttpPost(postUrl, content, heardParams, out bool exception); if (ret == null) { TraceLog.Error("HeroSentimentSvc.OnReportChatData HttpGet return null query failed"); return; } JsonData jsonData = JsonMapper.ToObject(ret); JsonData codeData = jsonData["code"]; int code = codeData != null ? (int)codeData : -1; string msg = jsonData["msg"].ToString(); TraceLog.Trace("HeroSentimentSvc.OnReportChatData ret {0} code {1} msg {2}", ret, code, msg); if (code != 0) { TraceLog.Error("HeroSentimentSvc.OnReportChatData: ret code : {0} msg :{1}", code, msg); return; } } public static void ThreadReportBanLog() { while (m_isStart) { long nowSecond = ChatServerUtils.GetTimeSecond(); if (m_BanLogList.Count >= 10 || (nowSecond - lastReportBanLogTime) >= 5) { lastReportBanLogTime = nowSecond; try { OnReportBanLogData(); } catch (Exception e) { TraceLog.Error("ThreadReportBanLog.OnReportBanLogData.error: ", e.Message); } } else { Thread.Sleep(10); } } } public static void OnHandleChatBan(long uid, PlayerOnChat player, ref CSGagChatNotify req) { try { AddChatBanLogToList(uid, player, ref req); } catch (Exception e) { TraceLog.Error("OnHandleChatBan.AddChatBanLogToList - error: ", e.Message); } } private static void AddChatBanLogToList(long uid, PlayerOnChat player, ref CSGagChatNotify req) { var chatMsg = new JsonData(); TraceLog.Trace("OnHandleChatBan.AddChatBanLogToList uid {0} GagEndTime {1} EnableType {2} EnableStr {3}", uid, req.GagEndTime, req.EnableType, req.EnableStr); if (player == null) //TODO 没有上报区服和名字 { chatMsg["server_id"] = 0; chatMsg["role_id"] = uid.ToString(); chatMsg["role_name"] = ""; } else { chatMsg["server_id"] = (int)player.RealmID; chatMsg["role_id"] = player.UserID.ToString(); chatMsg["role_name"] = player.Nick; } chatMsg["ban_type"] = 2; //禁言 if (!string.IsNullOrEmpty(req.EnableStr.ToString())) chatMsg["ban_reason"] = req.EnableStr.ToString(); else chatMsg["ban_reason"] = req.EnableType.ToString(); chatMsg["ban_begin_time"] = ChatServerUtils.GetTimeSecond(); chatMsg["ban_end_time"] = req.GagEndTime; lock (m_Banlocker) { m_BanLogList.Add(chatMsg); } } public static void OnHandleSetFreezeTime(long uid, PlayerOnChat player, ref SSGMSetFreezeTimeRes req) { try { AddSetFreezeTimeBanLogToList(uid, player, ref req); } catch (Exception e) { TraceLog.Error("OnHandleChatBan.AddSetFreezeTimeBanLogToList - error: ", e.Message); } } private static void AddSetFreezeTimeBanLogToList(long uid, PlayerOnChat player, ref SSGMSetFreezeTimeRes req) { var chatMsg = new JsonData(); TraceLog.Trace("OnHandleChatBan.AddSetFreezeTimeBanLogToList uid {0} FreezeTime {1} FreezeReason {2} FreezeReasonStr {3}", uid, req.FreezeTime, req.FreezeReason, req.FreezeReasonStr); if (player == null) //TODO 没有上报区服和名字 { chatMsg["server_id"] = 0; chatMsg["role_id"] = uid.ToString(); chatMsg["role_name"] = ""; } else { chatMsg["server_id"] = (int)player.RealmID; chatMsg["role_id"] = player.UserID.ToString(); chatMsg["role_name"] = player.Nick; } chatMsg["ban_type"] = 1; //封号 if (!string.IsNullOrEmpty(req.FreezeReasonStr.ToString())) chatMsg["ban_reason"] = req.FreezeReasonStr.ToString(); else chatMsg["ban_reason"] = req.FreezeReason.ToString(); chatMsg["ban_begin_time"] = ChatServerUtils.GetTimeSecond(); chatMsg["ban_end_time"] = req.FreezeTime; lock (m_Banlocker) { m_BanLogList.Add(chatMsg); } } private static void OnReportBanLogData() { if (m_BanLogList.Count == 0) { return; } JsonData reportData = new JsonData(); string reportDataStr; lock (m_Banlocker) { reportData["log_items"] = m_BanLogList; reportDataStr = reportData.ToJson(); m_BanLogList.Clear(); } var heardParams = new List>(); string timeStamp = ChatServerUtils.GetTimeStamp(DateTime.Now).ToString(); heardParams.Add(new KeyValuePair("gameId", Convert.ToString(gameId))); heardParams.Add(new KeyValuePair("sign", GetSign(timeStamp))); heardParams.Add(new KeyValuePair("timeStamp", timeStamp)); var content = new StringContent( reportDataStr, Encoding.UTF8, Application.Json); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); string postUrl = m_baseUrl + banLogUrl; string ret = HttpUtils.HttpPost(postUrl, content, heardParams, out bool exception); if (ret == null) { TraceLog.Error("HeroSentimentSvc.OnReportBanLogData HttpGet return null query failed"); return; } JsonData jsonData = JsonMapper.ToObject(ret); JsonData codeData = jsonData["code"]; int code = codeData != null ? (int)codeData : -1; string msg = jsonData["msg"].ToString(); TraceLog.Trace("HeroSentimentSvc.OnReportBanLogData ret {0} code {1} msg {2}", ret, code, msg); if (code != 0) { TraceLog.Error("HeroSentimentSvc.OnReportBanLogData: ret code : {0} msg :{1}", code, msg); return; } } } }