Files
hjha-server/MrWu/DB/MySQL.cs
xiaoou e9616125ce feat: initial commit - HJHA game server full source
6 major server modules (PdkFriendServer/GlobalSever/ServerCore/GameModule/GameNetModule) +
game logic (GameFix/GameDAL/ServerData) +
network layer (NetWorkMessage) +
data layer (ObjectModel) +
utilities (MrWu/Core/Config/CloudAPI/dll) +
adapters (zyxAdapter/base)

.NET 8.0 C# solution, 16 projects, 958 source files
2026-07-07 12:02:15 +08:00

691 lines
27 KiB
C#

#if MySQL
using System;
using System.Data;
using MySql.Data.MySqlClient;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Text;
using System.Diagnostics;
using System.IO;
using MrWu.Model;
using System.Threading;
using MrWu.Debug;
using System.Data.SqlClient;
namespace MrWu.DB {
/// <summary>
/// mysql操作
/// </summary>
public class MySQL : SqlEx<MySqlConnection, MySqlCommand, MySqlDbType,MySqlParameter> {
/// <summary>
/// 链接池
/// </summary>
private class ConnectionPool : IConnectionPool<MySqlConnection> {
/// <summary>
/// 链接字符串
/// </summary>
private readonly string _connstr;
string IConnectionPool<MySqlConnection>.connecStr => _connstr;
private readonly int _capsize;
/// <summary>
/// 最大的缓存链接数量
/// </summary>
int IPool<MySqlConnection>.capSize => _capsize;
private readonly int _surviveCountSize;
/// <summary>
/// 同时存在最大的链接数量
/// </summary>
/// <returns></returns>
int IPool<MySqlConnection>.surviveCountSize => _surviveCountSize;
/// <summary>
/// 当前池中的数量
/// </summary>
int IPool<MySqlConnection>.capCount => _conns.Count;
long _surviveCount = 0;
/// <summary>
/// 当前存活数量
/// </summary>
int IPool<MySqlConnection>.surviveCount {
get {
return (int)Interlocked.Read(ref _surviveCount);
}
}
/// <summary>
/// 所有的链接
/// </summary>
private readonly ConcurrentQueue<MySqlConnection> _conns = new ConcurrentQueue<MySqlConnection>();
ConcurrentQueue<MySqlConnection> IConnectionPool<MySqlConnection>.conns => _conns;
/// <summary>
/// 用于异步等待连接可用的信号量
/// </summary>
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
public ConnectionPool(SqlConfig config) {
this._capsize = config.cacheCount;
this._surviveCountSize = config.surviveCountSize;
this._connstr = string.Format("server={0};port={1};user={2};password={3};AllowUserVariables={4};pooling=true;database=",
config.host, config.port, config.username, config.pwd, config.AllowUserVariables);
}
/// <summary>
/// 获取一个连接
/// </summary>
/// <param name="dbbase"></param>
/// <returns></returns>
public MySqlConnection TakeOut(params object[] dbbase) {
if (dbbase.Length <= 0) return null;
string dbname = dbbase[0] as string;
if (dbname == null) return null;
MySqlConnection conn;
if (_conns.TryDequeue(out conn)) {
if (conn != null) {
return conn;
}
}
if (this._surviveCountSize > 0 && Interlocked.Read(ref _surviveCount) >= this._surviveCountSize)
return null;
Interlocked.Increment(ref _surviveCount);
return new MySqlConnection(this._connstr + dbname);
}
/// <summary>
/// 异步获取一个连接(连接池满时不会阻塞线程)
/// </summary>
public async Task<MySqlConnection> TakeOutAsync(string dbname) {
if (dbname == null) return null;
while (true) {
MySqlConnection conn;
if (_conns.TryDequeue(out conn)) {
if (conn != null) return conn;
}
if (this._surviveCountSize == 0 || Interlocked.Read(ref _surviveCount) < this._surviveCountSize) {
Interlocked.Increment(ref _surviveCount);
return new MySqlConnection(this._connstr + dbname);
}
// 连接池满,异步等待
await _semaphore.WaitAsync();
_semaphore.Release();
}
}
/// <summary>
/// 放入一个连接
/// </summary>
/// <param name="conn"></param>
public void PutInto(MySqlConnection conn) {
if (_capsize == 0 || _conns.Count < _capsize) {
_conns.Enqueue(conn);
} else {
conn.Dispose();
Interlocked.Decrement(ref _surviveCount);
}
}
}
/*
* 1.执行sql语句 获得执行sql语句后的结果
* 2.执行存储过程 获得存储过程的结果
*
* 每个库都有一个链接
*
* */
private ConnectionPool _ConnPool = null;
/// <summary>
/// 链接池
/// </summary>
public override IConnectionPool<MySqlConnection> connPool => _ConnPool;
/// <summary>
/// 初始化
/// </summary>
/// <param name="config"></param>
public MySQL(SqlConfig config) {
this._ConnPool = new ConnectionPool(config);
errorTryAgainCount = config.errorTryAgainCount;
errorAgainTimeInc = config.errorAgainTimeInc;
errorAgainTime = config.errorAgainTime;
if (errorTryAgainCount < 0)
throw new ArgumentException("errorTryAgainCount 不能小于 0");
if (errorAgainTimeInc < 0)
throw new ArgumentException("errorAgainTimeInc 不能小于 0");
if (errorAgainTime < 0)
throw new ArgumentException("errorAgainTime 不能小于 0");
}
/// <summary>
/// 获取一个连接
/// </summary>
/// <param name="dbbase"></param>
/// <returns></returns>
public override MySqlConnection GetConnection(string dbbase) {
MySqlConnection tmp;
while (true) {
tmp = _ConnPool.TakeOut(dbbase);
if (tmp != null)
return tmp;
Debug.Debug.Warning("链接数超过上限,等待重试");
Thread.Sleep(2);
}
}
/// <summary>
/// 异步获取一个连接(不会阻塞线程)
/// </summary>
public async Task<MySqlConnection> GetConnectionAsync(string dbbase) {
return await _ConnPool.TakeOutAsync(dbbase);
}
/// <summary>
/// 开始存储过程
/// </summary>
/// <param name="dbbase">存储过程所在的数据库</param>
/// <param name="procedure">存储过程名</param>
/// <returns></returns>
public override IProdureParameter<MySqlCommand> BeginProcedure(string dbbase, string procedure) {
if (_ConnPool == null)
throw new Exception("you dont hava init");
MySqlCommand cmd = new MySqlCommand(procedure, GetConnection(dbbase));
MySqlProcedureParameter result = new MySqlProcedureParameter(cmd, dbbase);
//存储过程模式
cmd.CommandType = CommandType.StoredProcedure;
return result;
}
/// <summary>
/// 添加参数
/// </summary>
/// <param name="spp">存储过程查询键</param>
/// <param name="paramName">参数名</param>
/// <param name="value">参数值</param>
/// <param name="type">参数类型</param>
/// <param name="pd">参数种类</param>
public override void AddParam(IProdureParameter<MySqlCommand> spp, string paramName, object value, MySqlDbType type, ParameterDirection pd = ParameterDirection.Input) {
if (spp == null || spp.command == null)
throw new Exception("param spp null");
MySqlParameter param = spp.command.Parameters.Add(paramName, type);
param.Direction = pd;
if (value == null)
param.Value = DBNull.Value;
else
param.Value = value;
}
/// <summary>
/// 提交存储过程
/// </summary>
/// <param name="spp">存储过程查询键</param>
/// <param name="ds">如果 存储过程中有查询表操作,则需要传递DataSet实例</param>
/// <param name="againCount">重试次数 小于0表示使用配置值 大于等于0表示自定义</param>
/// <returns>参数值,输出参数从字典中获取</returns>
public override Dictionary<string, object> SubProcedure(IProdureParameter<MySqlCommand> spp, DataSet ds = null, int againCount = -1) {
if (spp == null || spp.command == null)
throw new Exception("param spp null");
int cnt = Execute(spp.command, spp.dbName, ds, againCount);
Dictionary<string, object> dic = new Dictionary<string, object>();
var pms = spp.command.Parameters;
int len = pms.Count;
for (int i = 0; i < len; i++) {
dic.Add(pms[i].ParameterName, pms[i].Value);
}
if (!dic.ContainsKey(DbConst.RowsAffected))
{
dic[DbConst.RowsAffected] = cnt;
}
spp.Dispose();
return dic;
}
/// <summary>
/// 执行sql语句
/// </summary>
/// <param name="dbbase">数据库</param>
/// <param name="sql">sql语句</param>
/// <param name="mysqlparams"></param>
/// <param name="againCount">重试次数 小于0表示使用配置值 大于等于0表示自定义</param>
/// <param name="ds">如果是查询 使用传入ds实例</param>
public override int ExecuteSql(string dbbase, string sql, IEnumerable<MySqlParameter> mysqlparams = null, DataSet ds = null, int againCount = -1) {
MySqlCommand cmd = new MySqlCommand(sql, GetConnection(dbbase));
cmd.CommandType = CommandType.Text;
if (mysqlparams != null) {
//int len = mysqlparams.Count;
//for (int i = 0; i < len; i++) {
// ISqlAloneParameter<MySqlDbType> msap = mysqlparams[i];
// cmd.Parameters.Add(msap.paramname, msap.type, msap.valueLength);
// cmd.Parameters[msap.paramname].Value = msap.paramvalue;
//}
foreach (var param in mysqlparams) {
cmd.Parameters.Add(param);
}
}
return Execute(cmd, dbbase, ds, againCount);
}
/// <summary>
/// 执行sql语句
/// </summary>
/// <param name="dbbase">数据库</param>
/// <param name="sql">sql语句</param>
/// <param name="mysqlparams">附加参数</param>
/// <returns></returns>
public MySqlCommand GetCommandBySql(string dbbase, string sql, IEnumerable<MySqlParameter> mysqlparams = null) {
MySqlCommand cmd = new MySqlCommand(sql, GetConnection(dbbase));
cmd.CommandType = CommandType.Text;
if (mysqlparams != null) {
//int len = mysqlparams.Count;
//for (int i = 0; i < len; i++) {
// ISqlAloneParameter<MySqlDbType> msap = mysqlparams[i];
// cmd.Parameters.Add(msap.paramname, msap.type, msap.valueLength);
// cmd.Parameters[msap.paramname].Value = msap.paramvalue;
//}
foreach (var param in mysqlparams) {
cmd.Parameters.Add(param);
}
}
return cmd;
}
/// <summary>
/// 普通查询一个表
/// </summary>
/// <param name="dbbase">数据库名称</param>
/// <param name="table">表名</param>
/// <param name="column">列名</param>
/// <param name="where">条件</param>
/// <param name="count">查询的数量</param>
/// <param name="againCount">重试次数 小于0表示使用配置值 大于等于0表示自定义</param>
/// <param name="sqlparams">sqlparams</param>
/// <returns>查询到的表</returns>
public override DataTable Select(string dbbase, string table, string[] column = null, string where = null, int count = 0, int againCount = -1, IEnumerable<MySqlParameter> sqlparams = null) {
StringBuilder sb = new StringBuilder();
sb.Append("select ");
if (column != null && column.Length > 0) {
int len = column.Length;
for (int i = len - 1; i >= 0; i--) {
sb.Append(column[i]);
if (i > 0)
sb.Append(",");
}
} else {
sb.Append("*");
}
sb.Append(" from ");
sb.Append(table);
if (!string.IsNullOrEmpty(where)) {
sb.Append(" where ");
sb.Append(where);
}
if (count > 0) {
sb.AppendFormat(" limit {0}", count);
}
DataSet ds = new DataSet();
ExecuteSql(dbbase, sb.ToString(), sqlparams, ds, againCount);
if (ds.Tables == null || ds.Tables.Count == 0)
return null;
return ds.Tables[0];
}
/// <summary>
/// 执行sql命令
/// </summary>
/// <param name="cmd">指令</param>
/// <param name="dbname">数据库名称</param>
/// <param name="ds">null表示不需要查询结果集 有值将会把结果集放到 ds中</param>
/// <param name="againCount">重试次数 小于0表示使用配置值 大于等于0表示自定义</param>
public override int Execute(MySqlCommand cmd, string dbname, DataSet ds = null, int againCount = -1) {
// Debug.Debug.Log("执行sql指令!");
int result = 0;
int exceCnt = 0; //执行次数
//重试次数
if (againCount < 0)
againCount = errorTryAgainCount;
try {
while (exceCnt <= againCount) {
try {
if (cmd.Connection.State == ConnectionState.Closed)
cmd.Connection.Open();
cmd.Connection.ChangeDatabase(dbname);
if (ds == null)
result = cmd.ExecuteNonQuery();
else {
using(MySqlDataAdapter sda = new MySqlDataAdapter()) {
sda.SelectCommand = cmd;
sda.Fill(ds);
}
}
break;
} catch (Exception e) {
cmd.Connection.Close();
if (exceCnt < againCount)
Debug.Debug.Error("数据库执行错误重试次数:" + exceCnt + ",错误类型:" + e.Message);
else
throw e;
}
int time = errorAgainTime + errorAgainTimeInc * exceCnt;
exceCnt++; //执行次数+1
Thread.Sleep(time);
}
} finally {
_ConnPool.PutInto(cmd.Connection); //放入链接
}
return result;
}
/// <summary>
/// 开始一个事务
/// </summary>
/// <param name="dbbase">数据库</param>
/// <returns></returns>
public override IProdureParameter<MySqlCommand> BeginTransaction(string dbbase) {
MySqlCommand cmd = new MySqlCommand();
MySqlProcedureParameter spp = new MySqlProcedureParameter(cmd, dbbase);
try {
cmd.Connection = GetConnection(dbbase);
if (cmd.Connection.State == ConnectionState.Closed)
cmd.Connection.Open();
cmd.Connection.ChangeDatabase(dbbase);
cmd.Transaction = cmd.Connection.BeginTransaction(); //开始事务,还可以设置隔离等级
} catch (Exception e) {
spp.exception = e;
}
return spp;
}
/// <summary>
/// 事务添加sql语句
/// </summary>
/// <param name="spp"></param>
/// <param name="sql"></param>
/// <param name="sqlparams"></param>
public override int TransactionAddSql(IProdureParameter<MySqlCommand> spp, string sql, List<ISqlAloneParameter<MySqlDbType>> sqlparams = null) {
if (spp.isException)
return 0;
try {
MySqlCommand cmd = spp.command;
cmd.CommandText = sql;
cmd.Parameters.Clear();
if (sqlparams != null) {
int len = sqlparams.Count;
for (int i = 0; i < len; i++) {
ISqlAloneParameter<MySqlDbType> sap = sqlparams[i];
cmd.Parameters.Add(sap.paramname, sap.type, sap.valueLength);
cmd.Parameters[sap.paramname].Value = sap.paramvalue;
}
}
return cmd.ExecuteNonQuery();
} catch (Exception e) {
spp.exception = e;
throw e;
}
}
/// <summary>
/// 提交事务
/// </summary>
/// <param name="spp"></param>
/// <returns>true 表示提交成功 false 表示提交失败</returns>
public override bool SubTransaction(IProdureParameter<MySqlCommand> spp) {
try {
spp.command.Transaction.Commit();
} catch (Exception e) {
spp.exception = e;
spp.command.Transaction.Rollback();
} finally {
_ConnPool.PutInto(spp.command.Connection);
spp.Dispose();
}
return spp.exception == null;
}
/// <summary>
/// 回滚事务
/// </summary>
/// <param name="spp"></param>
public override void RollbackTransaction(IProdureParameter<MySqlCommand> spp) {
try {
spp.command.Transaction.Rollback();
} catch (Exception ex) {
Debug.Debug.Error("RollbackTransaction ex: " + ex);
}
}
#region
/// <summary>
/// 异步提交存储过程
/// </summary>
public override async Task<Dictionary<string, object>> SubProcedureAsync(IProdureParameter<MySqlCommand> spp, DataSet ds = null, int againCount = -1) {
if (spp == null || spp.command == null)
throw new Exception("param spp null");
int cnt = await ExecuteAsync(spp.command, spp.dbName, ds, againCount);
Dictionary<string, object> dic = new Dictionary<string, object>();
var pms = spp.command.Parameters;
int len = pms.Count;
for (int i = 0; i < len; i++) {
dic.Add(pms[i].ParameterName, pms[i].Value);
}
if (!dic.ContainsKey(DbConst.RowsAffected)) {
dic[DbConst.RowsAffected] = cnt;
}
spp.Dispose();
return dic;
}
/// <summary>
/// 异步执行sql语句
/// </summary>
public override async Task<int> ExecuteSqlAsync(string dbbase, string sql, IEnumerable<MySqlParameter> mysqlparams = null, DataSet ds = null, int againCount = -1) {
MySqlCommand cmd = new MySqlCommand(sql, await GetConnectionAsync(dbbase));
cmd.CommandType = CommandType.Text;
if (mysqlparams != null) {
foreach (var param in mysqlparams) {
cmd.Parameters.Add(param);
}
}
return await ExecuteAsync(cmd, dbbase, ds, againCount);
}
/// <summary>
/// 异步查询
/// </summary>
public override async Task<DataTable> SelectAsync(string dbbase, string table, string[] column = null, string where = null, int count = 0, int againCount = -1, IEnumerable<MySqlParameter> sqlparams = null) {
StringBuilder sb = new StringBuilder();
sb.Append("select ");
if (column != null && column.Length > 0) {
int len = column.Length;
for (int i = len - 1; i >= 0; i--) {
sb.Append(column[i]);
if (i > 0)
sb.Append(",");
}
} else {
sb.Append("*");
}
sb.Append(" from ");
sb.Append(table);
if (!string.IsNullOrEmpty(where)) {
sb.Append(" where ");
sb.Append(where);
}
if (count > 0) {
sb.AppendFormat(" limit {0}", count);
}
DataSet ds = new DataSet();
await ExecuteSqlAsync(dbbase, sb.ToString(), sqlparams, ds, againCount);
if (ds.Tables == null || ds.Tables.Count == 0)
return null;
return ds.Tables[0];
}
/// <summary>
/// 异步执行sql命令
/// </summary>
public override async Task<int> ExecuteAsync(MySqlCommand cmd, string dbname, DataSet ds = null, int againCount = -1) {
int result = 0;
int exceCnt = 0;
if (againCount < 0)
againCount = errorTryAgainCount;
try {
while (exceCnt <= againCount) {
try {
if (cmd.Connection.State == ConnectionState.Closed)
await cmd.Connection.OpenAsync();
cmd.Connection.ChangeDatabase(dbname);
if (ds == null)
result = await cmd.ExecuteNonQueryAsync();
else {
using(MySqlDataAdapter sda = new MySqlDataAdapter()) {
sda.SelectCommand = cmd;
sda.Fill(ds);
}
}
break;
} catch (Exception e) {
cmd.Connection.Close();
if (exceCnt < againCount)
Debug.Debug.Error("数据库异步执行错误重试次数:" + exceCnt + ",错误类型:" + e.Message);
else
throw;
}
int time = errorAgainTime + errorAgainTimeInc * exceCnt;
exceCnt++;
await Task.Delay(time);
}
} finally {
_ConnPool.PutInto(cmd.Connection);
}
return result;
}
#endregion
/// <summary>
/// 保存每个存储过程为单个文件
/// </summary>
/// <param name="dbbase">那个数据库的</param>
/// <param name="path">存储的路径</param>
/// <param name="method">如果导出其中一个存储过程,则填存储过程名称</param>
public void SaveMethod(string dbbase, string path, string method = null) {
DataTable dt;
if (method == null)
dt = Select("information_schema", "routines", new string[] { "routine_name", "routine_type" }, string.Format("routine_schema='{0}'", dbbase));
else
dt = Select("information_schema", "routines", new string[] { "routine_name", "routine_type" }, string.Format("routine_schema='{0}' and routine_name='{1}'", dbbase, method));
foreach (DataColumn item in dt.Columns) {
Console.WriteLine(item.ColumnName);
}
int len = dt.Rows.Count;
for (int i = 0; i < len; i++) {
SaveMethod(dbbase, path, dt.Rows[i]["routine_name"] as string, dt.Rows[i]["routine_type"] as string);
}
}
/// <summary>
/// 保存方法
/// </summary>
/// <param name="dbbase"></param>
/// <param name="path"></param>
/// <param name="methodName"></param>
/// <param name="methodType"></param>
private void SaveMethod(string dbbase, string path, string methodName, string methodType) {
if (!Directory.Exists(path)) {
Directory.CreateDirectory(path);
}
string sql = string.Format("show create {0} {1};", methodType, methodName);
DataSet ds = new DataSet();
ExecuteSql(dbbase, sql, null, ds);
if (ds.Tables == null || ds.Tables.Count == 0 || ds.Tables[0].Rows == null || ds.Tables[0].Rows.Count == 0)
return;
path = string.Format("{0}/{1}.sql", path, methodName);
string sqlCode = string.Format("Drop {0} if exists {1};{2}", methodType, methodName, Environment.NewLine);
sqlCode += "Commit;" + Environment.NewLine;
sqlCode += ds.Tables[0].Rows[0][string.Format("Create {0}", methodType)] as string;
File.WriteAllText(path, sqlCode, Encoding.UTF8);
}
/// <summary>
/// 执行sql文件
/// </summary>
/// <param name="dbbase"></param>
/// <param name="path"></param>
public void Execute(string dbbase, string path) {
if (File.Exists(path)) {
try {
string sql = File.ReadAllText(path);
ExecuteSql(dbbase, sql);
} catch (Exception e) {
Console.WriteLine("运行出错:" + path + Environment.NewLine + e.ToString());
}
} else if (Directory.Exists(path)) {
DirectoryInfo dinfo = new DirectoryInfo(path);
string[] paths = Directory.GetDirectories(path);
foreach (var item in paths) {
Console.WriteLine(item);
Execute(dbbase, item);
}
FileInfo[] finfos = dinfo.GetFiles();
foreach (var fl in finfos) {
Execute(dbbase, fl.FullName);
}
}
}
}
}
#endif