下面是一段代码,它是静态类的一部分,让我无法进行重构。我花了几个星期思考如何处理这个问题,但到目前为止还没有成功。
某些上下文 - 这是我正在处理的TCP服务器的一部分。 TCP服务器的行为类似于websocket规范 - 当新客户端连接时,客户端套接字的所有管理都被抽象掉了,而是公开了可以注册给订户的简单回调。
即。 OnReceived,OnConnect,OnDisconnect,OnSend
这样我就可以让另一个类订阅套接字客户端所需的行为,并将管理套接字和业务逻辑的职责分开。只要客户端套接字接收数据,它就会读取整个数据块,然后引发“OnReceived”回调方法,传入接收到的数据。
所有这一切的目的是从Lua客户端读取各种请求并执行适当的操作。一些操作将转到MySQL数据库(通过调用存储过程并将请求数据作为参数传递给sproc)。在请求中能够发送多个数据集的灵活性也增加了,这意味着需要在客户端和服务器之间发送更少的请求/响应。
这已经好几个月了,并且使得维护和更改业务代码变得非常容易,而无需担心破坏套接字行为。如果我需要更改数据库中的存储过程,则只需要更新数据库和Lua客户端代码,因为TCP服务器不关心这些实现细节。
但是,现在有一个新的要求是支持MySQL和Redis,并且TCP服务器能够以灵活的方式将请求引导到这些源中的任何一个。
重复的第一个问题是 ProtocolResponseSingleData 和 ProtocolResponseMultiData 结构 - 这些结构几乎完全相同,但“数据”属性除外。我需要能够返回单个数据集或数据集的集合。这两个类都被序列化为JSON字符串。这导致了 SendToDBSingleData 和 SendToDBMultiData 方法的重复。
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种方法在方式上非常相似 - SendToMySQLDBSingleData,SendToMySQLDBMultiData,SendToRedisDBSingleData,SendToRedisDBMultiData。我似乎无法弄清楚如何将这些方法最多折叠为1或2种方法。
第三个问题这个类是所有静态方法 - 套接字客户端公开回调,但它不知道对象的任何特定实例,因此回调需要声明为static。这使得很难应用策略和工厂模式之类的东西来简化设计。
我有希望吗?
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;
}