564 lines
23 KiB
C#
564 lines
23 KiB
C#
using Newtonsoft.Json;
|
|
|
|
|
|
using ServerCore; using ServerBase;
|
|
using ServerCommon;
|
|
using static ServerCommon.MetaHelper;
|
|
|
|
|
|
using META_ID = System.UInt32;
|
|
using Amazon.S3.Model;
|
|
|
|
|
|
namespace GameServer
|
|
{
|
|
public class AIChatAction : EntityActionBase
|
|
{
|
|
private string user_jwt = string.Empty;
|
|
|
|
public AIChatAction(Player owner)
|
|
: base(owner)
|
|
{
|
|
}
|
|
|
|
public override Task<Result> onInit()
|
|
{
|
|
var result = new Result();
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
public override void onClear()
|
|
{
|
|
user_jwt = string.Empty;
|
|
}
|
|
|
|
//-------------인증 JWT 관련 API-------------
|
|
//특정 유저의 JWT를 검증합니다.
|
|
public async Task<(Result, AIChatJwtInfo?)> jwtverify(string user_jwt)
|
|
{
|
|
var result = new Result();
|
|
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
if (server_logic.getServerConfig().OfflineMode == true)
|
|
{
|
|
result.setFail(ServerErrorCode.AiChatServerInactive);
|
|
return (result, null);
|
|
}
|
|
|
|
var url = $"/api/auth/jwt/verify";
|
|
(result, string resMsg) = await AIChatServerConnector.sendAIChatServer("GET", url, user_jwt, null);
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error($"Failed ai chat server action : jwtverify, msg : {resMsg}");
|
|
return (result, null);
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatJwtVerify);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
try
|
|
{
|
|
var aichat_jwt_info = JsonConvert.DeserializeObject<AIChatJwtInfo>(resMsg);
|
|
return (result, aichat_jwt_info);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Log.getLogger().error($"Exception !!!, DeserializeObject : exception:{ex}, resMsg:{resMsg}");
|
|
return (result, null);
|
|
}
|
|
}
|
|
|
|
//특정 유저의 JWT를 발행합니다.
|
|
public async Task<(Result, string?)> jwtIssue(string user_guid)
|
|
{
|
|
var result = new Result();
|
|
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
if (server_logic.getServerConfig().OfflineMode == true)
|
|
{
|
|
result.setFail(ServerErrorCode.AiChatServerInactive);
|
|
return (result, string.Empty);
|
|
}
|
|
|
|
var url = $"/api/auth/jwt/issue/{user_guid}";
|
|
var body = AIChatServerConnector.getUserJwtJson();
|
|
|
|
(result, string resMsg) = await AIChatServerConnector.sendAIChatServer("POST", url, null, body);
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error($"Failed ai chat server action : jwtIssue, msg : {resMsg}");
|
|
return (result, string.Empty);
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatJwtIssue);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
try
|
|
{
|
|
var ai_chat_issue_jwt = JsonConvert.DeserializeObject<AIChatIssueJwt>(resMsg);
|
|
NullReferenceCheckHelper.throwIfNull(ai_chat_issue_jwt, () => $"ai_chat_issue_jwt is null !!! - {toBasicString()}");
|
|
|
|
user_jwt = ai_chat_issue_jwt.jwt == null ? string.Empty : ai_chat_issue_jwt.jwt;
|
|
|
|
return (result, user_jwt);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.getLogger().error($"Exception !!!, DeserializeObject : exception:{ex}, resMsg:{resMsg}");
|
|
return (result, null);
|
|
}
|
|
}
|
|
|
|
//포인트 충전
|
|
public async Task<Result> pointCharge(AIChatPointCharge aIChatPointCharge)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/point/charge";
|
|
var body = JsonConvert.SerializeObject(aIChatPointCharge);
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("PUT", url, body);
|
|
if (result.isFail())
|
|
{
|
|
if (result.ErrorCode == ServerErrorCode.AiChatServerInactive)
|
|
return result;
|
|
|
|
result.setFail(ServerErrorCode.AiChatServerRetryChargePoint);
|
|
Log.getLogger().error($"Failed ai chat server action : pointCharge, msg : {resMsg}");
|
|
return result;
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatPointCharge);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//포인트 충전 확인
|
|
public async Task<Result> pointChargeCheckByOrderGuid(string order_guid)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var aIPointVerify = new AIChatPointVerify() { orderGuid = order_guid };
|
|
|
|
var url = $"/api/point/charge";
|
|
var body = JsonConvert.SerializeObject(aIPointVerify);
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("GET", url, body);
|
|
if (result.isFail()) return result;
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatPointChargeVerify);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//어제까지의 벌어들인 미청구 포인트 조회
|
|
public async Task<(Result, AIChatPointClaimables?)> pointClaimables(int page, int num_per_page, string user_guid)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/point/claimables/{user_guid}?page={page}&numPerPage={num_per_page}";
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("GET", url, null);
|
|
if (result.isFail())
|
|
{
|
|
if (result.ErrorCode == ServerErrorCode.AiChatServerUserNotFound)
|
|
{
|
|
result = await registerUser(player.getUserGuid(), player.getUserNickname());
|
|
if (result.isFail())
|
|
{
|
|
return (result, null);
|
|
}
|
|
|
|
(result, resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("GET", url, null);
|
|
if (result.isFail())
|
|
{
|
|
return (result, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatIncentiveSearch);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
try
|
|
{
|
|
var aichat_point_claim_ables = JsonConvert.DeserializeObject<AIChatPointClaimables>(resMsg);
|
|
return (result, aichat_point_claim_ables);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.getLogger().error($"Exception !!!, DeserializeObject : exception:{ex}, resMsg:{resMsg}");
|
|
return (result, null);
|
|
}
|
|
}
|
|
//인센티브 수령 완료 표시
|
|
public async Task<Result> pointMarkClaimed(string user_guid, int marking_day)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"server_logic is null !!!");
|
|
|
|
var url = $"/api/point/mark-as-claimed/{user_guid}?aggregationEndTime={marking_day}";
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("POST", url, null);
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error($"Failed ai chat server action : pointMarkClaimed, msg : {resMsg}");
|
|
return result;
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatIncentiveMarking);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//신규 유저 등록
|
|
public async Task<Result> registerUser(string user_guid, string name)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/user/{user_guid}";
|
|
|
|
var aIRegisterUser = new AIChatRegisterUser() { nick = name };
|
|
var body = JsonConvert.SerializeObject(aIRegisterUser);
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("POST", url, body);
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error($"Failed ai chat server action : registerUser, user_guid : {user_guid}, name : {name}, msg : {resMsg}");
|
|
return result;
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatRegisterUser);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//유저 등록 제거
|
|
public async Task<Result> deleteUser(string user_guid)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/user/{user_guid}";
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("DELETE", url, null);
|
|
if (result.isFail()) return result;
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatDeleteUser);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//특정 캐릭터 조회
|
|
public async Task<(Result, AIChatCharacter?)> getCharacterByTargetGuid(string target_guid)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/character/{target_guid}";
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("GET", url, null);
|
|
if (result.isFail()) return (result, null);
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatGetCharacter);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
try
|
|
{
|
|
var aichat_character = JsonConvert.DeserializeObject<AIChatCharacter>(resMsg);
|
|
return (result, aichat_character);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.getLogger().error($"Exception !!!, DeserializeObject : exception:{ex}, resMsg:{resMsg}");
|
|
return (result, null);
|
|
}
|
|
}
|
|
|
|
//-------------유저 권한 API-------------
|
|
//신규 캐릭터 등록
|
|
public async Task<Result> registerCharacter(AIChatRegisterCharacter aIChatRegisterCharacter)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/character";
|
|
var body = JsonConvert.SerializeObject(aIChatRegisterCharacter);
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("POST", url, body);
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error($"Failed ai chat server action : registerCharacter, msg : {resMsg}");
|
|
return result;
|
|
}
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatRegisterCharacter);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//캐릭터 수정
|
|
public async Task<Result> updateCharacter(AIChatRegisterCharacter aIChatRegisterCharacter)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/character/locale/{aIChatRegisterCharacter.guid}";
|
|
var body = JsonConvert.SerializeObject(aIChatRegisterCharacter);
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("PUT", url, body);
|
|
if (result.isFail()) return result;
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatUpdateCharacter);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, body);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
//캐릭터 등록 제거
|
|
public async Task<Result> deleteCharacter(string character_guid)
|
|
{
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var url = $"/api/character/{character_guid}";
|
|
|
|
(var result, string resMsg) = await AIChatServerConnector.sendAIChatServerWithServerJwt("DELETE", url, null);
|
|
if (result.isFail()) return result;
|
|
|
|
var invokers = new List<ILogInvoker>();
|
|
var log_action = new LogActionEx(LogActionType.AIChatDeleteCharacter);
|
|
|
|
var task_log_data = AIChatBusinessLogHelper.toLogInfo(url, string.Empty);
|
|
invokers.Add(new AIChatBusinessLog(task_log_data));
|
|
|
|
BusinessLogger.collectLogs(log_action, player, invokers);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 데이터 수집 기간 : 월요일 00:00:00 ~ 일요일 23:59:59(KST 기준 - UTC+9)
|
|
/// 인센티브 보상 지급 날짜 및 시각 : 매주 월요일 05:00 (KST 기준 - UTC+9)
|
|
/// </summary>
|
|
|
|
public async Task<Result> updateTickOfIncentivePoint()
|
|
{
|
|
var result = new Result();
|
|
var err_msg = string.Empty;
|
|
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var current_time = DateTimeHelper.Current;
|
|
var incentive_collect_time = current_time.AddDays(Convert.ToInt32(DayOfWeek.Monday) - Convert.ToInt32(current_time.DayOfWeek)).Date;
|
|
var incentive_provide_time = incentive_collect_time.AddHours(5);
|
|
|
|
var ai_chat_attribute = player.getEntityAttribute<AiChatAttribute>();
|
|
NullReferenceCheckHelper.throwIfNull(ai_chat_attribute, () => $"ai_chat_attribute is null !!! - {player.toBasicString()}");
|
|
var last_incentive_provide_time = ai_chat_attribute.LastIncentiveProvideTime;
|
|
|
|
if (last_incentive_provide_time != default ||
|
|
last_incentive_provide_time == incentive_provide_time ||
|
|
incentive_provide_time > current_time)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
result = await TakeIncentivePoint(incentive_provide_time);
|
|
if(result.isFail()) return result;
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<Result> TakeIncentivePoint(DateTime new_incentive_provide_time)
|
|
{
|
|
var result = new Result();
|
|
var err_msg = string.Empty;
|
|
|
|
var player = getOwner() as Player;
|
|
NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!");
|
|
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var dynamo_db_client = server_logic.getDynamoDbClient();
|
|
|
|
var mail_action = player.getEntityAction<MailAction>();
|
|
NullReferenceCheckHelper.throwIfNull(mail_action, () => $"mail_action is null !!!");
|
|
|
|
var ai_chat_attribute = player.getEntityAttribute<AiChatAttribute>();
|
|
NullReferenceCheckHelper.throwIfNull(ai_chat_attribute, () => $"ai_chat_attribute is null !!! - {player.toBasicString()}");
|
|
|
|
if (MetaData.Instance._CurrencyMetaTableByCurrencyType.TryGetValue(CurrencyType.Beam, out var currencyMetaData) == false)
|
|
{
|
|
result.setFail(ServerErrorCode.CurrencyNotFoundData, err_msg);
|
|
return result;
|
|
}
|
|
|
|
(result, var pointClaim) = await pointClaimables(0, 100, player.getUserGuid());
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
NullReferenceCheckHelper.throwIfNull(pointClaim, () => $"pointClaim is null !!! - {toBasicString()}");
|
|
if (pointClaim.items.Count == 0)
|
|
{
|
|
ai_chat_attribute.LastIncentiveProvideTime = new_incentive_provide_time;
|
|
ai_chat_attribute.modifiedEntityAttribute();
|
|
|
|
(result, var ai_chat_doc) = await ai_chat_attribute.toDocBase();
|
|
if (result.isFail()) return result;
|
|
NullReferenceCheckHelper.throwIfNull(ai_chat_doc, () => $"ai_chat_doc is null !!! - {player.toBasicString()}");
|
|
|
|
result = await dynamo_db_client.simpleUpdateDocumentWithDocType(ai_chat_doc);
|
|
if (result.isFail())
|
|
{
|
|
err_msg = $"Failed to simpleUpdateDocumentWithDocType ai_chat_doc !!! - {player.toBasicString()}";
|
|
Log.getLogger().error(err_msg);
|
|
return result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
var IncentivePointList = new List<int>();
|
|
int maxPoint = GameConfigMeta.AiChatInsentiveMax;
|
|
int last_incentive_period = 0;
|
|
|
|
foreach (var pointInfo in pointClaim.items)
|
|
{
|
|
int amount = pointInfo.amount;
|
|
if(maxPoint < amount)
|
|
{
|
|
amount = maxPoint;
|
|
}
|
|
|
|
IncentivePointList.Add(amount);
|
|
if(last_incentive_period < pointInfo.end_time)
|
|
{
|
|
last_incentive_period = pointInfo.end_time;
|
|
}
|
|
}
|
|
|
|
var fn_send_system_mail = async delegate ()
|
|
{
|
|
var result = new Result();
|
|
var err_msg = string.Empty;
|
|
|
|
var receivedMailDocs = new List<DynamoDbDocBase>();
|
|
foreach (var amount in IncentivePointList)
|
|
{
|
|
var mail_items = new List<ServerCommon.MailItem>() { new ServerCommon.MailItem() { ItemId = (META_ID)currencyMetaData.ItemId, Count = amount } };
|
|
(result, var receivedMailDoc) = await mail_action.tryMakeNewSystemMail(mail_items, ServerCommon.Constant.NPC_INSENTIVE_META_KEY, GameConfigMeta.AiChatInsentivePeriod, string.Empty);
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
NullReferenceCheckHelper.throwIfNull(receivedMailDoc, () => $"receivedMailDoc is null !!! - {toBasicString()}");
|
|
receivedMailDocs.Add(receivedMailDoc);
|
|
}
|
|
|
|
ai_chat_attribute.LastIncentiveProvideTime = new_incentive_provide_time;
|
|
|
|
var batch = new QueryBatchEx<QueryRunnerWithDocument>(player, LogActionType.MailAiChatIncentivePoint
|
|
, dynamo_db_client);
|
|
{
|
|
batch.addQuery(new DBQEntityWrite(receivedMailDocs));
|
|
batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner());
|
|
}
|
|
|
|
result = await QueryHelper.sendQueryAndBusinessLog(batch);
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
result = await pointMarkClaimed(player.getUserGuid(), last_incentive_period);
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
mail_action.NewReceivedMail();
|
|
|
|
return result;
|
|
};
|
|
|
|
var transaction_name = "provideIncentivePoint";
|
|
result = await player.runTransactionRunnerSafelyWithTransGuid(player.getUserGuid()
|
|
, TransactionIdType.PrivateContents, transaction_name
|
|
, fn_send_system_mail);
|
|
if (result.isFail())
|
|
{
|
|
err_msg = $"Failed to runTransactionRunnerSafelyWithTransGuid() !!! : {result.toBasicString()} - transactionName:{transaction_name}, {player.toBasicString()}";
|
|
Log.getLogger().error(err_msg);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|