Files
lol_coder/lck_cl_data_solution/updateServer/UpdateManager.cs
2026-04-01 20:20:09 +09:00

744 lines
26 KiB
C#

using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace updateServer
{
internal class UpdateManager
{
public static UpdateManager mInstance = null;
public static UpdateManager getInstance()
{
if (mInstance == null)
{
mInstance = new UpdateManager();
}
return mInstance;
}
private UpdateManager()
{
init();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
}
MongoClient mDBClient = null;
IMongoDatabase mEventDataBase = null;
string mReceivedEventToken = "";
bool isUpdateWork = false;
public string GameKey = "";
public string GameName = "";
public bool init()
{
try
{
mDBClient = new MongoClient(DEFINE.DB_접속정보);
mEventDataBase = mDBClient.GetDatabase("datalol");
//mUpdateWorkerTable = new Dictionary<string, List<updateWorkObject>>();
mUpdateWorkerTable = new Dictionary<string, updateWorkObject>();
return true;
}
catch (Exception ex)
{
#if (DEBUG)
{
System.Windows.Forms.MessageBox.Show(ex.ToString());
}
#endif
return false;
}
}
//internal Dictionary<string, List<updateWorkObject>> mUpdateWorkerTable = null;
internal object mWorkerTableLocker = new object();
internal Dictionary<string, updateWorkObject> mUpdateWorkerTable = null;
internal void startUpdateEventRaw(string gameID, bool isProgress)
{
lock (mWorkerTableLocker)
{
//bool isRemoved = false;
List<string> keys = new List<string>();
foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s);
foreach (string s in keys)
{
mUpdateWorkerTable[s].stopUpdateWork();
mUpdateWorkerTable.Remove(s);
//isRemoved = true;
}
//if (isRemoved) Program.mMainForm.Msg();
if (mUpdateWorkerTable.ContainsKey(gameID))
{
mUpdateWorkerTable[gameID].stopUpdateWork();
mUpdateWorkerTable.Remove(gameID);
}
mUpdateWorkerTable.Add(gameID, new updateWorkObject(gameID, isProgress));
mUpdateWorkerTable[gameID].startUpdateWork();
}
}
internal void RemoveAllEventRaw()
{
lock (mWorkerTableLocker)
{
//bool isRemoved = false;
List<string> keys = new List<string>();
foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s);
foreach (string s in keys)
{
mUpdateWorkerTable[s].stopUpdateWork();
mUpdateWorkerTable.Remove(s);
}
}
}
public async Task RemoveBackup()
{
MongoClient dd = new MongoClient(DEFINE.DB_접속정보);
var collectionNames = dd.GetDatabase("datalol").ListCollectionNames().ToList();
var mEventDataBaseTarget = dd.GetDatabase("datalol");
foreach (string item in collectionNames)
{
await mEventDataBaseTarget.GetCollection<BsonValue>(item)
.DeleteManyAsync(x => true);
}
mUpdateWorkerTable.Clear();
}
/// <summary>
/// 라이엇 API에 경기중인 게임리스트를 요청한다.
/// </summary>
/// <param name="isTest"></param>
/// <returns></returns>
internal Dictionary<string, string> GameListUpdateWorker(bool isTest)
{
//string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress";;
string bufRequestURL = DEFINE._게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress"; ;
//string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ;
string recvValue = requestRiotData(bufRequestURL);
IEnumerable<BsonValue> bufGameList = null;
List<string> updateRoomList = new List<string>();
Dictionary<String, string> rtnValue = new Dictionary<string, string>();
///가져온 데이터가 null이 아니라면 데이터를 Game단위(Document)로 짤라서 Enumarable화 하고, 아니면 빈 결과를 리턴한다.
if (recvValue != null)
{
bufGameList = BsonSerializer.Deserialize<BsonArray>(recvValue).Select(p => p.AsBsonDocument);
}
else
{
Program.mMainForm.updateGameRoomList(updateRoomList);
return rtnValue;
}
//가져온 데이터에서 방제와 플랫폼게임ID를 파싱해서 KV페어로 만든다.
foreach (BsonValue item in bufGameList)
{
string bufString = "";
///20210615테스트할부분 방을 새로만든부분이 있을경우 Document 순서에서 나중에 들어오므로 과거에 들어왔던 데이터를 버린다.
///혹시 모르니 안버린다.
//if (rtnValue.ContainsValue(item["gameName"].ToString()))
//{
// rtnValue.Remove(rtnValue.FirstOrDefault(x => x.Value == item["gameName"].ToString()).Key);
//}
if (isTest)
{
rtnValue.Add(item["platformGameId"].ToString(), item["gameName"].ToString());
bufString = item["platformGameId"].ToString() + "_" + item["gameName"].ToString();
updateRoomList.Add(bufString);
}
else
{
BsonDocument itemDocument = item.AsBsonDocument["platformGames"].AsBsonArray.Last().ToBsonDocument();
rtnValue.Add(itemDocument["platformGameId"].ToString(), itemDocument["gameName"].ToString());
bufString = itemDocument["platformGameId"].ToString() + "_" + itemDocument["gameName"].ToString();
updateRoomList.Add(bufString);
}
#if (DEBUG)
{
Console.WriteLine(bufString);
}
#endif
}
///완성된 방 정보를 UI에 업데이트한다.
Program.mMainForm.updateGameRoomList(updateRoomList);
return rtnValue;
}
/// <summary>
/// 끝난경기의 정보는 방제만으로 알 수 없기때문에 선수를 검색해서 경기를 찾기위한 게임 구조 클래스
/// </summary>
internal class game
{
internal string gamename = "";
internal List<string> playerList = new List<string>();
}
/// <summary>
/// 라이엇 API에서 끝난경기의 정보를 받아와서 gamelist.txt파일로 저장한다.
/// </summary>
/// <returns></returns>
internal void finishGameListUpdateWorker()
{
try
{
Dictionary<string, game> rtnValue = new Dictionary<string, game>();
string bufRequestURL = DEFINE._게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ;
string recvValue = requestRiotData(bufRequestURL);
IEnumerable<BsonValue> bufGameList = null;
if (recvValue != null)
{
bufGameList = BsonSerializer.Deserialize<BsonArray>(recvValue).Select(p => p["platformGames"]);
}
else
{
return;
}
foreach (BsonArray item in bufGameList)
{
BsonDocument selectItem = item.Last().ToBsonDocument();
game bufGame = new game();
bufGame.gamename = selectItem["gameName"].ToString();
BsonArray players = selectItem["participants"].AsBsonArray;
List<string> bufplayerList = new List<string>();
foreach (BsonDocument itemp in players)
{
bufplayerList.Add(itemp["summonerName"].ToString());
}
if (players.Count == 0)
{
string dd = "";
}
bufGame.playerList = bufplayerList;
rtnValue.Add(selectItem["platformGameId"].ToString(), bufGame);
string players123 = "";
foreach (string item2 in bufGame.playerList)
{
players123 += item2 + "_";
}
File.AppendAllText("gameList.txt", selectItem["platformGameId"].ToString() + " " + selectItem["gameName"].ToString() + " " + players123 + Environment.NewLine);
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("오류발생 - " + ex.Message);
}
}
/// <summary>
/// 완성된 URL을 통해 라이엇에 데이터를 요청하여 BsonDocument Type으로 결과를 리턴받는다.
/// </summary>
/// <param name="requestURL"></param>
/// <returns></returns>
string requestRiotData(string requestURL)
{
try
{
///20210809 SSL접속문제로 인해 서버인증기능을 끈 부분을 추가한다 이거 이래도 되나?
//ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
///가져온 URL에 맞춰 리퀘스트를 만든다.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestURL);
WebHeaderCollection hd = new WebHeaderCollection();
hd.Add(DEFINE.RIOT_API_KEY);
req.Headers = hd;
WebResponse rsp = req.GetResponse();
string result = "";
///가져온 데이터를 문자열화 하고, Bson형태로 파싱해서 리턴한다.
using (var reader = new StreamReader(rsp.GetResponseStream()))
{
result = reader.ReadToEnd();
}
if (result.Trim() == "[]")
{
return null;
}
return result;
}
catch (Exception ex)
{
return ex.ToString();
}
}
/// <summary>
/// EventRaw를 데이터를 업데이트 하기위한 UpdateWorker Class
/// </summary>
internal class updateWorkObject
{
string mGameID = "";
bool mIsProgress = false;
object tokenLocker = new object();
string receviedEventToken = "";
internal string mReceivedEventToken
{
get
{
lock (tokenLocker)
{
return receviedEventToken;
}
}
set
{
lock (tokenLocker)
{
receviedEventToken = value;
}
}
}
Thread mUpdateWorker = null;
internal bool isUpdateWork = false;
internal updateWorkObject(string gameID, bool recvProgress)
{
this.mGameID = gameID;
this.mIsProgress = recvProgress;
}
internal void startUpdateWork()
{
//mUpdateWorker = new Thread(updateTestDataWork);
Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.removeWorkerList(); }));
mUpdateWorker = new Thread(updateWork);
//mUpdateWorker = new Thread(updateWorkTest);
mUpdateWorker.IsBackground = true;
isUpdateWork = true;
mUpdateWorker.Start();
Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));
//Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.Msg(); }));
}
int tokenCount = 0;
internal void stopUpdateWork()
{
isUpdateWork = false;
}
void updateWork()
{
while (isUpdateWork)
{
try
{
string bufRequestURL = DEFINE._이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken;
BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL));
///경기가 끝났음
if (bufRecvPayload.Contains("missingEventsStatus"))
{
if (bufRecvPayload["missingEventsStatus"].ToString() == "lost_permanently")
{
isUpdateWork = false;
}
else
{
mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
}
}
else if (bufRecvPayload.Contains("nextPageToken"))
{
mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
}
//다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함.
else if (!bufRecvPayload.Contains("nextPageToken"))
{
isUpdateWork = false;
}
///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림
//UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + "event_raw")
// .UpdateOne(
// x => x["nextPageToken"] == bufRecvPayload["nextPageToken"],
// Builders<BsonValue>.Update.Set(x => x["events"], bufRecvPayload["events"]),
// new UpdateOptions() { IsUpsert = true }
// );
///토큰에 할당된 이벤트로우를 배열로 가져옴
BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray;
///DB에 Duplicate를 할 수 있는 bulkwrite를 하기위한 WriteModel형식으로 만듬.
///Bulkwrite를 선택하면서 DB에서 한번에 유지할수있는 경기의 수가 많이 줄어들었다.
Dictionary<string, List<WriteModel<BsonValue>>> bufDataTable = new Dictionary<string, List<WriteModel<BsonValue>>>();
foreach (BsonDocument item in bufDataList)
{
BsonDocument bufUpdateValue = new BsonDocument();
///rfc461Schema에 해당 event의 종류가 들어있음.
string bufStatus = item["rfc461Schema"].ToString();
//DB에서 경기를 관리하기 위한 Key로 RequestGameID와 sequenceIndex를 사용.
bufUpdateValue.Add("RequestGameID", mGameID);
bufUpdateValue.Add(new BsonElement("sequenceIndex", Convert.ToInt32(item["sequenceIndex"])));
var gameFilter = Builders<BsonValue>.Filter.Eq(x => x["RequestGameID"], mGameID);
var sequanceFilter = Builders<BsonValue>.Filter.Eq(x => x["sequenceIndex"], item["sequenceIndex"]);
var Parentfilter = Builders<BsonValue>.Filter.And(gameFilter, sequanceFilter);
///Value를 eventDocument에 탑재.
bufUpdateValue.Add("eventDocument", item);
UpdateOneModel<BsonValue> updateRaw = new UpdateOneModel<BsonValue>(
Parentfilter,
Builders<BsonValue>.Update.Set(x => x["eventDocument"], item)
)
{ IsUpsert = true };
if (!bufDataTable.ContainsKey(bufStatus))
{
bufDataTable.Add(bufStatus, new List<WriteModel<BsonValue>>());
}
bufDataTable[bufStatus].Add(updateRaw);
}
foreach (var item in bufDataTable)
{
UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + item.Key)
.BulkWriteAsync(item.Value);
}
Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken);
tokenCount += 1;
Thread.Sleep(500);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
isUpdateWork = false;
}
}
//isUpdate가 false로 빠지는 경우 업데이트중인 게임 목록에서 해당 게임을 지운다.
lock (UpdateManager.getInstance().mWorkerTableLocker)
{
UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID);
}
Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));
}
void updateWorkTest()
{
while (isUpdateWork)
{
ThreadPool.QueueUserWorkItem(o => { updateRoutineForTest(); });
Thread.Sleep(1000);
}
lock (UpdateManager.getInstance().mWorkerTableLocker)
{
UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID);
}
Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));
}
//테스트를위한 업데이트루틴
void updateRoutineForTest()
{
try
{
string bufRequestURL = DEFINE._이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken;
BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL));
///경기가 끝났음
if (bufRecvPayload.Contains("missingEventsStatus"))
{
if (bufRecvPayload.Contains("lost_permanently"))
{
isUpdateWork = false;
}
else
{
mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
}
}
else if (bufRecvPayload.Contains("nextPageToken"))
{
mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
}
//다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함.
else if (!bufRecvPayload.Contains("nextPageToken"))
{
isUpdateWork = false;
}
///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림
//UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + "event_raw")
// .UpdateOne(
// x => x["nextPageToken"] == bufRecvPayload["nextPageToken"],
// Builders<BsonValue>.Update.Set(x => x["events"], bufRecvPayload["events"]),
// new UpdateOptions() { IsUpsert = true }
// );
var filterGameID = Builders<BsonValue>.Filter.Eq(x => x["RequestGameID"], mGameID);
BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray;
//var filter = Builders<BsonDocument>.Filter.Eq("_id", ObjectId.Parse(dbPro.Id));
//var update = Builders<BsonDocument>.Update.Push("tags", buildBsonArrayFromTags(pro.Tags));
//var result = collection.UpdateOne(filter, update);
//if (result.IsModifiedCountAvailable)
//{
// if (result.ModifiedCount == 1)
// {
// return true;
// }
//}
//벌크인서트를 위한 해시테이블
//Dictionary<string, BsonArray> bufBulkInsertTable = new Dictionary<string, BsonArray>();
//foreach (BsonDocument item in bufDataList)
//{
// ///이벤트네임을 키값으로 하고 벌크인서트를 위해 데이터를 나눔.
// string bufEventName = item["rfc461Schema"].ToString();
// if (!bufBulkInsertTable.ContainsKey(bufEventName))
// {
// bufBulkInsertTable.Add(bufEventName, new BsonArray());
// }
// bufBulkInsertTable[bufEventName].Add(item);
//}
// Dictionary<string, List<WriteModel<BsonDocument>>> bulkModel = new Dictionary<string, List<WriteModel<BsonDocument>>>();
// //경기정보를위해 전체정보를 각각의 이벤트에 맞는 콜렉션에 저장함(누락을 막기위해 동기화).
// foreach (var item in bufDataList)
// {
// var bufFilter = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);
// var bufUpdate = Builders<BsonDocument>.Update.Push("events", item);
// UpdateOneModel<BsonDocument> updateRaw = new UpdateOneModel<BsonDocument>(
// bufFilter, bufUpdate) { IsUpsert = true };
// if (!bulkModel.ContainsKey(item["rfc461Schema"].ToString()))
// {
// bulkModel.Add(item["rfc461Schema"].ToString(), new List<WriteModel<BsonDocument>>());
// }
// bulkModel[item["rfc461Schema"].ToString()].Add(updateRaw);
//// var resultOne = collectionEvent.UpdateMany(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true });
// }
// foreach (var item in bulkModel)
// {
// var result = UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>(item.Key).BulkWrite(item.Value);
// }
foreach (var item in bufDataList)
{
var bufFilter = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);
var bufUpdate = Builders<BsonDocument>.Update.Push("events", item);
var collectionEvent = UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>(item["rfc461Schema"].ToString());
var resultOne = collectionEvent.UpdateOne(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true });
}
////로그를위해 전체정보를 eventraws콜렉션에 저장함(비동기).
//var collectionAll = UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>("eventraws");
//var filterAll = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);
//foreach (var item in collection)
//{
//}
//var updateAll = Builders<BsonDocument>.Update.Push("events", bufDataList);
//var result2 = collectionAll.UpdateManyAsync(filterAll, updateAll, new UpdateOptions() { IsUpsert = true });
Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken);
tokenCount += 1;
Thread.Sleep(1000);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
isUpdateWork = false;
}
}
}
}
}