Files
StoreWeb/StoreHub.KJDWebService/Commons/MysqlUtils.cs
T
2026-08-21 16:46:36 +08:00

504 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}