从山证v2.3.0拷贝

This commit is contained in:
吴方海
2024-05-09 14:06:26 +08:00
parent 566ff33259
commit f9d8a256a6
4471 changed files with 1203456 additions and 9 deletions
@@ -0,0 +1,53 @@
using YLErp.Abstract;
namespace YLErp.Modules.DataCacheModule
{
class AppConfigUpdater : IDataUpdater, IJsonSerializable
{
public string TableName => nameof(AppConfig);
public void UpdateData(IEnumerable<string> updateKeyIds)
{
if (updateKeyIds is null || !updateKeyIds.Any())
{
return;
}
const string prefix = "ProjectConfig.";
updateKeyIds = updateKeyIds.Where(n => n.StartsWith(prefix)).Select(n => n.Substring(prefix.Length)).ToArray();
if (!updateKeyIds.Any())
{
return;
}
using (var db = DbContextFactory.GetYLDbContext())
{
var datas = db.AppConfig.Where(n => n.PGroup == "ProjectConfig" && updateKeyIds.Contains(n.PName))
.Select(n => new { n.PName, n.PValue }).ToArray();
var logger = LogFactory.GetLogger(nameof(AppConfigUpdater));
foreach (var item in datas)
{
PS.SetConfig(item.PName, item.PValue);
logger.Info($"更新PSConfig(name:{item.PName},value:{item.PValue})");
}
}
}
public string ToJson()
{
return PS.Config.ToJson();
}
public static readonly AppConfigUpdater Default;
static AppConfigUpdater()
{
Default = new AppConfigUpdater();
}
}
}
@@ -0,0 +1,255 @@
using BaseOUDAL;
using Newtonsoft.Json;
using System.Collections.Concurrent;
namespace YLErp.Modules.DataCacheModule
{
/// <summary>
/// Represents a manager for caching between HTTP requests (long term caching)
/// </summary>
public class DBCacheManager
{
public const int CacheTimeout = 3 * 60;
private List<string> TradeDetailReport = new List<string>() {
CacheTable.CCEmail,CacheTable.TradeDetailsBiaoTou,CacheTable.TradeDetailsBiaoWei,
CacheTable.TradeDetailsLuoKuan,CacheTable.TradeDetailsSendUser,CacheTable.TradeDerailsNeedAppendix
};
private List<string> TradeSwapDetailReport = new List<string>() {
CacheTable.SwapCCEmail,CacheTable.TradeSwapDetailsBiaoTou,CacheTable.TradeSwapDetailsBiaoWei,
CacheTable.TradeSwapDetailsLuoKuan,CacheTable.TradeSwapDetailsSendUser,CacheTable.TradeDerailsNeedAppendix
};
private List<string> ClientBalanceReport = new List<string>() {
CacheTable.DingShiDesc,CacheTable.TradeMarketSheets,CacheTable.TradeMarketSendUser,
CacheTable.LuoKuanDesc,CacheTable.MarginLuoKuanDesc,CacheTable.ClientBalanceReportTiltle,CacheTable.MarketCCEmail };
public static readonly DBCacheManager Single;
static DBCacheManager()
{
Single = new DBCacheManager();
}
readonly ConcurrentDictionary<string, CacheTable> _cacheDic;
protected DBCacheManager()
{
_cacheDic = new ConcurrentDictionary<string, CacheTable>(StringComparer.OrdinalIgnoreCase);
}
ConcurrentDictionary<string, CacheTable> CacheDic
{
get
{
if (_cacheDic.Count < 1)
{
using (ErpBaseContext db = new ErpBaseContext())
{
var caches = db.CacheTable.Where(c => c.ValidateDate >= DateTime.Now).ToArray();
foreach (var item in caches)
{
if (!string.IsNullOrEmpty(item.Key))
{
_cacheDic.TryAdd(item.Key + item.TemplateName, item);
}
}
if (_cacheDic.Count < 1)
{
var item = new CacheTable()
{
Key = "$" + Guid.NewGuid().ToString()
};
_cacheDic.TryAdd(item.Key, item);
}
}
}
return _cacheDic;
}
}
public string GetStr(string key, string template = "默认")
{
if (string.IsNullOrEmpty(key))
{
return string.Empty;
}
if (CacheDic.TryGetValue(key + template, out CacheTable ca))
{
return ca.Data;
}
using (ErpBaseContext db = new ErpBaseContext())
{
ca = db.CacheTable.FirstOrDefault(c => c.Key == key && c.TemplateName == template);
if (ca == null)
{
return string.Empty;
}
CacheDic.AddOrUpdate(key + template, ca, (k, old) => ca);
return ca.Data;
}
}
public void UpdateObj(string key, string value, string template = "默认")
{
if (string.IsNullOrEmpty(key))
{
return;
}
using (ErpBaseContext db = new ErpBaseContext())
{
var caObj = db.CacheTable.FirstOrDefault(c => c.Key == key && c.TemplateName == template);
if (caObj == null)
{
caObj = new CacheTable { Key = key, TemplateName = template, CreateDate = DateTime.Now };
db.CacheTable.Add(caObj);
}
caObj.Data = value;
caObj.ValidateDate = DateTime.Now.AddSeconds(CacheTimeout);
db.SaveChanges();
CacheDic.AddOrUpdate(key + template, caObj, (k, old) => caObj);
}
}
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key.</returns>
public virtual T Get<T>(string key, string template = "默认") where T : new()
{
if (!string.IsNullOrEmpty(key) && CacheDic.TryGetValue(key + template, out CacheTable ca))
{
return JsonConvert.DeserializeObject<T>(ca.Data);
}
return new T();
}
/// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
public virtual void Set(string key, object data, int cacheTime = CacheTimeout, string template = "默认")
{
if (string.IsNullOrEmpty(key) || data == null)
{
return;
}
lock (CacheDic)
{
var dataStr = string.Empty;
if (data == null)
{
dataStr = string.Empty;
}
else if (data is string str)
{
dataStr = str;
}
else
{
dataStr = JsonConvert.SerializeObject(data);
}
using (ErpBaseContext db = new ErpBaseContext())
{
var ca = db.CacheTable.FirstOrDefault(c => c.Key == key && c.TemplateName == template);
if (ca == null)
{
ca = new CacheTable { Key = key, TemplateName = template, CreateDate = DateTime.Now };
db.CacheTable.Add(ca);
}
ca.Data = dataStr;
ca.ValidateDate = DateTime.Now.AddSeconds(cacheTime);
db.SaveChanges();
CacheDic.AddOrUpdate(key + template, ca, (k, old) => ca);
}
}
}
/// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>Result</returns>
public virtual bool IsSet(string key, string template = "默认")
{
return !string.IsNullOrEmpty(key + template) && CacheDic.ContainsKey(key + template);
}
/// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
public virtual void Remove(string key, string template = "默认")
{
if (string.IsNullOrEmpty(key))
{
return;
}
CacheDic.TryRemove(key, out CacheTable cacheTable);
using (ErpBaseContext db = new ErpBaseContext())
{
db.CacheTable.Where(n => n.Key == key && n.TemplateName == template).ToArray()
.Where(c => c.Key == key).ToList().ForEach(c =>
{
c.ValidateDate = DateTime.MinValue;
});
db.SaveChanges();
}
}
public virtual List<string> GetTemplateNames(string reportType)
{
List<string> fliterList = new List<string>();
switch (reportType)
{
case "交易明细": fliterList = TradeDetailReport; break;
case "互换明细": fliterList = TradeSwapDetailReport; break;
case "结算报告": fliterList = ClientBalanceReport; break;
default:
throw new ServiceException("错误的报告类型");
}
using (ErpBaseContext db = new ErpBaseContext())
{
return db.CacheTable.Where(o => fliterList.Contains(o.Key)).Select(o => o.TemplateName).Distinct().ToList();
}
}
public virtual void RemoveTemplate(string reportType, string template)
{
List<string> fliterList = new List<string>();
switch (reportType)
{
case "交易明细": fliterList = TradeDetailReport; break;
case "互换明细": fliterList = TradeSwapDetailReport; break;
case "结算报告": fliterList = ClientBalanceReport; break;
default:
throw new ServiceException("错误的报告类型");
}
using (ErpBaseContext db = new ErpBaseContext())
{
var needDel = db.CacheTable.Where(o => fliterList.Contains(o.Key) && o.TemplateName == template);
if (needDel.Count() > 0)
{
db.CacheTable.RemoveRange(needDel);
db.SaveChanges();
}
else
{
throw new ServiceException("不存在 " + needDel + " 模板");
}
}
}
}
}
@@ -0,0 +1,777 @@
using BaseOUDAL;
using System.Collections;
using YieldChain.Commons;
using YLErp.Abstract;
using YLErp.Model;
namespace YLErp.Modules.DataCacheModule
{
/// <summary>
/// 数据缓存管理静态类(提供基础数据缓存及跟踪数据更新事件)
/// </summary>
public static partial class DataCacheManager
{
const int MaxTimeOut = 2 * 60 * 1000;
static DateTime _lastTime;
static readonly IYcLogger _logger;
static readonly Timer _updateTimer;
static readonly ThrottleAction _throttleValueDate;
static readonly TimeSpan[] MarketPriceTimes;
static readonly YLDBDataCacheUpdater _yLDBDataUpdater;
static readonly ClientDBDataCacheUpdater _clientDBDataUpdater;
static DataCacheManager()
{
_updateTimer = new Timer(TimerTask, null, 1000, Interval);
_throttleValueDate = new ThrottleAction(() => BLL.valuedateBLL.ResetValueDate(), 30);
_yLDBDataUpdater = new YLDBDataCacheUpdater();
_clientDBDataUpdater = new ClientDBDataCacheUpdater();
_logger = LogFactory.GetLogger(nameof(DataCacheManager));
DebugOut = AppContext.TryGetSwitch("AppSettings:DataCacheDebug", out _);
MarketPriceTimes = new TimeSpan[] {
new TimeSpan(3,0,0),new TimeSpan(8,30,0),
new TimeSpan(15,30,0),new TimeSpan(20,30,0)
};
}
/// <summary>
/// 当前最大traceID
/// </summary>
public static long MaxId => _yLDBDataUpdater.MaxId;
/// <summary>
/// 轮询间隔
/// </summary>
public static int Interval { get; private set; } = 2000;
/// <summary>
/// 调试日志是否输出
/// </summary>
public static bool DebugOut { get; set; }
/// <summary>
/// 这个方法在WEB项目IRegisteredObject接口中调用非常关键,
/// 可以有效避免定时更新被dispose掉
/// </summary>
public static void StartUpdate(int interval = 2000)
{
if (interval > 2000)
{
Interval = interval;
}
_updateTimer.Change(0, Interval);
}
/// <summary>
///
/// </summary>
public static void StopUpdate()
{
_updateTimer?.Change(Timeout.Infinite, 0);
}
/// <summary>
/// 更新一次
/// </summary>
public static void UpdateOnce()
{
TimerTask(null);
}
/// <summary>
/// 注册数据跟踪监听器(暂时只支持ylcms数据库)
/// </summary>
public static void RegisterDataTraceSink(IDataTraceSink sink)
{
_yLDBDataUpdater.RegisterDataTraceSink(sink);
}
/// <summary>
/// 取消数据跟踪监听器注册(暂时只支持ylcms数据库)
/// </summary>
public static void UnRegisterDataTraceSink(IDataTraceSink sink)
{
_yLDBDataUpdater.UnRegisterDataTraceSink(sink);
}
public static void ResetDataCache()
{
_yLDBDataUpdater.ResetDataCache();
_clientDBDataUpdater.ResetDataCache();
}
/// <summary>
/// 定时调用缓存更新操作
/// </summary>
private static void TimerTask(object state)
{
try
{
_updateTimer.Change(MaxTimeOut, MaxTimeOut);
//控制基础数据更新在10s的频率上
if (_lastTime.AddSeconds(10) < DateTime.Now)
{
_lastTime = DateTime.Now;
_yLDBDataUpdater.Update();
_clientDBDataUpdater.Update();
}
//更新标的价格
try
{
var delay = false;
var time = DateTime.Now.TimeOfDay;
for (var i = 0; i < MarketPriceTimes.Length; i += 2)
{
delay = time > MarketPriceTimes[i] && time < MarketPriceTimes[i + 1];
if (delay)
{
break;
}
}
if (GetUnderlyingDataSource().UpdatePrices(delay) && DebugOut)
{
_logger.Debug("更新标的价格");
}
}
catch (Exception ex)
{
_logger.Error(ex, "更新标的价格出错");
}
_throttleValueDate.Execute();
}
catch (Exception ex)
{
_logger.Error(ex, "TimerTask");
}
finally
{
_updateTimer.Change(Interval, Interval);
}
}
#region-----------
/// <summary>
/// 交易品种
/// </summary>
public static IDataSourceKey2<Variety> GetVarietyDataSource()
{
return VarietyDataSource.Default;
}
/// <summary>
/// 期权标的
/// </summary>
public static IUnderlyingDataSource GetUnderlyingDataSource()
{
return new UnderlyingDbDataSource();
}
/// <summary>
/// 场内期权基础数据
/// </summary>
public static IDataSourceKey2<ExchangeListOption> GetExchangeListOptionDataSource()
{
return ExchangeListOptionDataSource.Default;
}
/// <summary>
/// 标的相关性
/// </summary>
public static IDataSource<CorrelationTable> GetCorrelationDataSource()
{
return CorrelationDataSource.Default;
}
/// <summary>
/// 个股黑白名单
/// </summary>
public static IDataSource<StockBlackWhite> GetStockBlackWhiteDataSource()
{
return StockBlackWhiteDataSource.Default;
}
/// <summary>
/// 股票手续费设定
/// </summary>
public static IDataSource<StockCommissionConfig> GetStockCommissionDataSource()
{
return StockCommissionDataSource.Default;
}
/// <summary>
/// 对冲账户
/// </summary>
public static IDataSource<ExchangeAccount> GetExchangeAccountDataSource()
{
return ExchangeAccountDataSource.Default;
}
/// <summary>
/// 簿记账户
/// </summary>
public static IDataSource<AssetUnit> GetAssetUnitDataSource()
{
return AssetUnitDataSource.Default;
}
/// <summary>
/// 簿记账户组
/// </summary>
public static IDataSource<AssetUnitGroup> GetAssetUnitGroupDataSource()
{
return AssetUnitDataGroupSource.Default;
}
/// <summary>
/// 客户黑名单
/// </summary>
public static IDataSource<client_black> GetClientBlackDataSource()
{
return ClientBlackDataSource.Default;
}
/// <summary>
/// 客户信息
/// </summary>
public static IDataSource<Client> GetClientDataSource()
{
return ClientDataSource.Default;
}
/// <summary>
/// 客户等级信息
/// </summary>
public static IDataSource<ClientLevel> GetClientLevelDataSource()
{
return ClientLevelDataSource.Default;
}
/// <summary>
/// 市场信息
/// </summary>
public static IDataSource<Market> GetMarketDataSource()
{
return MarketDataSource.Default;
}
#endregion
#region--------
public static List<string> GetSinkTables()
{
var tableNames = new List<string>(30);
_yLDBDataUpdater.GetSinkTables(tableNames);
_clientDBDataUpdater.GetSinkTables(tableNames);
return tableNames;
}
public static string GetSinkData(string tableName)
{
if (string.IsNullOrEmpty(tableName))
{
return "tableName参数不能为空";
}
var str = _yLDBDataUpdater.GetSinkData(tableName);
if (str.StartsWith("未找到数据"))
{
str = _clientDBDataUpdater.GetSinkData(tableName);
}
return str;
}
#endregion
#region----DataUpdater----
abstract class DataCacheUpdaterBase
{
//初始值设置为-1支持datatrace表数据空的情况
public long MaxId { get; private set; } = -1;
protected List<IDataTraceSink> SinkList { get; set; }
/// <summary>
/// 更新跟踪数据
/// </summary>
protected void InnerUpdate(DataTraceDbContext dbContext, string dbName)
{
var maxid = MaxId;
try
{
if (MaxId < 0)
{
MaxId = dbContext.DataTrace.Max(n => (long?)n.id) ?? 0;
}
else
{
var query = from a in dbContext.DataTrace
where a.id > MaxId
orderby a.id
select new DataTraceInfo
{
id = a.id,
TableName = a.TableName,
DataKeyId = a.DataKeyId
};
var traceDatas = query.ToArray();
if (traceDatas.Length > 0)
{
MaxId = traceDatas[traceDatas.Length - 1].id;
var set = new HashSet<string>(traceDatas.Length, StringComparer.OrdinalIgnoreCase);
foreach (var data in traceDatas)
{
var key = string.Concat(data.TableName, "[|]", data.DataKeyId);
if (set.Add(key))
{
try
{
lock (SinkList)
{
foreach (var sink in SinkList)
{
sink.OnDataTrace(data);
}
}
}
catch (Exception ex)
{
_logger.Error(ex, "UpdateTraceData");
}
}
}
}
}
if (MaxId == maxid)
{
return;
}
if (DebugOut)
{
_logger.Debug($"[{dbName}.Datatrace MaxId]{MaxId}");
}
}
catch (Exception ex)
{
_logger.Error(ex, dbName + ".Datatrace更新出错");
}
try
{
lock (SinkList)
{
foreach (var sink in SinkList)
{
sink.UpdateCache();
}
}
}
catch (Exception ex)
{
MaxId = -1;
_logger.Error(ex, dbName + ".Datatrace缓存更新出错");
}
}
/// <summary>
/// 注册数据跟踪监听器
/// </summary>
public void RegisterDataTraceSink(IDataTraceSink sink)
{
if (sink == null)
{
throw new ArgumentNullException(nameof(sink));
}
lock (SinkList)
{
if (!SinkList.Contains(sink))
{
SinkList.Add(sink);
}
}
}
/// <summary>
/// 取消数据跟踪监听器注册
/// </summary>
public void UnRegisterDataTraceSink(IDataTraceSink sink)
{
if (sink != null)
{
lock (SinkList)
{
SinkList.Remove(sink);
}
}
}
/// <summary>
/// 重置数据缓存
/// </summary>
public void ResetDataCache()
{
MaxId = -1;
foreach (var sink in SinkList)
{
sink.Reset();
}
}
public void GetSinkTables(List<string> resultList)
{
foreach (var sink in SinkList)
{
if (sink is DataTraceSink ds)
{
resultList.Add(ds.TableName);
}
else if (sink is DataTraceSinkGroup dsg)
{
resultList.AddRange(dsg.AsEnumerable().Select(n => n.TableName));
}
else
{
resultList.Add("unknown table sink");
}
}
}
public string GetSinkData(string tableName)
{
if (string.IsNullOrEmpty(tableName))
{
return "tableName参数不能为空";
}
foreach (var sink in SinkList)
{
if (sink is DataTraceSink ds)
{
if (ds.TableName == tableName)
{
return (ds.Updater as IJsonSerializable)?.ToJson() ?? "未实现JSON序列化接口:" + tableName;
}
}
else if (sink is DataTraceSinkGroup dsg)
{
foreach (var sink2 in dsg)
{
if (sink2.TableName == tableName)
{
return (sink2.Updater as IJsonSerializable)?.ToJson() ?? "未实现JSON序列化接口:" + tableName;
}
}
}
}
return "未找到数据更新容器:" + tableName;
}
}
class YLDBDataCacheUpdater : DataCacheUpdaterBase
{
public YLDBDataCacheUpdater()
{
var basicDataUpdaters = new List<IDataUpdater>() {
(IDataUpdater)GetMarketDataSource(),
(IDataUpdater)GetVarietyDataSource(),
(IDataUpdater)GetCorrelationDataSource(),
(IDataUpdater)GetUnderlyingDataSource(),
(IDataUpdater)GetExchangeListOptionDataSource(),
(IDataUpdater)GetStockBlackWhiteDataSource(),
(IDataUpdater)GetClientBlackDataSource(),
(IDataUpdater)GetStockCommissionDataSource(),
(IDataUpdater)GetExchangeAccountDataSource(),
(IDataUpdater)GetAssetUnitDataSource(),
(IDataUpdater)GetAssetUnitGroupDataSource()
};
if (AppManager.SubSystem != Enums.SubSystemName.OtcWeb)
{
basicDataUpdaters.Insert(0, AppConfigUpdater.Default);
}
//基础数据6秒刷新
var baseDataTrace = new DataTraceSinkGroup(6, basicDataUpdaters.ToArray());
SinkList = new List<IDataTraceSink>() { baseDataTrace };
//设置基础数据更新时间
var handler = new EventHandler((object sender, EventArgs e) =>
{
Events.EventBus.Publish<Events.DataCacheUpdateEvent>();
});
foreach (var item in basicDataUpdaters)
{
if (item is IDataSourceEvent dataSourceEvent)
{
dataSourceEvent.DataSourceUpdated += handler;
}
}
}
public void Update()
{
using (var dbtrace = DbContextFactory.GetYLDbContext().GetDataTraceDbContext())
{
InnerUpdate(dbtrace, "ylcmsDB");
}
}
}
class ClientDBDataCacheUpdater : DataCacheUpdaterBase
{
public ClientDBDataCacheUpdater()
{
var basicDataUpdaters = new List<IDataUpdater>() {
(IDataUpdater)GetClientDataSource(),
(IDataUpdater)GetClientLevelDataSource(),
(IDataUpdater)GetClientBlackDataSource()
};
//基础数据6秒刷新
var baseDataTrace = new DataTraceSinkGroup(6, basicDataUpdaters.ToArray());
SinkList = new List<IDataTraceSink>() { baseDataTrace };
//设置基础数据更新时间
var handler = new EventHandler((object sender, EventArgs e) =>
{
Events.EventBus.Publish<Events.DataCacheUpdateEvent>();
});
foreach (var item in basicDataUpdaters)
{
if (item is IDataSourceEvent dataSourceEvent)
{
dataSourceEvent.DataSourceUpdated += handler;
}
}
}
public void Update()
{
using (var dbtrace = DbContextFactory.GetClientDbContext(null).GetDataTraceDbContext())
{
InnerUpdate(dbtrace, "clientDB");
}
}
}
#endregion
}
/// <summary>
/// 数据跟踪监听接口
/// </summary>
public interface IDataTraceSink
{
void Reset();
void UpdateCache();
void OnDataTrace(DataTraceInfo dataTrace);
}
/// <summary>
/// 数据跟踪监听类库
/// </summary>
public class DataTraceSink : IDataTraceSink
{
readonly List<string> _list;
readonly ThrottleAction _throttle;
/// <summary>
/// 构造函数--不使用throttle更新方式
/// </summary>
public DataTraceSink(IDataUpdater dataUpdater)
{
_list = new List<string> { "0" };
Updater = dataUpdater ?? throw new ArgumentNullException(nameof(dataUpdater));
}
/// <summary>
/// 构造函数--使用throttle更新方式
/// </summary>
public DataTraceSink(int throttleUpdateWaitSeconds, IDataUpdater dataUpdater) : this(dataUpdater)
{
_throttle = new ThrottleAction(DoUpdateCache, throttleUpdateWaitSeconds < 1 ? 2 : throttleUpdateWaitSeconds, 60);
}
public void Reset()
{
if (Updater is IDataSource dataSource)
{
dataSource.ResetDataSource();
}
_list.Add("0");
}
public void OnDataTrace(DataTraceInfo dataTrace)
{
if (dataTrace != null && Updater.TableName.Equals(dataTrace.TableName, StringComparison.OrdinalIgnoreCase))
{
lock (_list)
{
_list.Add(dataTrace.DataKeyId);
}
}
}
/// <summary>
/// 更新数据
/// </summary>
public void UpdateCache()
{
if (_throttle != null)
{
_throttle.Execute();
}
else
{
DoUpdateCache();
}
}
private void DoUpdateCache()
{
string[] idArr = null;
try
{
lock (_list)
{
if (_list.Count < 1)
{
return;
}
idArr = _list.ToArray();
_list.Clear();
}
AppManager.SetSysInfo("[DataTrace]" + Updater.TableName, "执行,top10-Id:" + string.Join(",", idArr.Take(10)));
Updater.UpdateData(idArr);
}
catch (Exception ex)
{
if (idArr != null && idArr.Length > 0)
{
lock (_list)
{
_list.AddRange(idArr);
}
}
LogFactory.GetLogger("DataTrace_" + Updater.TableName).Error(ex, "更新缓存失败");
}
}
public IDataUpdater Updater { get; }
/// <summary>
///
/// </summary>
public string TableName => Updater.TableName;
}
/// <summary>
///
/// </summary>
public class DataTraceSinkGroup : IDataTraceSink, IEnumerable<DataTraceSink>
{
readonly ThrottleAction _throttle;
readonly IEnumerable<DataTraceSink> _sinks;
/// <summary>
///
/// </summary>
/// <param name="updaters">更新器集合</param>
/// <param name="throttleUpdateWaitSeconds">每次更新等待的时间(秒)</param>
public DataTraceSinkGroup(int throttleUpdateWaitSeconds, params IDataUpdater[] updaters)
{
if (updaters == null)
{
throw new ArgumentNullException(nameof(updaters));
}
_throttle = new ThrottleAction(ThrottleUpdate, throttleUpdateWaitSeconds < 1 ? 2 : throttleUpdateWaitSeconds, 60);
_sinks = updaters.Where(n => !string.IsNullOrEmpty(n?.TableName)).Select(n => new DataTraceSink(n)).ToArray();
}
public void Reset()
{
foreach (var sink in _sinks)
{
sink.Reset();
}
}
/// <summary>
/// Throttle机制的更新行为调用方法
/// </summary>
private void ThrottleUpdate()
{
foreach (var sink in _sinks)
{
sink.UpdateCache();
}
}
/// <summary>
/// 更新缓存数据(采用了Throttle机制)
/// </summary>
public void UpdateCache()
{
_throttle.Execute();
}
/// <summary>
///
/// </summary>
public void OnDataTrace(DataTraceInfo dataTrace)
{
foreach (var sink in _sinks)
{
sink.OnDataTrace(dataTrace);
}
}
public IEnumerator<DataTraceSink> GetEnumerator()
{
return _sinks.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _sinks.GetEnumerator();
}
}
}
@@ -0,0 +1,77 @@
using System.Collections;
using System.Linq.Expressions;
namespace YLErp.Modules.DataCacheModule
{
class DataCacheQueryable<T> : IOrderedQueryable<T>
{
private IQueryable _query;
public DataCacheQueryable(IQueryable query)
{
_query = query ?? throw new ArgumentNullException(nameof(query));
}
public Expression Expression => _query.Expression;
public Type ElementType => _query.ElementType;
public IQueryProvider Provider => new DataCacheQueryProvider<T>(_query.Provider);
public IEnumerator<T> GetEnumerator()
{
var em = Provider.Execute<IEnumerable<T>>(Expression);
var t = typeof(T);
if (t.IsClass && typeof(IClonable<T>).IsAssignableFrom(t))
{
em = em.Select(n => ((IClonable<T>)n).Clone());
}
return em.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class DataCacheQueryProvider<T> : IQueryProvider
{
readonly IQueryProvider _baseProvider;
public DataCacheQueryProvider(IQueryProvider baseProvider)
{
_baseProvider = baseProvider ?? throw new ArgumentNullException(nameof(baseProvider));
}
public IQueryable CreateQuery(Expression expression)
{
return new DataCacheQueryable<T>(_baseProvider.CreateQuery(expression));
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
var query = _baseProvider.CreateQuery<TElement>(expression);
var t = typeof(TElement);
if (t.IsClass && typeof(IClonable<TElement>).IsAssignableFrom(t))
{
return new DataCacheQueryable<TElement>(query);
}
return query;
}
public object Execute(Expression expression)
{
return Execute<T>(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _baseProvider.Execute<TResult>(expression);
}
}
}
@@ -0,0 +1,38 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 簿记账户
/// </summary>
class AssetUnitDataSource : GenericeCachedDataSource<AssetUnit>
{
public override string TableName => nameof(AssetUnit);
//public override void UpdateData(IEnumerable<string> updateKeyIds)
//{
// System.Diagnostics.Debug.WriteLine("DEBUG");
// base.UpdateData(updateKeyIds);
//}
public static readonly AssetUnitDataSource Default;
static AssetUnitDataSource()
{
Default = new AssetUnitDataSource();
}
}
class AssetUnitDataGroupSource : GenericeCachedDataSource<AssetUnitGroup>
{
public override string TableName => nameof(AssetUnitGroup);
public static readonly AssetUnitDataGroupSource Default;
static AssetUnitDataGroupSource()
{
Default = new AssetUnitDataGroupSource();
}
}
}
}
@@ -0,0 +1,30 @@
using YLErp.Model;
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
class ClientBlackDataSource : GenericeCachedDataSource<client_black>
{
public ClientBlackDataSource()
{
}
public override string TableName => nameof(client_black);
protected override DbContext CreateDbContext()
{
return DbContextFactory.GetClientDbContext(null);
}
//---------------------------------------------
public static readonly ClientBlackDataSource Default;
static ClientBlackDataSource()
{
Default = new ClientBlackDataSource();
}
}
}
}
@@ -0,0 +1,25 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 客户信息
/// </summary>
class ClientDataSource : GenericeCachedDataSource<Client>
{
public override string TableName => nameof(Client);
protected override DbContext CreateDbContext()
{
return DbContextFactory.GetClientDbContext(null);
}
public static readonly ClientDataSource Default;
static ClientDataSource()
{
Default = new ClientDataSource();
}
}
}
}
@@ -0,0 +1,25 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 客户等级信息
/// </summary>
class ClientLevelDataSource : GenericeCachedDataSource<ClientLevel>
{
public override string TableName => nameof(ClientLevel);
protected override DbContext CreateDbContext()
{
return DbContextFactory.GetClientDbContext(null);
}
public static readonly ClientLevelDataSource Default;
static ClientLevelDataSource()
{
Default = new ClientLevelDataSource();
}
}
}
}
@@ -0,0 +1,27 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 标的关联性
/// </summary>
class CorrelationDataSource : GenericeCachedDataSource<CorrelationTable>
{
public CorrelationDataSource()
{
Filter = a => a.State == valuedate.使;
}
public override string TableName => nameof(CorrelationTable);
//---------------------------------------------
public static readonly CorrelationDataSource Default;
static CorrelationDataSource()
{
Default = new CorrelationDataSource();
}
}
}
}
@@ -0,0 +1,20 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 对冲账户
/// </summary>
class ExchangeAccountDataSource : GenericeCachedDataSource<ExchangeAccount>
{
public override string TableName => nameof(ExchangeAccount);
public static readonly ExchangeAccountDataSource Default;
static ExchangeAccountDataSource()
{
Default = new ExchangeAccountDataSource();
}
}
}
}
@@ -0,0 +1,83 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 场内期权基础数据
/// </summary>
class ExchangeListOptionDataSource : GenericeCachedDataSource<ExchangeListOption>, Abstract.IDataSourceKey2<ExchangeListOption>
{
public override string TableName => nameof(ExchangeListOption);
readonly Dictionary<string, ExchangeListOption> _dicEx;
public ExchangeListOptionDataSource()
{
_dicEx = new Dictionary<string, ExchangeListOption>(StringComparer.OrdinalIgnoreCase);
AfterUpdate = data =>
{
if (!string.IsNullOrWhiteSpace(data?.ContractCode))
{
_dicEx[data.ContractCode] = data;
}
};
AfterRemove = data =>
{
if (!string.IsNullOrWhiteSpace(data?.ContractCode))
{
_dicEx.Remove(data.ContractCode);
}
};
//使用近1个月的数据作为缓存
var date = DateTime.Today.AddDays(-30);
Filter = PredicateBuilder.Create<ExchangeListOption>(n => n.MaturityDate > date);
}
public ExchangeListOption GetData(string keyId)
{
if (string.IsNullOrWhiteSpace(keyId))
{
return null;
}
lock (this)
{
if (_dicEx.TryGetValue(keyId, out ExchangeListOption data))
{
return data;
}
using (var db = DbContextFactory.GetYLDbContext())
{
var exopt = db.exchange_list_option.AsNoTracking().FirstOrDefault(n => n.ContractCode == keyId);
if (exopt != null)
{
_dic[exopt.id] = exopt;
_dicEx[exopt.ContractCode] = exopt;
}
return exopt?.Clone();
}
}
}
public override void ResetDataSource()
{
base.ResetDataSource();
_dicEx.Clear();
}
//---------------------------------------------
public static readonly ExchangeListOptionDataSource Default;
static ExchangeListOptionDataSource()
{
Default = new ExchangeListOptionDataSource();
}
}
}
}
@@ -0,0 +1,211 @@
using System.Linq.Expressions;
using YLErp.Abstract;
namespace YLErp.Modules.DataCacheModule
{
/// <summary>
/// 通用数据源抽象类
/// </summary>
abstract class GenericeCachedDataSource<TData> : IDataUpdater, IDataSource, IDataSource<TData>, IDataSourceEvent, IJsonSerializable
where TData : class, IDataTraceV2, IDataEntity
{
protected int _maxId = -1;
protected bool _clonable;
protected readonly Dictionary<int, TData> _dic;
protected DateTime _lastUptime;
public event EventHandler DataSourceUpdated;
protected GenericeCachedDataSource()
{
_dic = new Dictionary<int, TData>();
_clonable = typeof(TData).GetInterface(typeof(IClonable<TData>).Name) != null;
}
/// <summary>
/// 数据条数
/// </summary>
public int Count => _dic.Count;
public abstract string TableName { get; }
/// <summary>
/// 获取数据
/// </summary>
public IQueryable<TData> AsQueryable(Expression<Func<TData, bool>> predicate = null)
{
lock (this)
{
if (_dic.Count < 1)
{
return Enumerable.Empty<TData>().AsQueryable();
}
var query = _dic.Values.AsQueryable();
if (predicate != null)
{
query = query.Where(predicate);
}
if (_clonable)
{
return new DataCacheQueryable<TData>(query);
}
return query;
}
}
/// <summary>
/// 更新数据源
/// </summary>
public virtual void UpdateData(IEnumerable<string> updateKeyIds)
{
lock (_dic)
{
InnerUpdateData(updateKeyIds);
}
}
private void InnerUpdateData(IEnumerable<string> updateKeyIds)
{
using (var db = CreateDbContext())
{
var table = db.Set<TData>().AsNoTracking();
var minId = _maxId;
var maxId = table.Max(n => (int?)n.id) ?? 0;
//ID更新集合
var updateIds = DataConvert.ConvertToInt32Set(updateKeyIds);
if (maxId < minId)
{
minId = -1;
}
if (maxId == minId && !updateIds.Any(n => n > 0))
{
return;
}
_lastUptime = DateTime.Now;
//数据查询
var query = table.Where(n => (n.id > minId && n.id <= maxId) || updateIds.Contains(n.id));
if (Filter != null)
{
query = query.Where(Filter);
}
var datas = query.OrderBy(n => n.id).ToArray();
datas = PreProcessDatas(datas);
lock (this)
{
//更新字典数据
foreach (var data in datas)
{
updateIds.Remove(data.id);
_dic[data.id] = data;
AfterUpdate?.Invoke(data);
}
//取不到数据的直接从本地字典中删除
updateIds.Remove(0);
foreach (var id in updateIds)
{
if (AfterRemove != null && _dic.TryGetValue(id, out var data))
{
AfterRemove(data);
}
_dic.Remove(id);
}
}
_maxId = maxId;
DataSourceUpdated?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// 根据KeyID获取数据
/// </summary>
public virtual TData GetData(int keyId)
{
if (keyId < 1)
{
return null;
}
lock (this)
{
if (_dic.TryGetValue(keyId, out TData data))
{
return _clonable ? ((IClonable<TData>)data).Clone() : data;
}
using (var db = CreateDbContext())
{
var dbModel = db.Set<TData>().AsNoTracking().FirstOrDefault(n => n.id == keyId);
if (dbModel != null)
{
_dic[dbModel.id] = dbModel;
AfterUpdate?.Invoke(dbModel);
return _clonable ? ((IClonable<TData>)dbModel).Clone() : dbModel;
}
return dbModel;
}
}
}
/// <summary>
/// 重置数据源
/// </summary>
public virtual void ResetDataSource()
{
_maxId = -1;
_dic.Clear();
}
//--------用于扩展配置------------------
/// <summary>
/// 数据加载后预处理
/// </summary>
protected virtual TData[] PreProcessDatas(TData[] datas)
{
return datas;
}
/// <summary>
/// 查询过滤条件
/// </summary>
public Expression<Func<TData, bool>> Filter { get; set; }
/// <summary>
/// 更新以后事件处理
/// </summary>
public Action<TData> AfterUpdate { get; set; }
/// <summary>
/// 删除以后事件处理
/// </summary>
public Action<TData> AfterRemove { get; set; }
/// <summary>
/// 创建数据库上下文(默认使用YLContext)
/// </summary>
protected virtual DbContext CreateDbContext()
{
return DbContextFactory.GetYLDbContext();
}
public string ToJson()
{
return new { _maxId, _lastUptime, _dic }.ToJson();
}
}
}
@@ -0,0 +1,14 @@
namespace YLErp.Modules.DataCacheModule
{
class MarketDataSource : GenericeCachedDataSource<Market>
{
public override string TableName => nameof(Market);
public static readonly MarketDataSource Default;
static MarketDataSource()
{
Default = new MarketDataSource();
}
}
}
@@ -0,0 +1,26 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 个股黑白名单
/// </summary>
class StockBlackWhiteDataSource : GenericeCachedDataSource<StockBlackWhite>
{
public StockBlackWhiteDataSource()
{
}
public override string TableName => nameof(StockBlackWhite);
//---------------------------------------------
public static readonly StockBlackWhiteDataSource Default;
static StockBlackWhiteDataSource()
{
Default = new StockBlackWhiteDataSource();
}
}
}
}
@@ -0,0 +1,25 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 股票手续费设定
/// </summary>
class StockCommissionDataSource : GenericeCachedDataSource<StockCommissionConfig>
{
public StockCommissionDataSource()
{
Filter = n => n.Enabled == 1;
}
public override string TableName => nameof(StockCommissionConfig);
public static readonly StockCommissionDataSource Default;
static StockCommissionDataSource()
{
Default = new StockCommissionDataSource();
}
}
}
}
@@ -0,0 +1,471 @@
using System.Collections.Concurrent;
using YieldChain.Commons;
using YieldChain.Helpers;
using YLErp.Abstract.DataProviders;
using YLErp.BLL;
using YLErp.Models;
using YLErp.Modules.UnderlyingModule;
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
/// <summary>
/// 标的数据源
/// </summary>
class UnderlyingDataSource : GenericeCachedDataSource<underlying_manager>, IUnderlyingDataSource, IBasketPriceProvider
{
readonly ThrottleAction _updatePricethrottle;
readonly Dictionary<string, underlying_manager> _dicEx;
readonly ConcurrentDictionary<string, SyntheticUnderlying> _dicSynthetic;
private UnderlyingDataSource()
{
_updatePricethrottle = new ThrottleAction(ThrottleUpdatePricesAction, 3, 180);
_dicEx = new Dictionary<string, underlying_manager>(StringComparer.OrdinalIgnoreCase);
_dicSynthetic = new ConcurrentDictionary<string, SyntheticUnderlying>(StringComparer.OrdinalIgnoreCase);
AfterUpdate = AfterUpdateHandle;
AfterRemove = AfterRemoveHandle;
//使用近2年的标的数据作为缓存
int.TryParse(PS.Config.ErpElement.UnderlyingCacheCfg, out int years);
if (years < 0) years = -years;
var date = DateTime.Today.AddYears(years < 2 ? -2 : -years);
Filter = PredicateBuilder.Create<underlying_manager>(
n => (n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.CommodityFutures
&& n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.StockIF) || n.MaturityDate >= date);
}
public override string TableName => nameof(underlying_manager);
/// <summary>
/// 扩展数据更新
/// </summary>
private void AfterUpdateHandle(underlying_manager data)
{
if (string.IsNullOrWhiteSpace(data?.UnderlyingCode))
{
return;
}
_dicEx[data.UnderlyingCode] = data;
_dicSynthetic.TryRemove(data.UnderlyingCode, out _);
var variety = DataCacheProvider.GetVarietyDataSource().GetData(data.UnderlyingTypeId);
if (data.IsStock())
{
data.CountRatio = 1;
data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 100;
data.QuoteUnit = data.TradeUnit = "股";
data.PinYinFirst = PingYinHelper.GetFirstPinYin(data.UnderlyingName)?.ToUpperInvariant();
data.CommodityCode = variety?.VarietyCode;
}
else if (data.IsBasket())
{
data.CountRatio = 1;
data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 100;
data.QuoteUnit = data.TradeUnit = "股";
}
else if (data.IsSynthetic())
{
data.CountRatio = 1;
data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 1;
}
else
{
if (variety != null)
{
if (data.ContractSize < 1)
{
data.ContractSize = DBModels.Helpers.VarietyHelper.GetTradeUnitValue(variety.VarietyCode, variety.TradeUnit) ?? 1;
}
if (string.IsNullOrEmpty(data.QuoteUnit))
{
data.QuoteUnit = DBModels.Helpers.VarietyHelper.GetQuoteUnitSingleOriginal(variety.QuoteUnit);
}
if (string.IsNullOrEmpty(data.TradeUnit))
{
data.TradeUnit = DBModels.Helpers.VarietyHelper.GetTradeUnitSingle(variety.TradeUnit);
}
data.CountRatio = DBModels.Helpers.VarietyHelper.GetCountRatio(variety.QuoteUnit);
data.CommodityCode = variety?.VarietyCode;
}
if (data.ContractSize < 1)
{
data.ContractSize = 1;
}
if (data.PriceTick < 1e-4)
{
data.PriceTick = DBModels.Helpers.VarietyHelper.ParseMinPriceChange(variety) ?? 0;
}
}
if (data.PriceTick < 1e-4)
{
data.PriceTick = 0.01;
}
}
/// <summary>
/// 扩展数据移除操作
/// </summary>
private void AfterRemoveHandle(underlying_manager data)
{
if (string.IsNullOrWhiteSpace(data?.UnderlyingCode))
{
return;
}
_dicEx.Remove(data.UnderlyingCode);
_dicSynthetic.TryRemove(data.UnderlyingCode, out _);
}
/// <summary>
/// 根据标的代码获取标的数据
/// </summary>
public underlying_manager GetData(string underlyingCode)
{
if (string.IsNullOrWhiteSpace(underlyingCode))
{
return null;
}
lock (this)
{
if (_dicEx.TryGetValue(underlyingCode, out var underlying))
{
var clone = underlying.Clone();
clone.Price = InnerGetPrice(clone);
return clone;
}
using (var db = DbContextFactory.GetYLDbContext())
{
var un = db.underlying_manager.AsNoTracking().FirstOrDefault(n => n.UnderlyingCode == underlyingCode);
if (un != null)
{
_dic[un.id] = un;
AfterUpdateHandle(un);
if (un.IsBasket() && InnerTryGetPrice(un, out var price))
{
un.Price = price;
}
}
return un?.Clone();
}
}
}
/// <summary>
/// 根据标的ID获取标的数据
/// </summary>
public override underlying_manager GetData(int keyId)
{
var un = base.GetData(keyId);
if (un != null)
{
un.Price = InnerGetPrice(un);
}
return un;
}
#region----IPriceProvider----
/// <summary>
/// 根据标的代码获取标的价格(包含组合标的)
/// </summary>
public double GetPrice(int underlyingId)
{
return InnerGetPrice(GetData(underlyingId));
}
/// <summary>
/// 根据标的代码获取标的价格(标的代码不区分大小写)(包含组合标的)
/// </summary>
public double GetPrice(string underlyingCode)
{
return InnerGetPrice(GetData(underlyingCode));
}
/// <summary>
/// 根据标的代码获取标的价格(包含组合标的)
/// </summary>
public bool TryGetPrice(int underlyingId, out double price)
{
return InnerTryGetPrice(GetData(underlyingId), out price);
}
/// <summary>
/// 根据标的代码获取标的价格,标的代码不区分大小写(包含组合标的)
/// </summary>
public bool TryGetPrice(string underlyingCode, out double price)
{
return InnerTryGetPrice(GetData(underlyingCode), out price);
}
public bool InitData(List<string> underlyingCodes)
{
lock (this)
{
var notInCaches = underlyingCodes.Where(p => !_dicEx.Keys.Contains(p)).ToList();
if (notInCaches != null && notInCaches.Count > 0)
{
List<underlying_manager> unList = null;
using (var db = DbContextFactory.GetYLDbContext())
{
unList = db.underlying_manager.AsNoTracking().Where(n => notInCaches.Contains(n.UnderlyingCode)).ToList();
}
if (unList != null && unList.Count > 0)
{
unList.ForEach(un =>
{
_dic[un.id] = un;
AfterUpdateHandle(un);
if (un.IsBasket() && InnerTryGetPrice(un, out var price))
{
un.Price = price;
}
});
}
}
}
return true;
}
private double InnerGetPrice(underlying_manager underlying)
{
return InnerTryGetPrice(underlying, out var price) ? price : 0;
}
private bool InnerTryGetPrice(underlying_manager un, out double price)
{
price = 0;
if (un == null)
{
return false;
}
if (un.IsBasket())
{
price = BasketUnderlyingHelper.GetBasketPrice(un, this, true).Price;
}
else
{
price = un.Price ?? 0;
}
return true;
}
/// <summary>
/// 更新标的价格
/// </summary>
public bool UpdatePrices(bool delay)
{
if (_updatePricethrottle.Execute())
{
if (delay)
{
_updatePricethrottle.DelayExecute(60);
}
return true;
}
return false;
}
private void ThrottleUpdatePricesAction()
{
using (var db = DbContextFactory.GetYLDbContext())
{
var query1 = from u in db.underlying_manager
where u.UnderlyingState != "Matured"
&& u.UnderlyingType != "组合标的"
select new PriceModel
{
InstrumentCode = u.UnderlyingCode,
Price = u.Price ?? 0,
PreClose = u.PrevClosePrice,
PriceTime = u.LastUpdateTime
};
var query2 = from su in db.synthetic_underlying
join u in db.underlying_manager on su.Name equals u.UnderlyingCode
join u1 in db.underlying_manager on su.UnderlyingCode1 equals u1.UnderlyingCode into u1t
from u1 in u1t.DefaultIfEmpty()
join u2 in db.underlying_manager on su.UnderlyingCode2 equals u2.UnderlyingCode into u2t
from u2 in u2t.DefaultIfEmpty()
join u3 in db.underlying_manager on su.UnderlyingCode3 equals u3.UnderlyingCode into u3t
from u3 in u3t.DefaultIfEmpty()
join u4 in db.underlying_manager on su.UnderlyingCode4 equals u4.UnderlyingCode into u4t
from u4 in u4t.DefaultIfEmpty()
where u.UnderlyingState == "Live" && u.LaunchState == "1"
select new PriceModel
{
InstrumentCode = u.UnderlyingCode,
Price = (u1.Price ?? 0) * (su.Coefficient1 ?? 0)
+ (u2.Price ?? 0) * (su.Coefficient2 ?? 0)
+ (u3.Price ?? 0) * (su.Coefficient3 ?? 0)
+ (u4.Price ?? 0) * (su.Coefficient4 ?? 0)
+ (su.Constant ?? 0),
PreClose = (u1.PrevClosePrice ?? 0) * (su.Coefficient1 ?? 0)
+ (u2.PrevClosePrice ?? 0) * (su.Coefficient2 ?? 0)
+ (u3.PrevClosePrice ?? 0) * (su.Coefficient3 ?? 0)
+ (u4.PrevClosePrice ?? 0) * (su.Coefficient4 ?? 0)
+ (su.Constant ?? 0),
PriceTime = u1.LastUpdateTime ?? u2.LastUpdateTime ?? u3.LastUpdateTime ?? u4.LastUpdateTime
};
var datas = query1.Concat(query2).ToArray();
foreach (var item in datas)
{
if (_dicEx.TryGetValue(item.InstrumentCode, out var underlying))
{
underlying.Price = item.Price;
underlying.PrevClosePrice = item.PreClose;
underlying.LastUpdateTime = item.PriceTime;
}
}
}
}
/// <summary>
/// 获取篮子的子标的价格
/// </summary>
public bool TryGetSubPrice(string underlyingCode, out double price, out double settlePrice)
{
//篮子标的的子标的只能是普通标的
var data = GetData(underlyingCode);
if (data != null)
{
price = settlePrice = data.Price ?? 0;
return true;
}
price = settlePrice = 0;
return false;
}
#endregion
/// <summary>
/// 获取组合标的信息
/// </summary>
public SyntheticUnderlying GetSyntheticUnderlying(string underlyingCode)
{
if (string.IsNullOrEmpty(underlyingCode))
{
return null;
}
if (_dicSynthetic.TryGetValue(underlyingCode, out var sy))
{
return sy;
}
var un = GetData(underlyingCode);
if (un?.CommodityCode != "组合标的")
{
return null;
}
using (var db = DbContextFactory.GetYLDbContext())
{
sy = db.synthetic_underlying.AsNoTracking().FirstOrDefault(n => n.Name == underlyingCode);
if (sy != null)
{
_dicSynthetic[sy.Name] = sy;
}
return sy?.Clone();
}
}
/// <summary>
/// 获取数量转份额的乘积因子(最小为1)
/// </summary>
public int GetCountRatio(string underlyingCode)
{
return GetData(underlyingCode)?.CountRatio ?? 1;
}
public override void ResetDataSource()
{
base.ResetDataSource();
_dicEx.Clear();
_dicSynthetic.Clear();
UpdateData(null);
}
//排序规则:优先使用未过期的期货,然后股票/现货,然后没有过期日的期货,然后过期的期货
protected override underlying_manager[] PreProcessDatas(underlying_manager[] datas)
{
var valueDate = valuedateBLL.ValueDate;
return datas.OrderBy(n =>
{
var prefix = "1";
if (n.IsFutures())
{
if (n.MaturityDate.HasValue)
{
prefix = (n.MaturityDate.Value > valueDate ? 0 : valueDate.Year - n.MaturityDate.Value.Year + 2).ToString();
}
else
{
prefix = "2";
}
}
return prefix + (n.IsCombined() ? "1" : "0") + n.UnderlyingCode;
}).ToArray();
}
/// <summary>
/// 更新标的资产启用状态
/// </summary>
public void UpdateLauchState()
{
using (var db = DbContextFactory.GetYLDbContext())
{
var datas = db.underlying_manager.Where(Filter).Select(n => new { n.id, n.LaunchState }).ToArray();
lock (_dic)
{
foreach (var item in datas)
{
if (_dic.TryGetValue(item.id, out var um))
{
um.LaunchState = item.LaunchState;
}
}
}
}
}
//---------------------------------------------
public static readonly UnderlyingDataSource Default;
static UnderlyingDataSource()
{
Default = new UnderlyingDataSource();
}
}
}
}
@@ -0,0 +1,186 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using YieldChain.Commons;
using YieldChain.Helpers;
using YLErp.Abstract;
using YLErp.Abstract.DataProviders;
using YLErp.BLL;
using YLErp.Models;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.UnderlyingModule;
using YLErp.QdpModule;
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
class UnderlyingDbDataSource : IUnderlyingDataSource, IBasketPriceProvider, IDataUpdater, IDataSource, IDataSource<underlying_manager>, IDataSourceEvent, IJsonSerializable
{
private readonly YLContext _context=new YLContext();
/// <summary>
/// 查询过滤条件
/// </summary>
public Expression<Func<underlying_manager, bool>> Filter { get; set; }
public int Count
{
get
{
return _context.underlying_manager.Count();
}
}
public string TableName => "underlying_manager";
public IQueryable<underlying_manager> AsQueryable(Expression<Func<underlying_manager, bool>> predicate = null)
{
if (predicate == null)
{
return _context.underlying_manager;
}
return _context.underlying_manager.Where(predicate);
}
public int GetCountRatio(string underlyingCode)
{
return GetData(underlyingCode)?.CountRatio ?? 1;
}
public underlying_manager GetData(string underlyingCode)
{
return _context.underlying_manager.FirstOrDefault(x => x.UnderlyingCode == underlyingCode);
}
public underlying_manager GetData(int keyId)
{
return _context.underlying_manager.FirstOrDefault(x => x.id == keyId);
}
public double GetPrice(int underlyingId)
{
return InnerGetPrice(GetData(underlyingId));
}
public double GetPrice(string underlyingCode)
{
return InnerGetPrice(GetData(underlyingCode));
}
public SyntheticUnderlying GetSyntheticUnderlying(string underlyingCode)
{
if (string.IsNullOrEmpty(underlyingCode))
{
return null;
}
var un = GetData(underlyingCode);
if (un?.CommodityCode != "组合标的")
{
return null;
}
var sy = _context.synthetic_underlying.AsNoTracking().FirstOrDefault(n => n.Name == underlyingCode);
return sy?.Clone();
}
public bool InitData(List<string> underlyingCodes)
{
return true;
}
public void ResetDataSource()
{
}
public bool TryGetPrice(int underlyingId, out double price)
{
return InnerTryGetPrice(GetData(underlyingId), out price);
}
public bool TryGetPrice(string underlyingCode, out double price)
{
return InnerTryGetPrice(GetData(underlyingCode), out price);
}
public bool TryGetSubPrice(string underlyingCode, out double price, out double settlePrice)
{
//篮子标的的子标的只能是普通标的
var data = GetData(underlyingCode);
if (data != null)
{
price = settlePrice = data.Price ?? 0;
return true;
}
price = settlePrice = 0;
return false;
}
public void UpdateLauchState()
{
}
public bool UpdatePrices(bool delay)
{
return true;
}
private double InnerGetPrice(underlying_manager underlying)
{
return InnerTryGetPrice(underlying, out var price) ? price : 0;
}
private bool InnerTryGetPrice(underlying_manager un, out double price)
{
price = 0;
if (un == null)
{
return false;
}
if (un.IsBasket())
{
price = BasketUnderlyingHelper.GetBasketPrice(un, this, true).Price;
}
else if (un.IsBond())
{
var valueDate = valuedateBLL.ValueDate;
valueDate=QdpCalendarHelper.GetNonHolidayDefore(valueDate.AddDays(-1));
var bondPrice = EodPriceQueryService.GetBondPrice(valueDate,un.UnderlyingCode);
price = bondPrice!=null? bondPrice.ClosePrice:un.Price??0;
}
else
{
price = un.Price ?? 0;
}
return true;
}
public void UpdateData(IEnumerable<string> updateKeyIds)
{
}
public string ToJson()
{
return "";
}
public static readonly UnderlyingDbDataSource Default;
public event EventHandler DataSourceUpdated;
static UnderlyingDbDataSource()
{
Default = new UnderlyingDbDataSource();
}
}
}
}
@@ -0,0 +1,62 @@
namespace YLErp.Modules.DataCacheModule
{
public static partial class DataCacheManager
{
class VarietyDataSource : GenericeCachedDataSource<Variety>, Abstract.IDataSourceKey2<Variety>
{
public override string TableName => nameof(Variety);
readonly Dictionary<string, Variety> _dicEx;
private VarietyDataSource()
{
_dicEx = new Dictionary<string, Variety>(StringComparer.OrdinalIgnoreCase);
}
public Variety GetData(string varietyCode)
{
if (string.IsNullOrWhiteSpace(varietyCode))
{
return null;
}
lock (this)
{
if (_dicEx.TryGetValue(varietyCode, out var variety))
{
var clone = variety.Clone();
return clone;
}
using (var db = DbContextFactory.GetYLDbContext())
{
var va = db.variety.AsNoTracking().FirstOrDefault(n => n.VarietyCode == varietyCode);
if (va != null)
{
_dic[va.id] = va;
AfterUpdateHandle(va);
}
return va?.Clone();
}
}
}
private void AfterUpdateHandle(Variety data)
{
if (string.IsNullOrWhiteSpace(data.VarietyCode))
{
return;
}
_dicEx[data.VarietyCode] = data;
}
public static readonly VarietyDataSource Default;
static VarietyDataSource()
{
Default = new VarietyDataSource();
}
}
}
}
@@ -0,0 +1,14 @@
namespace YLErp.Modules.DataCacheModule
{
/// <summary>
/// 数据跟踪信息
/// </summary>
public class DataTraceInfo
{
public long id { get; set; }
public string TableName { get; set; }
public string DataKeyId { get; set; }
}
}
@@ -0,0 +1,52 @@
using YLErp.Abstract.DataProviders;
namespace YLErp.Modules.DataCacheModule
{
/// <summary>
/// 标的数据源缓存接口
/// </summary>
public interface IUnderlyingDataSource : Abstract.IDataSource<underlying_manager>, IPriceProvider
{
/// <summary>
/// 根据标的代码获取标的信息,标的代码不区分大小写(包含组合标的)
/// </summary>
underlying_manager GetData(string underlyingCode);
/// <summary>
/// 更新价格
/// </summary>
bool UpdatePrices(bool delay);
/// <summary>
/// 获取组合标的
/// </summary>
SyntheticUnderlying GetSyntheticUnderlying(string underlyingCode);
/// <summary>
/// 获取数量转份额的乘积因子(最小为1)
/// </summary>
int GetCountRatio(string underlyingCode);
/// <summary>
/// 更新标的资产启用状态
/// </summary>
void UpdateLauchState();
/// <summary>
/// 获取价格
/// </summary>
double GetPrice(int underlyingId);
/// <summary>
/// 获取价格
/// </summary>
bool TryGetPrice(int underlyingId, out double price);
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="underlyingCodes"></param>
/// <returns></returns>
bool InitData(List<string> underlyingCodes);
}
}