feat: 基金管理人取数支持跨环境库名——新增 DbSchema 库名插值,bigdata 连接串指向 96/glms_bigdata
问题:SQL 硬编码物理库名(bigdata./glms_bigdata.),关联库在不同环境库名不同,换环境即失效;
且仓库所有 appsettings 均未配 bigdata 连接串,本地/单测环境一直走 Unavailable 降级。
- 新增 YLErpDAL/DataBase/DbSchema:从连接串 database= 解析物理库名(含反引号、\w+ 白名单校验、进程级缓存),
跨库 SQL 写 DbSchema.Of("bigdata").mf_fundarchives ——代码只认逻辑连接名(等价 Java @Mapper 指定数据源),
物理库名归各环境 appsettings;将来 join ERP 主库用 DbSchema.Of("ylcms") 同法插值
- FundManagerLookupService:SQL 两处库名前缀改为插值(查找逻辑/CONVERT/COLLATE 不变),Unavailable 降级语义不变
- appsettings.local.json / UnitTestProject appsettings.json 补 bigdata=192.168.2.96:3306/glms_bigdata(抄现有 96 库连接串改库名)
- 新增 FundManagerLookupServiceTest(连 96 实库):DbSchema 解析断言 + Lookup 连通性
实测 511160.SH → Unique「东财基金管理有限公司」全链路打通;161210/630006 因 mf_investadvisoroutline 仅2行无映射返回 NotFound(数据覆盖问题)
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
using Dapper;
|
||||||
|
using YLErp.Modules.UnderlyingModule;
|
||||||
|
|
||||||
|
namespace YLErp.UnitTestProject.Modules.UnderlyingModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 基金管理人取数(bigdata 数据源)连通性与库名插值验证(连 96 测试库):
|
||||||
|
/// - DbSchema.Of 从连接串解析物理库名(glms_bigdata),SQL 不再硬编码库名、跨环境库名不同也能命中;
|
||||||
|
/// - Lookup 全链路(连接 96 → 插值 SQL 执行 → 结果归并)不返回 Unavailable 即为连通且 SQL 有效。
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class FundManagerLookupServiceTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void DbSchema_从连接串解析物理库名()
|
||||||
|
{
|
||||||
|
Assert.AreEqual("`glms_bigdata`", DbSchema.Of("bigdata"));
|
||||||
|
Assert.AreEqual("`glms_yltrs_ylcms`", DbSchema.Of("ylcms"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Lookup_连接96大数据库_非降级()
|
||||||
|
{
|
||||||
|
var service = new FundManagerLookupService();
|
||||||
|
//96 库现有测试数据 161210/630006/511160(SECUCODE 无后缀,与去后缀匹配逻辑一致),用库存代码验证全链路命中
|
||||||
|
foreach (var code in new[] { "511160.SH", "161210.SZ", "630006.SH" })
|
||||||
|
{
|
||||||
|
var result = service.Lookup(code);
|
||||||
|
Console.WriteLine($"code={code} → Status={result.Status}, InvestAdvisorName={result.InvestAdvisorName}");
|
||||||
|
Assert.AreNotEqual(FundManagerLookupStatus.Unavailable, result.Status,
|
||||||
|
$"code={code} 返回 Unavailable:96 bigdata 库不可达或插值 SQL 执行失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Diag_查看库存secucode样例与行数()
|
||||||
|
{
|
||||||
|
using var connection = new MySqlConnector.MySqlConnection(AppManager.GetConnectionString("bigdata"));
|
||||||
|
var count = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_fundarchives");
|
||||||
|
var samples = connection.Query<string>($"SELECT SECUCODE FROM {DbSchema.Of("bigdata")}.mf_fundarchives LIMIT 8");
|
||||||
|
Console.WriteLine($"mf_fundarchives 行数={count}, SECUCODE样例=[{string.Join(",", samples)}]");
|
||||||
|
var advCount = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_investadvisoroutline");
|
||||||
|
Console.WriteLine($"mf_investadvisoroutline 行数={advCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
|
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||||
},
|
},
|
||||||
"LibreOffice": {
|
"LibreOffice": {
|
||||||
"ExePath": "",
|
"ExePath": "",
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using MySqlConnector;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace YLErp.BLL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 跨库 SQL 的物理库名解析器:代码里只写逻辑连接名(appsettings ConnectionStrings 的 key,
|
||||||
|
/// 与 Java @Mapper 指定数据源名同语义),物理库名由各环境连接串的 database= 决定——
|
||||||
|
/// 同一逻辑库在不同环境库名不同(如 bigdata 在测试环境为 glms_bigdata),SQL 中硬编码库名会跨环境失败。
|
||||||
|
/// 用法:{@DbSchema.Of("bigdata")}.mf_fundarchives(返回带反引号的库名,可直接内插)。
|
||||||
|
/// 仅标识自家的 appsettings 连接串,值不来自用户输入;仍做 \w+ 白名单校验防御配置笔误。
|
||||||
|
/// </summary>
|
||||||
|
public static class DbSchema
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<string, string> Cache = new();
|
||||||
|
private static readonly Regex SafeIdentifier = new(@"^\w+$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取逻辑连接名对应的物理库名(形如 `glms_bigdata`,含反引号)。配置缺失或库名非法立即抛错——
|
||||||
|
/// 跨库 SQL 拼错库名在运行期才暴露更难排查,配置错误应尽早失败。
|
||||||
|
/// </summary>
|
||||||
|
public static string Of(string connectionKey)
|
||||||
|
{
|
||||||
|
return Cache.GetOrAdd(connectionKey, key =>
|
||||||
|
{
|
||||||
|
var connectionString = AppManager.GetConnectionString(key);
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"跨库SQL依赖的连接串未配置:{key}");
|
||||||
|
}
|
||||||
|
var database = new MySqlConnectionStringBuilder(connectionString).Database;
|
||||||
|
if (string.IsNullOrWhiteSpace(database) || !SafeIdentifier.IsMatch(database))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"连接串 {key} 缺少 database 或库名非法:{database}");
|
||||||
|
}
|
||||||
|
return "`" + database + "`";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,16 +30,21 @@ namespace YLErp.Modules.UnderlyingModule
|
|||||||
public string InvestAdvisorName { get; set; }
|
public string InvestAdvisorName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string LookupSql = @"
|
//物理库名由连接串 database= 决定(DbSchema 解析),各环境库名不同(如测试环境 glms_bigdata),
|
||||||
|
//SQL 不硬编码库名;连接开在 bigdata 数据源上,将来跨库 join ERP 主库时用 DbSchema.Of("ylcms") 同法插值
|
||||||
|
private string BuildLookupSql()
|
||||||
|
{
|
||||||
|
return $@"
|
||||||
SELECT
|
SELECT
|
||||||
ia.investadvisorcode AS InvestAdvisorCode,
|
ia.investadvisorcode AS InvestAdvisorCode,
|
||||||
ia.investadvisorname AS InvestAdvisorName
|
ia.investadvisorname AS InvestAdvisorName
|
||||||
FROM bigdata.mf_fundarchives AS fa
|
FROM {DbSchema.Of("bigdata")}.mf_fundarchives AS fa
|
||||||
INNER JOIN bigdata.mf_investadvisoroutline AS ia
|
INNER JOIN {DbSchema.Of("bigdata")}.mf_investadvisoroutline AS ia
|
||||||
ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||||
CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||||
CONVERT(TRIM(SUBSTRING_INDEX(@UnderlyingCode, '.', 1)) USING utf8mb4) COLLATE utf8mb4_unicode_ci";
|
CONVERT(TRIM(SUBSTRING_INDEX(@UnderlyingCode, '.', 1)) USING utf8mb4) COLLATE utf8mb4_unicode_ci";
|
||||||
|
}
|
||||||
|
|
||||||
public FundManagerLookupResult Lookup(string underlyingCode)
|
public FundManagerLookupResult Lookup(string underlyingCode)
|
||||||
{
|
{
|
||||||
@@ -58,7 +63,7 @@ WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = new MySqlConnection(connectionString);
|
using var connection = new MySqlConnection(connectionString);
|
||||||
var matches = connection.Query<FundManagerRow>(LookupSql, new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
var matches = connection.Query<FundManagerRow>(BuildLookupSql(), new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
||||||
.Where(row => !string.IsNullOrWhiteSpace(row.InvestAdvisorName))
|
.Where(row => !string.IsNullOrWhiteSpace(row.InvestAdvisorName))
|
||||||
.GroupBy(row => (row.InvestAdvisorCode ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase)
|
.GroupBy(row => (row.InvestAdvisorCode ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(group => group.Select(row => row.InvestAdvisorName.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray())
|
.Select(group => group.Select(row => row.InvestAdvisorName.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray())
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||||
|
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||||
},
|
},
|
||||||
"AppSettings": {
|
"AppSettings": {
|
||||||
"VirtualPathRoot": "",
|
"VirtualPathRoot": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user