下面是一段代码,它是一个静态类的一部分,它给我重构带来了麻烦。我花了几个星期的时间思考解决这个问题的方法,但到目前为止还没有成功。
一些上下文-这是我正在处理的tcp服务器的一部分。tcp服务器的行为类似于websocket规范—当新的客户机连接时,客户机套接字的所有管理都会被抽象掉,相反,简单的回调会被公开,可以向订阅者注册。
即:onreceived、onconnect、ondisconnect、onsend
通过这种方式,我可以让另一个类订阅socket客户机所需的行为,并将管理socket和业务逻辑的职责分开。每当客户端套接字接收到数据时,它都会读取整个数据块,然后引发“onreceived”回调方法,传入接收到的数据。
所有这些的目的都是读取来自lua客户机的各种请求并执行适当的操作。有些操作将转到mysql数据库(通过调用存储过程并将请求数据作为参数传递给存储过程)。此外,还增加了灵活性,可以在请求中发送多个数据集,这意味着在客户端和服务器之间需要发送的请求/响应更少。
这已经好几个月了,使得维护和更改业务代码变得非常容易,而不必担心破坏套接字行为。如果需要更改数据库中的存储过程,只需要更新数据库和lua客户机代码,因为tcp服务器不关心这些实现细节。
然而,现在有一个新的需求,即同时支持mysql和redis,并且tcp服务器能够以灵活的方式将请求定向到这些源中的任何一个。
复制的第一个问题是protocolresponsesingledata和protocolresponsemultidata结构-除了'data'属性之外,这些结构几乎是相同的。我需要能够返回单个数据集或数据集的集合。这两个类都序列化为json字符串。这导致sendtobsingledata和sendtobmultidata方法重复。
struct ProtocolResponseMultiData
{
public string Action;
public bool Result;
public string Error;
public List<List<object>> Data;
}
struct ProtocolResponseSingleData
{
public string Action;
public bool Result;
public string Error;
public List<object> Data;
}
第二个问题是有4种方法在方式上非常相似—sendtomysqldbingledata、sendtomysqldbmultidat、sendtoredisdbsingledata、sendtoredisdbmultidata。我似乎不知道如何将这些方法最多折叠成1或2个方法。
第三个问题是这个类都是静态方法—套接字客户机公开回调,但它不知道对象的任何特定示例,因此回调需要声明为静态的。这使得很难应用策略和工厂模式之类的东西来简化设计。
我还有希望吗?
public static void OnReceived(object sender, IPCReceivedEventArgs e)
{
try
{
LogToFile(e.data, ((SocketClient)(sender)).LogFile);
Console.WriteLine("Received data from client (" + ((SocketClient)(sender)).Address + ")");
dynamic j = Newtonsoft.Json.JsonConvert.DeserializeObject(e.data);
// Verify the request format is valid
if (j["Action"] != null && j["BulkQuery"] != null && j["Data"] != null && j["Destination"] != null)
{
ProtocolRequest request = new ProtocolRequest
{
Action = j["Action"],
Destination = j["Destination"],
IPAddress = ((SocketClient)(sender)).Address,
LogPath = ((SocketClient)(sender)).LogFile
};
bool isBulkQuery = j["BulkQuery"];
string jsonResp = "";
if (isBulkQuery)
{
ProtocolResponseMultiData resp = SendToDBMultiData(request, ref j);
jsonResp = JsonConvert.SerializeObject(resp);
}
else
{
ProtocolResponseSingleData resp = SendToDBSingleData(request, ref j);
jsonResp = JsonConvert.SerializeObject(resp);
}
((SocketClient)(sender)).Write(jsonResp);
}
else
{
// send malformed request response
string jsonResponse = "{ \"Action\" : " + j["Action"] + ", \"Result\" : false, \"Error\" : \"Malformed Request Received\", \"Data\" : null }";
((SocketClient)(sender)).Write(jsonResponse);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception encountered during OnReceived handler: " + ex.Message);
LogToFile("Exception encountered during OnReceived handler: " + ex.Message, ((SocketClient)(sender)).LogFile);
string jsonResponse = "{ \"Action\" : \"UNKNOWN\", \"Result\" : false, \"Error\" : \"Malformed JSON Request Received\", \"Data\" : null }";
((SocketClient)(sender)).Write(jsonResponse);
}
finally
{
}
}
public static ProtocolResponseSingleData SendToDBSingleData(ProtocolRequest request, ref dynamic j)
{
if (request.Destination == "MYSQL")
{
return SendToMySQLDBSingleData(request, ref j);
}
else if (request.Destination == "REDIS")
{
return SendToRedisDBSingleData(request, ref j);
}
else
{
ProtocolResponseSingleData response = new ProtocolResponseSingleData
{
Action = request.Action,
Error = "Invalid Destination specified - must be either 'REDIS' or 'MYSQL'",
Data = null,
Result = false
};
return response;
}
}
public static ProtocolResponseMultiData SendToDBMultiData(ProtocolRequest request, ref dynamic j)
{
if (request.Destination == "MYSQL")
{
return SendToMySQLDBMultiData(request, ref j);
}
else if (request.Destination == "REDIS")
{
return SendToRedisDBMultiData(request, ref j);
}
else
{
ProtocolResponseMultiData response = new ProtocolResponseMultiData
{
Action = request.Action,
Error = "Invalid Destination specified - must be either 'REDIS' or 'MYSQL'",
Data = null,
Result = false
};
return response;
}
}
private static ProtocolResponseSingleData SendToMySQLDBSingleData(ProtocolRequest request, ref dynamic j)
{
// serialize a new json string for just the data by itself
string jdataString = Newtonsoft.Json.JsonConvert.SerializeObject(j["Data"]);
// now deserialize this string into a list of dictionaries for parsing
Dictionary<string, object> dataDictionary = null;
if (((JToken)j["Data"]).Type == JTokenType.Object)
dataDictionary = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(jdataString);
else
dataDictionary = new Dictionary<string, object>();
ProtocolResponseSingleData result = new ProtocolResponseSingleData
{
Action = request.Action,
Error = "",
Data = new List<object>(),
Result = false
};
// special scenario - because we cant get the ip address of the game server from DCS, we'll get it from the socket sender object
// and specially insert it as a parameter into the data dictionary
// the other special scenario is the server description request can supply - this can contain harmful html, so we must sanitize the input
if (request.Action == ACTION_GET_SERVERID)
{
dataDictionary.Add("IP", request.IPAddress);
if (dataDictionary.ContainsKey("Description"))
{
try
{
string html = Convert.ToString(dataDictionary["Description"]);
html = System.Web.HttpUtility.HtmlEncode(html);
dataDictionary["Description"] = SanitizeHTML(html);
}
catch (Exception ex)
{
LogToFile("Error sanitizing ServerDescription html string (Action: " + request.Action + ") - " + ex.Message, request.LogPath);
result.Error = "Error sanitizing ServerDescription html string (Action: " + request.Action + ") - " + ex.Message;
return result;
}
}
}
MySql.Data.MySqlClient.MySqlConnection _conn = null;
MySql.Data.MySqlClient.MySqlDataReader rdr = null;
try
{
_conn = new MySql.Data.MySqlClient.MySqlConnection(Config.MySQLDBConnect);
_conn.Open();
MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand(request.Action)
{
Connection = _conn,
CommandType = System.Data.CommandType.StoredProcedure
};
foreach (var d in dataDictionary)
{
if (d.Value.GetType() == typeof(Int64) && (Int64)d.Value == LUANULL)
cmd.Parameters.AddWithValue(d.Key, null);
else
cmd.Parameters.AddWithValue(d.Key, d.Value);
}
rdr = cmd.ExecuteReader();
if (rdr.Read())
{
for (int i = 0; i < rdr.FieldCount; i++)
{
result.Data.Add(rdr[i]);
}
}
rdr.Close();
_conn.Close();
result.Result = true;
}
catch (Exception ex)
{
LogToFile("Error executing query against MySQL (Action: " + request.Action + ") - " + ex.Message, request.LogPath);
result.Error = "Error executing query against MySQL (Action: " + request.Action + ") - " + ex.Message;
}
finally
{
if (_conn != null)
if (_conn.State == System.Data.ConnectionState.Open || _conn.State == System.Data.ConnectionState.Connecting)
_conn.Close();
if (rdr != null)
if (!rdr.IsClosed)
rdr.Close();
}
return result;
}
private static ProtocolResponseMultiData SendToMySQLDBMultiData(ProtocolRequest request, ref dynamic j)
{
// serialize a new json string for just the data by itself
string jdataString = Newtonsoft.Json.JsonConvert.SerializeObject(j["Data"]);
// now deserialize this string into a list of dictionaries for parsing
List<Dictionary<string, object>> dataDictionary =
Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(jdataString);
ProtocolResponseMultiData result = new ProtocolResponseMultiData
{
Action = request.Action,
Error = "",
Data = new List<List<object>>(),
Result = false
};
MySql.Data.MySqlClient.MySqlConnection _conn = null;
MySql.Data.MySqlClient.MySqlDataReader rdr = null;
try
{
foreach (var d in dataDictionary)
{
_conn = new MySql.Data.MySqlClient.MySqlConnection(Config.MySQLDBConnect);
_conn.Open();
MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand(request.Action)
{
Connection = _conn,
CommandType = System.Data.CommandType.StoredProcedure
};
foreach (var kv in d)
{
if (kv.Value.GetType() == typeof(Int64) && (Int64)kv.Value == LUANULL)
cmd.Parameters.AddWithValue(kv.Key, null);
else
cmd.Parameters.AddWithValue(kv.Key, kv.Value);
}
rdr = cmd.ExecuteReader();
if (rdr.Read())
{
List<object> result_set = new List<object>();
for (int i = 0; i < rdr.FieldCount; i++)
{
result_set.Add(rdr[i]);
}
result.Data.Add(result_set);
}
else
{
result.Error += "No Results Returned\n";
}
rdr.Close();
_conn.Close();
}
result.Result = true;
}
catch (Exception ex)
{
LogToFile("Error executing query against MySQL (Action: " + request.Action + ") - " + ex.Message, request.LogPath);
result.Error = "Error executing query against MySQL (Action: " + request.Action + ") - " + ex.Message;
}
finally
{
if (_conn != null)
if (_conn.State == System.Data.ConnectionState.Open || _conn.State == System.Data.ConnectionState.Connecting)
_conn.Close();
if (rdr != null)
if (!rdr.IsClosed)
rdr.Close();
}
return result;
}
private static ProtocolResponseSingleData SendToRedisDBSingleData(ProtocolRequest request, ref dynamic j)
{
// Serialize the JSON Data property into its own JSON String
string jdataString = Newtonsoft.Json.JsonConvert.SerializeObject(j["Data"]);
// now deserialize this string into a list of dictionaries for parsing
Dictionary<string, object> dataDictionary = null;
if (((JToken)j["Data"]).Type == JTokenType.Object)
dataDictionary = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(jdataString);
else
dataDictionary = new Dictionary<string, object>();
ProtocolResponseSingleData result = new ProtocolResponseSingleData
{
Action = request.Action,
Error = "",
Data = new List<object>(),
Result = false
};
if (!RedisConnection.IsConnected)
{
LogToFile("Connection to Redis Closed - Attempting to reopen...", request.LogPath);
try
{
RedisConnection = ConnectionMultiplexer.Connect(Config.RedisDBConnect);
}
catch (Exception ex)
{
LogToFile("Error connecting to Redis - lost connection (" + ex.Message + ")", request.LogPath);
result.Error = "Error connecting to Redis - lost connection (" + ex.Message + ")";
return result;
}
}
try
{
string serverid = Convert.ToString(dataDictionary["ServerID"]);
string rediskey = Config.RedisActionKeys[request.Action];
if (serverid == null)
{
result.Error = "Error executing query against Redis (Action: " + request.Action + ") - " + "'ServerID' not found in Data request";
return result;
}
if (rediskey == null)
{
result.Error = "Error executing query against Redis - Action: '" + request.Action + "' not found in server configuration - please check action message or server configuration.";
return result;
}
IDatabase db = RedisConnection.GetDatabase();
string k = rediskey + ":" + serverid;
if (!db.StringSet(k, jdataString))
{
result.Error = "Failed to Set Key in Redis (Key: '" + k + "')";
}
else
{
result.Data.Add(1);
result.Result = true;
}
}
catch (Exception ex)
{
LogToFile("Error executing query against Redis (Action: " + request.Action + ") - " + ex.Message, request.LogPath);
result.Error = "Error executing query against Redis (Action: " + request.Action + ") - " + ex.Message;
}
return result;
}
private static ProtocolResponseMultiData SendToRedisDBMultiData(ProtocolRequest request, ref dynamic j)
{
// serialize a new json string for just the data by itself
string jdataString = Newtonsoft.Json.JsonConvert.SerializeObject(j["Data"]);
// now deserialize this string into a list of dictionaries for parsing
List<Dictionary<string, object>> dataDictionary =
Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(jdataString);
ProtocolResponseMultiData result = new ProtocolResponseMultiData
{
Action = request.Action,
Error = "",
Data = new List<List<object>>(),
Result = false
};
if (!RedisConnection.IsConnected)
{
LogToFile("Connection to Redis Closed - Attempting to reopen...", request.LogPath);
try
{
RedisConnection = ConnectionMultiplexer.Connect(Config.RedisDBConnect);
}
catch (Exception ex)
{
LogToFile("Error connecting to Redis - lost connection (" + ex.Message + ")", request.LogPath);
result.Error = "Error connecting to Redis - lost connection (" + ex.Message + ")";
return result;
}
}
try
{
int id = 0;
foreach (Dictionary<string, object> x in dataDictionary)
{
id += 1;
string serverid = Convert.ToString(x["ServerID"]);
string rediskey = Config.RedisActionKeys[request.Action];
if (serverid == null)
{
result.Error = "Error executing query against Redis (Action: " + request.Action + ") - " + "'ServerID' not found in Data request";
return result;
}
if (rediskey == null)
{
result.Error = "Error executing query against Redis - Action: '" + request.Action + "' not found in server configuration - please check action message or server configuration.";
return result;
}
IDatabase db = RedisConnection.GetDatabase();
string k = rediskey + ":" + serverid + ":" + id;
string jdatastring = Newtonsoft.Json.JsonConvert.SerializeObject(x);
if (!db.StringSet(k, jdatastring))
{
result.Error = "Failed to Set Key in Redis (Key: '" + k + "')";
result.Result = false;
}
else
{
List<object> res = new List<object>
{
k
};
result.Data.Add(res);
result.Result = true;
}
}
}
catch (Exception ex)
{
LogToFile("Error executing query against Redis (Action: " + request.Action + ") - " + ex.Message, request.LogPath);
result.Error = "Error executing query against Redis (Action: " + request.Action + ") - " + ex.Message;
}
return result;
}
暂无答案!
目前还没有任何答案,快来回答吧!