从山证v2.3.0拷贝
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user