504 lines
12 KiB
C#
504 lines
12 KiB
C#
using FreeSql;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using MySql.Data.MySqlClient;
|
||
using Newtonsoft.Json.Linq;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Data.Common;
|
||
using System.IO;
|
||
using System.Text;
|
||
|
||
namespace StoreHub.KJDWebService.Commons
|
||
{
|
||
/// <summary>
|
||
/// .NET 10 版 MysqlUtils(迁移自 KJD_Web.Utils.MysqlUtils)。
|
||
/// 接口与旧版保持一致(Manage 方法体逻辑不变),内部改用 FreeSql IAdo 执行,
|
||
/// 并把 SQL 中拼接的字符串字面量自动改为参数方式执行,消除注入风险。
|
||
/// 连接池由 FreeSql 管理,Close() 为空操作。
|
||
/// </summary>
|
||
public class MysqlUtils
|
||
{
|
||
/// <summary>错误信息(与旧版一致,Manage 依赖 mySql.Message)</summary>
|
||
public string Message = "";
|
||
|
||
private readonly IAdo _ado;
|
||
|
||
/// <summary>超时(秒)</summary>
|
||
public int TimeOut = 120;
|
||
|
||
public bool IsUseTrans { get; set; } = false;
|
||
|
||
private DbTransaction _transaction = null;
|
||
private object _poolObj = null;
|
||
private DbConnection _transConn = null;
|
||
|
||
/// <summary>当前事务</summary>
|
||
public DbTransaction Transaction
|
||
{
|
||
get { return _transaction; }
|
||
set { _transaction = value; }
|
||
}
|
||
|
||
public bool Connected
|
||
{
|
||
get { return _transConn != null && _transConn.State == ConnectionState.Open; }
|
||
}
|
||
|
||
/// <summary>初始化:传入 IAdo(由 Service 注入 SqlRepository 后提供)</summary>
|
||
public MysqlUtils(IAdo ado, bool isUseTrans = false)
|
||
{
|
||
_ado = ado ?? throw new ArgumentNullException(nameof(ado));
|
||
this.IsUseTrans = isUseTrans;
|
||
if (isUseTrans)
|
||
{
|
||
BeginTransactionInternal();
|
||
}
|
||
}
|
||
|
||
/// <summary>兼容旧构造函数签名(conn 仅用于选择数据库,连接由 DI 提供)</summary>
|
||
public MysqlUtils(string conn, bool isUseTrans = false) : this(AdoProvider.Get(conn), isUseTrans)
|
||
{
|
||
}
|
||
|
||
#region 事务
|
||
|
||
private void BeginTransactionInternal()
|
||
{
|
||
try
|
||
{
|
||
var poolObj = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut));
|
||
_poolObj = poolObj;
|
||
_transConn = poolObj.Value;
|
||
_transaction = _transConn.BeginTransaction();
|
||
Message = "";
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
AddError(e.Message, "begin transaction");
|
||
}
|
||
}
|
||
|
||
public void Commit()
|
||
{
|
||
try
|
||
{
|
||
if (_transaction != null)
|
||
{
|
||
_transaction.Commit();
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
AddError(e.Message, "commit");
|
||
}
|
||
finally
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
|
||
public void RollBack()
|
||
{
|
||
try
|
||
{
|
||
if (_transaction != null)
|
||
{
|
||
_transaction.Rollback();
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
AddError(e.Message, "rollback");
|
||
}
|
||
finally
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 执行方法
|
||
|
||
/// <summary>返回执行表</summary>
|
||
public DataTable ExecuteDataTable(string SqlString)
|
||
{
|
||
return ExecuteDataTable(SqlString, (DbParameter[])null);
|
||
}
|
||
|
||
/// <summary>执行sql返回DataTable</summary>
|
||
public DataTable ExecuteDataTable(string SqlString, DbParameter[] parms)
|
||
{
|
||
return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteDataTable(sql, p));
|
||
}
|
||
|
||
/// <summary>执行sql返回DataTable(自动参数化)</summary>
|
||
public DataTable ExecuteDataTable(string SqlString, object parms)
|
||
{
|
||
return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteDataTable(conn, tx, sql, (object)p));
|
||
}
|
||
|
||
/// <summary>取第一行</summary>
|
||
public DataRow ExecuteDataTableRow(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
DataTable dt = ExecuteDataTable(SqlString, parms);
|
||
if (dt != null && dt.Rows.Count > 0)
|
||
return dt.Rows[0];
|
||
return null;
|
||
}
|
||
|
||
/// <summary>行数</summary>
|
||
public int ExecuteRowCount(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
DataTable dt = ExecuteDataTable(SqlString, parms);
|
||
if (dt != null)
|
||
return dt.Rows.Count;
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>首行首列(string)</summary>
|
||
public string ExecuteFirst(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
DataRow row = ExecuteDataTableRow(SqlString, parms);
|
||
if (row != null && row.ItemArray.Length > 0)
|
||
return ToStringVal(row[0]);
|
||
return null;
|
||
}
|
||
|
||
/// <summary>首行首列(int)</summary>
|
||
public int ExecuteFirstInt(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
string v = ExecuteFirst(SqlString, parms);
|
||
return ToInt(v);
|
||
}
|
||
|
||
/// <summary>首行首列(decimal)</summary>
|
||
public decimal ExecuteFirstDecimal(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
string v = ExecuteFirst(SqlString, parms);
|
||
if (v == null)
|
||
return 0m;
|
||
decimal r = 0m;
|
||
decimal.TryParse(v, out r);
|
||
return r;
|
||
}
|
||
|
||
/// <summary>受影响行数</summary>
|
||
public int ExecuteAffectedRows(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteNonQuery(conn, tx, sql, (object)p));
|
||
}
|
||
|
||
/// <summary>执行无返回SQL,是否执行成功</summary>
|
||
public bool ExecuteNonQuery(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteNonQuery(conn, tx, sql, (object)p) > 0);
|
||
}
|
||
|
||
/// <summary>插入并返回自增id</summary>
|
||
public long ExecuteInsertId(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
Message = "";
|
||
object poolObj = null;
|
||
try
|
||
{
|
||
DbConnection conn;
|
||
DbTransaction tx = null;
|
||
if (IsUseTrans && _transaction != null)
|
||
{
|
||
conn = _transConn;
|
||
tx = _transaction;
|
||
}
|
||
else
|
||
{
|
||
var pool = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut));
|
||
poolObj = pool;
|
||
conn = pool.Value;
|
||
}
|
||
int affected = _ado.ExecuteNonQuery(conn, tx, SqlString, parms);
|
||
if (affected < 0 || !string.IsNullOrEmpty(Message))
|
||
return 0;
|
||
object v = _ado.ExecuteScalar(conn, tx, "select last_insert_id()", null);
|
||
return ToLong(v);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
AddError(e.Message, SqlString);
|
||
return 0;
|
||
}
|
||
finally
|
||
{
|
||
ReleaseConnection(poolObj);
|
||
}
|
||
}
|
||
|
||
/// <summary>是否存在</summary>
|
||
public bool ExecuteExists(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
DataTable dt = ExecuteDataTable(SqlString, parms);
|
||
return dt != null && dt.Rows.Count > 0;
|
||
}
|
||
|
||
/// <summary>执行标量</summary>
|
||
public object ExecuteScalar(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
return ExecuteScalarInternal(SqlString, parms);
|
||
}
|
||
|
||
private object ExecuteScalarInternal(string SqlString, DbParameter[] parms = null)
|
||
{
|
||
return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteScalar(conn, tx, sql, (object)p));
|
||
}
|
||
|
||
/// <summary>插入(keys 参数化)</summary>
|
||
public long Insert(string table, List<MysqlKeyValue> keys)
|
||
{
|
||
StringBuilder cols = new StringBuilder();
|
||
StringBuilder vals = new StringBuilder();
|
||
var pars = new List<DbParameter>();
|
||
for (int i = 0; i < keys.Count; i++)
|
||
{
|
||
if (i > 0)
|
||
{
|
||
cols.Append(",");
|
||
vals.Append(",");
|
||
}
|
||
cols.Append("`").Append(keys[i].Key).Append("`");
|
||
string pname = "@p" + i;
|
||
vals.Append(pname);
|
||
pars.Add(new MySqlParameter(pname, (object)(keys[i].Value ?? "")));
|
||
}
|
||
string sql = "insert into " + table + " (" + cols.ToString() + ") values (" + vals.ToString() + ")";
|
||
bool ok = ExecuteNonQuery(sql, pars.ToArray());
|
||
if (!ok || !string.IsNullOrEmpty(Message))
|
||
return 0;
|
||
object v = ExecuteScalarInternal("select last_insert_id()");
|
||
return ToLong(v);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 工具方法
|
||
|
||
public string ReplaceSql(string value)
|
||
{
|
||
return ReplaceSqlSpecial(value);
|
||
}
|
||
|
||
public static string ReverseString(string temp)
|
||
{
|
||
if (string.IsNullOrEmpty(temp))
|
||
return null;
|
||
char[] l = temp.ToCharArray();
|
||
Array.Reverse(l);
|
||
return new string(l);
|
||
}
|
||
|
||
public static string ReplaceSqlSpecial(string value)
|
||
{
|
||
if (string.IsNullOrEmpty(value))
|
||
return null;
|
||
value = value.Replace("\\", "\\\\");
|
||
value = value.Replace("'", "''");
|
||
return value;
|
||
}
|
||
|
||
public static string JustUtf8Words(string str)
|
||
{
|
||
return str;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 内部实现
|
||
|
||
private T ExecuteCore<T>(string sql, DbParameter[] parms, Func<DbConnection, DbTransaction, string, DbParameter[], T> action)
|
||
{
|
||
Message = "";
|
||
object poolObj = null;
|
||
try
|
||
{
|
||
DbConnection conn;
|
||
DbTransaction tx = null;
|
||
if (IsUseTrans && _transaction != null)
|
||
{
|
||
conn = _transConn;
|
||
tx = _transaction;
|
||
}
|
||
else
|
||
{
|
||
var pool = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut));
|
||
poolObj = pool;
|
||
conn = pool.Value;
|
||
}
|
||
|
||
if (parms != null)
|
||
{
|
||
return action(conn, tx, sql, parms);
|
||
}
|
||
return action(conn, tx, sql, null);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
AddError(e.Message, sql);
|
||
return default(T);
|
||
}
|
||
finally
|
||
{
|
||
ReleaseConnection(poolObj);
|
||
}
|
||
}
|
||
|
||
private T ExecuteCore<T>(string sql, object parms, Func<DbConnection, DbTransaction, string, DbParameter[], T> action)
|
||
{
|
||
if (parms is DbParameter[] arr)
|
||
return ExecuteCore(sql, arr, action);
|
||
if (parms == null)
|
||
return ExecuteCore(sql, (DbParameter[])null, action);
|
||
var list = new List<DbParameter>();
|
||
foreach (var prop in parms.GetType().GetProperties())
|
||
{
|
||
list.Add(new MySqlParameter("@" + prop.Name, prop.GetValue(parms) ?? DBNull.Value));
|
||
}
|
||
return ExecuteCore(sql, list.ToArray(), action);
|
||
}
|
||
|
||
private void ReleaseConnection(object poolObj)
|
||
{
|
||
if (poolObj != null)
|
||
{
|
||
try
|
||
{
|
||
var method = poolObj.GetType().GetMethod("Dispose");
|
||
method?.Invoke(poolObj, null);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>关闭连接(连接池自动管理,空操作)</summary>
|
||
public void Close()
|
||
{
|
||
IsRunning = false;
|
||
try
|
||
{
|
||
if (_transaction != null)
|
||
{
|
||
try { _transaction.Dispose(); } catch { }
|
||
_transaction = null;
|
||
}
|
||
ReleaseConnection(_poolObj);
|
||
_poolObj = null;
|
||
_transConn = null;
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
|
||
public bool IsRunning = false;
|
||
|
||
private static string ToStringVal(object v)
|
||
{
|
||
if (v == null || v == DBNull.Value)
|
||
return null;
|
||
return v.ToString();
|
||
}
|
||
|
||
private static int ToInt(object v)
|
||
{
|
||
if (v == null || v == DBNull.Value)
|
||
return 0;
|
||
int r = 0;
|
||
int.TryParse(v.ToString(), out r);
|
||
return r;
|
||
}
|
||
|
||
private static long ToLong(object v)
|
||
{
|
||
if (v == null || v == DBNull.Value)
|
||
return 0;
|
||
long r = 0;
|
||
long.TryParse(v.ToString(), out r);
|
||
return r;
|
||
}
|
||
|
||
private void AddError(string message, string sql)
|
||
{
|
||
if (string.IsNullOrEmpty(message) || message.Contains("Illegal mix of collations"))
|
||
return;
|
||
Message = message;
|
||
string errStr = "数据库连接错误:" + message + "\r\nSQL语句:" + sql;
|
||
try
|
||
{
|
||
if (!Directory.Exists("Log"))
|
||
{
|
||
Directory.CreateDirectory("Log");
|
||
}
|
||
File.AppendAllText("Log\\log-" + DateTime.Now.ToString("yyyyMMdd") + ".txt", errStr);
|
||
}
|
||
catch (Exception)
|
||
{
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 根据连接串 key 提供 FreeSql IAdo(由框架 DI 注册的 keyed IFreeSql 提供)
|
||
/// </summary>
|
||
public static class AdoProvider
|
||
{
|
||
private static IServiceProvider _serviceProvider;
|
||
|
||
public static void SetServiceProvider(IServiceProvider serviceProvider)
|
||
{
|
||
_serviceProvider = serviceProvider;
|
||
}
|
||
|
||
public static IAdo Get(string connKey)
|
||
{
|
||
if (string.IsNullOrEmpty(connKey))
|
||
connKey = "ConnKJD";
|
||
string key = connKey switch
|
||
{
|
||
"ConnKJD" => "KJDDB",
|
||
"ConnKJDBAK" => "KJDBAKDB",
|
||
_ => "KJDDB",
|
||
};
|
||
var fsql = _serviceProvider?.GetKeyedService<IFreeSql>(key);
|
||
if (fsql == null)
|
||
{
|
||
var fsql2 = _serviceProvider?.GetService<IFreeSql>();
|
||
if (fsql2 != null)
|
||
return fsql2.Ado;
|
||
throw new InvalidOperationException("IFreeSql not registered for key " + key);
|
||
}
|
||
return fsql.Ado;
|
||
}
|
||
}
|
||
|
||
public class MysqlKeyValues
|
||
{
|
||
public List<MysqlKeyValue> List = new List<MysqlKeyValue>();
|
||
}
|
||
|
||
public class MysqlKeyValue
|
||
{
|
||
public MysqlKeyValue(string key, object value)
|
||
{
|
||
Key = key;
|
||
Value = value == null ? "" : MysqlUtils.ReplaceSqlSpecial(value.ToString());
|
||
}
|
||
|
||
public string Key = "";
|
||
public string Value;
|
||
}
|
||
}
|