支持热更新,更新时间相差3秒以上的会自动重新加载
修改配置文件不用重启IIS服务
OpenApiConfig
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace UFIDA.U9.SH.OPENAPISV.Common
{
/// <summary>
/// 接口配置信息
/// </summary>
public class OpenApiConfig
{
private static Dictionary<string, string> _configCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private static DateTime _lastWriteTime;
private static DateTime _lastCheckTime;
private static readonly object _lock = new object();
private const int CheckIntervalSeconds = 3;
private const string ConfigName = "SH_OpenApiConfig.config";
private static readonly string FilePath;
static OpenApiConfig()
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
// 核心逻辑:智能定位 Portal 目录(保证U9主服务、调度服务、审批流服务都统一读取Portal目录下的配置文件)
// 兼容:Portal, Portal/AppServer, MailService, MailService/Libs
string portalPath = FindPortalDirectory(basePath);
FilePath = Path.Combine(portalPath, ConfigName);
}
/// <summary>
/// 智能查找 Portal 目录(纯字符串操作优化版)
/// </summary>
private static string FindPortalDirectory(string startPath)
{
string currentPath = startPath;
// 循环直到根目录(GetDirectoryName 返回 null 表示到达根目录)
while (!string.IsNullOrEmpty(currentPath))
{
// 1. 获取当前文件夹名称
// 注意:Path.GetFileName 对文件夹路径使用时,返回的是最后一级文件夹的名字
string dirName = Path.GetFileName(currentPath.TrimEnd(Path.DirectorySeparatorChar));
// 2. 【自身匹配】如果当前就是 Portal
if (string.Equals(dirName, "Portal", StringComparison.OrdinalIgnoreCase))
{
return currentPath;
}
// 3. 【子目录匹配】检查当前目录下是否有 Portal 子文件夹
// 场景:在 C:\yonyou\U9CE 下发现了 Portal 子目录
string potentialPortal = Path.Combine(currentPath, "Portal");
if (Directory.Exists(potentialPortal))
{
return potentialPortal;
}
// 4. 向上移动一级
currentPath = Path.GetDirectoryName(currentPath);
}
// 如果没找到,返回原始路径
return startPath;
}
private static void EnsureConfigLoaded()
{
if ((DateTime.Now - _lastCheckTime).TotalSeconds < CheckIntervalSeconds && _configCache.Count > 0)
return;
_lastCheckTime = DateTime.Now;
if (!File.Exists(FilePath))
{
if (_configCache.Count == 0)
throw new FileNotFoundException($"配置文件【{ConfigName}】未找到。\n目标路径: {FilePath}\n当前上下文: {AppDomain.CurrentDomain.BaseDirectory}");
return;
}
DateTime currentWriteTime = File.GetLastWriteTime(FilePath);
if (_lastWriteTime >= currentWriteTime && _configCache.Count > 0) return;
lock (_lock)
{
if (_lastWriteTime >= currentWriteTime && _configCache.Count > 0) return;
LoadConfigFromXml();
_lastWriteTime = currentWriteTime;
}
}
private static void LoadConfigFromXml()
{
try
{
// 使用 LINQ to XML 加载
XElement root = XElement.Load(FilePath);
// 解析 <add key="..." value="..." /> 格式
var newCache = root.Descendants("add")
.Where(x => x.Attribute("key") != null)
.ToDictionary(
x => x.Attribute("key").Value,
x => x.Attribute("value")?.Value ?? "",
StringComparer.OrdinalIgnoreCase
);
_configCache = newCache;
}
catch (Exception ex)
{
throw new Exception($"加载配置文件【{ConfigName}】失败。\n路径: {FilePath}\n错误: {ex.Message}", ex);
}
}
/// <summary>
/// 获取配置值
/// </summary>
public static string Get(string key, string orgCode = null)
{
EnsureConfigLoaded();
string value;
// 优先匹配 Key_OrgCode
if (!string.IsNullOrEmpty(orgCode) && _configCache.TryGetValue($"{key}_{orgCode}", out value)) return value;
// 次优先匹配原始 Key
if (_configCache.TryGetValue(key, out value)) return value;
return null;
}
/// <summary>
/// 获取整数配置值
/// </summary>
public static int GetInt(string key, int defaultValue)
{
string val = Get(key);
return int.TryParse(val, out int result) ? result : defaultValue;
}
/// <summary>
/// 获取长整数配置值
/// </summary>
public static long GetInt64(string key, long defaultValue)
{
string val = Get(key);
return long.TryParse(val, out long result) ? result : defaultValue;
}
}
}
示例配置文件SH_OpenApiConfig.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- 通用配置 -->
<add key="Idempotency_Timeout" value="30" desc="幂等性僵尸进程超时时间 (单位: 分钟)" />
<add key="HttpReplay_Timeout" value="10" desc="日志列表重发请求,Http超时时间(分钟)" />
<add key="CenterOrg" value="1002412044341256" desc="数据中心组织ID,实体扩展段查询使用,如供应商可能在数据中心而不是当前组织" />
<!-- 业务配置 -->
<add key="AutoDistributeOrgs" value="10" desc="自动下发组织(多个使用英文,号隔开)" />
<add key="PurPriceList_Create_DefCurrency" value="C001" desc="厂商价目表,默认币种" />
<add key="PurPriceAdjustment_DefDocumentType" value="PMTJ0001" desc="厂商价目表,默认调整单单据类型" />
<add key="SOLineThirdField" value="DescFlexField.PrivateDescSeg2" desc="NC销售订单行ID(销售订单行私有段2)" />
</appSettings>
</configuration>
PushConfig
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using UFIDA.U9.Base.Profile;
using UFSoft.UBF.PL;
namespace UFIDA.U9.SH.HER.QXPubBP.Common
{
/// <summary>
/// 推送配置管理类 (JSON版)
/// 特性:支持 // 注释、线程安全、热更新、字典缓存
/// </summary>
public class PushConfig
{
/// <summary>
/// 根据组织判断是否需要推送到NC
/// </summary>
/// <param name="orgCode">组织编码</param>
/// <returns></returns>
public static bool ShouldPushToNCByOrg(string orgCode)
{
ProfileValue pv = ProfileValue.Finder.Find("Profile=202601200001 and Organization.Code=@OrgCode", new OqlParam(orgCode));
if (pv == null) return false;
bool.TryParse(pv.Value, out bool isPush);
return isPush;
}
#region 内部成员
private static Dictionary<string, string> _configCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private static DateTime _lastWriteTime;
private static DateTime _lastCheckTime;
private static readonly object _lock = new object();
private const int CheckIntervalSeconds = 3;
private const string ConfigName = "SH_PushConfig.config";
private static readonly string FilePath;
#endregion
static PushConfig()
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
// 核心逻辑:智能定位 Portal 目录(保证U9主服务、调度服务、审批流服务都统一读取Portal目录下的配置文件)
// 兼容:Portal, Portal/AppServer, MailService, MailService/Libs
string portalPath = FindPortalDirectory(basePath);
FilePath = Path.Combine(portalPath, ConfigName);
}
/// <summary>
/// 智能查找 Portal 目录(纯字符串操作优化版)
/// </summary>
private static string FindPortalDirectory(string startPath)
{
string currentPath = startPath;
// 循环直到根目录(GetDirectoryName 返回 null 表示到达根目录)
while (!string.IsNullOrEmpty(currentPath))
{
// 1. 获取当前文件夹名称
// 注意:Path.GetFileName 对文件夹路径使用时,返回的是最后一级文件夹的名字
string dirName = Path.GetFileName(currentPath.TrimEnd(Path.DirectorySeparatorChar));
// 2. 【自身匹配】如果当前就是 Portal
if (string.Equals(dirName, "Portal", StringComparison.OrdinalIgnoreCase))
{
return currentPath;
}
// 3. 【子目录匹配】检查当前目录下是否有 Portal 子文件夹
// 场景:在 C:\yonyou\U9CE 下发现了 Portal 子目录
string potentialPortal = Path.Combine(currentPath, "Portal");
if (Directory.Exists(potentialPortal))
{
return potentialPortal;
}
// 4. 向上移动一级
currentPath = Path.GetDirectoryName(currentPath);
}
// 如果没找到,返回原始路径
return startPath;
}
private static void EnsureConfigLoaded()
{
if ((DateTime.Now - _lastCheckTime).TotalSeconds < CheckIntervalSeconds && _configCache.Count > 0)
return;
_lastCheckTime = DateTime.Now;
if (!File.Exists(FilePath))
{
if (_configCache.Count == 0)
throw new FileNotFoundException($"配置文件【{ConfigName}】未找到。\n目标路径: {FilePath}\n当前上下文: {AppDomain.CurrentDomain.BaseDirectory}");
return;
}
DateTime currentWriteTime = File.GetLastWriteTime(FilePath);
if (_lastWriteTime >= currentWriteTime && _configCache.Count > 0) return;
lock (_lock)
{
if (_lastWriteTime >= currentWriteTime && _configCache.Count > 0) return;
LoadConfigFromXml();
_lastWriteTime = currentWriteTime;
}
}
private static void LoadConfigFromXml()
{
try
{
// 使用 LINQ to XML 加载
XElement root = XElement.Load(FilePath);
// 解析 <add key="..." value="..." /> 格式
var newCache = root.Descendants("add")
.Where(x => x.Attribute("key") != null)
.ToDictionary(
x => x.Attribute("key").Value,
x => x.Attribute("value")?.Value ?? "",
StringComparer.OrdinalIgnoreCase
);
_configCache = newCache;
}
catch (Exception ex)
{
throw new Exception($"加载配置文件【{ConfigName}】失败。\n路径: {FilePath}\n错误: {ex.Message}", ex);
}
}
/// <summary>
/// 获取配置值
/// </summary>
public static string Get(string key, string orgCode = null)
{
EnsureConfigLoaded();
string value;
// 优先匹配 Key_OrgCode
if (!string.IsNullOrEmpty(orgCode) && _configCache.TryGetValue($"{key}_{orgCode}", out value)) return value;
// 次优先匹配原始 Key
if (_configCache.TryGetValue(key, out value)) return value;
return null;
}
/// <summary>
/// 获取整数配置值
/// </summary>
public static int GetInt(string key, int defaultValue)
{
string val = Get(key);
return int.TryParse(val, out int result) ? result : defaultValue;
}
/// <summary>
/// 获取长整数配置值
/// </summary>
public static long GetInt64(string key, long defaultValue)
{
string val = Get(key);
return long.TryParse(val, out long result) ? result : defaultValue;
}
}
}
示例配置文件SH_PushConfig.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="HttpPush_Timeout" value="10" desc="HTTP请求超时时间(分钟)" />
<add key="NCSync_BaseUrl" value="http://172.16.4.12:8054" desc="NC接口地址" />
<add key="NCSync_ItemMaster" value="/uapws/rest/service/u9/synMaterial" desc="NC料品同步接口路由" />
<add key="NCSync_ItemMaster_PushOrg" value="10" desc="NC料品同步的组织信息" />
<add key="NCSync_ItemMaster_DefMattaxes" value="CN001" desc="NC料品同步的默认税组合编码" />
</appSettings>
</configuration>
评论 (0)