Commit 8a5b5d45 authored by Maodashu's avatar Maodashu

Add project files.

parent 66ac5eb4
Pipeline #202 canceled with stages
[*.cs]
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# CS4014: Because this call is not awaited, execution of the current method continues before the call is completed
dotnet_diagnostic.CS4014.severity = none

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Addons
{
public class ApiExplorerGroupConvention : IControllerModelConvention
{
private readonly Dictionary<string, List<string>> GroupDict = new Dictionary<string, List<string>> {
{ "system",new List<string>{
"Logs",
"Console"
} },
{ "debug",new List<string>{
"Debug",
"WSChannel"
} },
{ "other",new List<string>{
} }
};
public void Apply(ControllerModel controller)
{
string groupName = "other";
foreach (var kv in GroupDict)
{
if (GroupDict[kv.Key].Contains(controller.ControllerName))
{
groupName = kv.Key;
break;
}
}
controller.ApiExplorer.GroupName = groupName;
}
}
}

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace OTWebSocket.API.Addons
{
/// <summary>
/// 向Swagger添加枚举值说明
/// </summary>
public class EnumDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.ToLower().StartsWith("matrixone"));
// 向结果模型添加枚举描述
foreach (var schemaDictionaryItem in swaggerDoc.Components.Schemas)
{
var schema = schemaDictionaryItem.Value;
if (schema.Enum.Count > 0)
{
Type enumType = null;
foreach (Assembly assembly in assemblies)
{
enumType = assembly.GetTypes().FirstOrDefault(x => x.IsEnum && x.Name.Equals(schemaDictionaryItem.Key));
if (enumType != null && enumType.FullName != null && enumType.FullName.ToLower().StartsWith("matrixone")) break;
}
if (enumType != null && enumType.GetEnumUnderlyingType().Equals(typeof(int))) schema.Description += DescribeEnum(enumType);
}
}
}
/// <summary>
/// 描述枚举
/// </summary>
/// <param name="enumType"></param>
/// <returns></returns>
private static string DescribeEnum(Type enumType)
{
var enumDescriptions = new List<string>();
foreach (object field in enumType.GetEnumValues())
{
int value = (int)Enum.Parse(enumType, field.ToString());
enumDescriptions.Add($"<br>[{value} = {Enum.GetName(enumType, value)}{GetDescription(enumType, value)}]");
}
return $"{Environment.NewLine}{string.Join(Environment.NewLine, enumDescriptions)}";
}
/// <summary>
/// 获取描述
/// </summary>
/// <param name="t"></param>
/// <param name="value"></param>
/// <returns></returns>
private static string GetDescription(Type t, object value)
{
foreach (MemberInfo mInfo in t.GetMembers())
{
if (mInfo.Name == t.GetEnumName(value))
{
foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
{
if (attr.GetType() == typeof(DescriptionAttribute))
{
return ((DescriptionAttribute)attr).Description;
}
}
}
}
return string.Empty;
}
}
}
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace OTWebSocket.API.Addons
{
public static class MD5Helper
{
public static string Encode(string str)
{
StringBuilder stringBuilder = new StringBuilder("WSID-");
if (!string.IsNullOrEmpty(str))
{
byte[] bytes = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(str));
foreach (byte b in bytes)
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString();
}
}
}

using Microsoft.Extensions.Configuration;
using NLog;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace OTWebSocket.API.Addons
{
[Target("MOLog")]
public sealed class MOLogTarget : TargetWithLayout
{
private readonly Queue<string> msgpool;
private readonly Task writtingTask;
private bool disposing;
private readonly CancellationTokenSource cTokenSource;
private readonly CancellationToken cToken;
private readonly string logPath;
private DateTime last_file_clean_time;
/// <summary>
/// 日志文件间隔,分钟
/// </summary>
private readonly int file_interval_minutes = 10;
/// <summary>
/// 日志文件过期时间,天
/// </summary>
private readonly int clean_expire_days = 7;
/// <summary>
/// 日志文件清理周期,小时
/// </summary>
private readonly int clean_interval_Hours = 12;
public MOLogTarget()
{
last_file_clean_time = DateTime.UtcNow;
Host = "localhost";
logPath = Environment.CurrentDirectory + $"/logs";
msgpool = new Queue<string>(1024 * 16);
cTokenSource = new CancellationTokenSource();
cToken = cTokenSource.Token;
disposing = false;
writtingTask = new Task(WriteFile, cToken);
if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath);
}
[RequiredParameter]
public Layout Host { get; set; }
protected override void Write(LogEventInfo logEvent)
{
string logMessage = RenderLogEvent(Layout, logEvent);
msgpool.Enqueue($"{logMessage}");
//清理clean_expire_days日之前的日志文件
try
{
if (last_file_clean_time.AddHours(clean_interval_Hours) < DateTime.UtcNow)
{
last_file_clean_time = DateTime.UtcNow;
Task.Run(() =>
{
string[] files = Directory.GetFiles(logPath);
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG]Logs cleaning start");
int removed_count = 0;
Array.ForEach(files, s =>
{
FileInfo fileInfo = new FileInfo(s);
if (fileInfo.LastWriteTimeUtc.AddDays(clean_expire_days) < DateTime.UtcNow)
{
try
{
fileInfo.Delete();
removed_count++;
}
catch (Exception e)
{
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG]IO error when remove {fileInfo.Name}:{e.Message}\n{e.StackTrace}");
}
}
});
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG]Logs cleaning finished,{removed_count} files removed.");
});
}
}
catch (Exception e)
{
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG]IO error:{e.Message}\n{e.StackTrace}");
}
}
private void WriteFile()
{
while (!disposing)
{
lock (msgpool)
{
FileStream fs = null;
try
{
DateTime now = DateTime.Now;
int pf = file_interval_minutes * (now.Minute / file_interval_minutes);
string fname = $"/{DateTime.Now:MM-dd-HH}-{pf:G2}.log";
fs = File.Open(logPath + fname, FileMode.Append);
if (fs != null)
{
for (int i = 0; i < 1000; i++)
{
if (msgpool.Count == 0) break;
string str = msgpool.Dequeue() + "\n";
fs.Write(Encoding.UTF8.GetBytes(str));
}
fs.Close();
}
}
catch (Exception e)
{
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG] 日志文件写入异常:{e.Message}\n{e.StackTrace}");
}
}
Thread.Sleep(3 * 1000);
}
}
protected override void InitializeTarget()
{
base.InitializeTarget();
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG] 日志服务启动");
writtingTask.Start();
}
protected override void CloseTarget()
{
disposing = true;
cTokenSource.Cancel();
writtingTask.Wait();
base.CloseTarget();
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG] 日志服务结束");
}
}
}
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
namespace OTWebSocket.API.Controllers
{
/// <summary>
///
/// </summary>
public class BaseController : Controller
{
protected readonly ILogger _logger;
protected readonly static Dictionary<string, long> requestSum = new Dictionary<string, long>();
public override void OnActionExecuting(ActionExecutingContext context)
{
lock (requestSum)
{
string url = context.HttpContext.Request.Path.Value;
string[] urls = url.Split('/');
int i = 0;
foreach (string seg in urls)
{
url += $"{seg}/";
i++;
if (i > 4) break;
}
if (requestSum.ContainsKey(url))
{
requestSum[url]++;
}
else
{
requestSum.Add(url, 1);
}
}
base.OnActionExecuting(context);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger"></param>
public BaseController(ILogger logger)
{
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected string GetLanguage()
{
if (HttpContext.Request.Headers.TryGetValue("Accept-Language", out StringValues language))
{
if (language.ToString().IndexOf("zh-CN") < 0)
language = "en-US";
else
language = "zh-CN";
}
return language;
}
/// <summary>
/// 临时用的控制台密码
/// </summary>
protected readonly Dictionary<string, string> _accountList = new Dictionary<string, string> {
{"634983d81a62d80dc7b4e7b61d371096","nixiaode"}
};
protected string MakeToken(string psw)
{
byte[] bytes = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(psw + HttpContext.Connection.RemoteIpAddress.ToString()));
string token = "";
foreach (byte b in bytes)
{
token += b.ToString("x2");
}
return token;
}
protected bool ValidToken(string token)
{
return token.Equals(MakeToken("nixiaode"));
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.Annotations;
namespace OTWebSocket.API.Controllers
{
/// <summary>
///
/// </summary>
[Produces("application/json")]
[Route("api/[controller]")]
public class ConsoleController : BaseController
{
/// <summary>
///
/// </summary>
public ConsoleController(
IConfiguration cfg,
ILogger<ConsoleController> logger
) : base(logger)
{
}
/// <summary>
/// 用户登录
/// </summary>
/// <returns></returns>
[SwaggerOperation(Tags = new[] { "日志管理接口" })]
[ProducesResponseType(typeof(bool), 200)]
[HttpPost("Login")]
public IActionResult Login(string code = "")
{
var ret = new
{
result = "fail",
token = ""
};
if (!string.IsNullOrEmpty(code))
{
//TODO通过MD5编码的code获取密码
if (_accountList.ContainsKey(code))
{
string psw = _accountList[code];//临时的
ret = new
{
result = "ok",
token = MakeToken(psw)
};
}
}
return Ok(ret);
}
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using OTWebSocket.API.Services;
using OTWebSocket.API.Types;
using Swashbuckle.AspNetCore.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class DebugController : BaseController
{
private readonly ApiService _apiService;
public DebugController(
ApiService apiService,
ILogger<DebugController> logger) : base(logger)
{
_apiService = apiService;
}
/// <summary>
/// 通过交易所接口获取订单信息
/// </summary>
/// <param name="orderId">订单Id</param>
/// <returns>本地订单对象</returns>
[SwaggerOperation(Tags = new[] { "调试接口" })]
[ProducesResponseType(typeof(OTOrderInfo), 200)]
[ProducesResponseType(typeof(string), 401)]
[ProducesResponseType(typeof(string), 429)]
[HttpGet("QueryOrderByOrderID")]
public async Task<IActionResult> QueryOrderByOrderID(string orderId)
{
OTOrderInfo ret = await _apiService.QueryOrderFromMongo(orderId);
return Ok(ret);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.Annotations;
namespace OTWebSocket.API.Controllers
{
/// <summary>
///
/// </summary>
[Produces("application/json")]
[Route("api/[controller]")]
public class LogsController : BaseController
{
/// <summary>
///
/// </summary>
public LogsController(
IConfiguration cfg,
ILogger<LogsController> logger
) : base(logger)
{
}
/// <summary>
/// 清除日志
/// </summary>
/// <returns></returns>
[SwaggerOperation(Tags = new[] { "日志管理接口" })]
[ProducesResponseType(typeof(bool), 200)]
[HttpGet("ClearLog")]
public IActionResult ClearLog(string afterfile = "")
{
if (!Request.Cookies.ContainsKey("MOToken") || !ValidToken(Request.Cookies["MOToken"].ToString()))
{
return Unauthorized();
}
int count = 0;
string logPath = Environment.CurrentDirectory + $"/logs";
List<string> filenames = Directory.GetFiles(logPath).ToList();
if (filenames.Count > 0)
{
List<FileInfo> fileInfos = new List<FileInfo>();
filenames.ForEach(x =>
{
FileInfo info = new FileInfo(x);
fileInfos.Add(info);
});
fileInfos.Sort((x, y) => x.LastWriteTime < y.LastWriteTime ? 1 : -1);
if (!string.IsNullOrEmpty(afterfile))
{
int idx = fileInfos.FindIndex(x => x.Name.Equals(afterfile + ".log"));
if (idx != -1)
{
fileInfos.RemoveRange(0, idx + 1);
}
else
{
//错误的afterfile将导致删除失败
return Ok("Failed:file name error");
}
}
foreach (var info in fileInfos)
{
try
{
info.Delete();
count++;
}
catch { }
}
}
return Ok($"Success:{count}");
}
/// <summary>
/// 日志查看
/// </summary>
/// <returns></returns>
[SwaggerOperation(Tags = new[] { "日志管理接口" })]
[ProducesResponseType(typeof(bool), 200)]
[HttpGet("GetLog")]
public IActionResult GetLog(string basefile = "", int type = 0)
{
if (!Request.Cookies.ContainsKey("MOToken") || !ValidToken(Request.Cookies["MOToken"].ToString()))
{
return Unauthorized();
}
JObject ret = new JObject
{
{ "file", "" },
{ "logs", new JArray() }
};
FileInfo fileInfo = null;
string logPath = Environment.CurrentDirectory + $"/logs";
List<string> filenames = Directory.GetFiles(logPath).ToList();
if (filenames.Count > 0)
{
List<FileInfo> fileInfos = new List<FileInfo>();
filenames.ForEach(x =>
{
FileInfo info = new FileInfo(x);
fileInfos.Add(info);
});
fileInfos.Sort((x, y) => x.LastWriteTime < y.LastWriteTime ? 1 : -1);
int idx = 0;
if (!string.IsNullOrEmpty(basefile))
{
idx = fileInfos.FindIndex(x => x.Name.Equals(basefile + ".log"));
}
if (idx != -1)
{
fileInfo = null;
if (type == -1 && idx != 0)
{
fileInfo = fileInfos[idx - 1];
}
if (type == 1 && idx != fileInfos.Count - 1)
{
fileInfo = fileInfos[idx + 1];
}
if (type == 0)
{
fileInfo = fileInfos[idx];
}
}
}
if (fileInfo != null)
{
TextReader reader = null;
//while (reader == null)
//{
try
{
reader = fileInfo.OpenText();
}
catch (Exception e)
{
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG] 日志文件读取异常 file:{fileInfo.Name}:{e.Message}\n{e.StackTrace}");
}
//}
ret["file"] = fileInfo.Name.Replace(".log", "");
string line;
while ((line = reader.ReadLine()) != null)
{
try
{
((JArray)ret["logs"]).Add(JObject.Parse(line));
}
catch (Exception e)
{
Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss:ffff}][LOG] 日志文件读取异常 file:{fileInfo.Name}:{e.Message}\n{e.StackTrace}");
}
}
reader.Close();
}
return Ok(ret);
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>OTWebSocket.API.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CSRedisCore" Version="3.6.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.13" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="MO.RabbitMQ" Version="1.1.3" />
<PackageReference Include="MongoDB.Bson" Version="2.12.3" />
<PackageReference Include="MongoDB.Driver" Version="2.11.2" />
<PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.11.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.1.1" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
</Project>
This diff is collapsed.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Targets;
using NLog.Web;
using OTWebSocket.API.Addons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API
{
public class Program
{
public static void Main(string[] args)
{
Target.Register<MOLogTarget>("MOLog");
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
NLogBuilder.ConfigureNLog($"nlog.config");
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
string settingsFileName = "appsettings.json";
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT").Equals("Development"))
{
settingsFileName = "appsettings.Development.json";
}
IConfiguration configuration = configurationBuilder.AddJsonFile(settingsFileName, optional: true, reloadOnChange: true)
.AddCommandLine(args)
.Build();
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseNLog();
webBuilder.UseConfiguration(configuration);
});
}
}
}
{
"profiles": {
"OTWebSocket.API": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OTWebSocket.API.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
namespace OTWebSocket.API.Services
{
public class ApiService
{
private readonly string _apiBaseUrl;
private readonly ILogger<ApiService> _logger;
private readonly HttpClient _httpClient;
public ApiService(IOptions<ApiOptions> options, ILogger<ApiService> logger)
{
_apiBaseUrl = options.Value.ApiBaseUrl;
_logger = logger;
_httpClient = new HttpClient();
}
public async Task<List<AccountInfo>> GetKeyList()
{
_logger.Debug($"[请求账户列表]");
List<AccountInfo> ret = null;
string endpoint = $"/key";
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, _apiBaseUrl + endpoint);
HttpResponseMessage resp = await _httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
_logger.Debug($"[请求账户列表] API返回:{content}");
JArray jsonArray = JArray.Parse(content);
ret = new List<AccountInfo>();
foreach (JObject json in jsonArray)
{
AccountInfo item = new AccountInfo
{
Id = int.Parse(json["id"].ToString()),
FundName = json["fundName"].ToString(),
OTKey = json["ot_key"].ToString(),
OTSerect = json["ot_secret"].ToString()
};
JArray bindings = (JArray)json["accounts"];
item.Accounts = new BindingInfo[bindings.Count];
for (int i = 0; i < bindings.Count; i++)
{
JObject bindingJson = (JObject)bindings[i];
item.Accounts[i] = new BindingInfo
{
ApiKey = bindingJson["ex_key"].ToString(),
Exchange = bindingJson["ot_exchange"].ToString().GetExchangeEnum(),
OTAccount = bindingJson["ot_account"].ToString()
};
}
ret.Add(item);
}
return ret;
}
public async Task<OTOrderInfo> QueryOrderFromExchange(string orderId, string fund_name, string OTAccountId, string ot_symbol)
{
_logger.Debug($"[请求OT订单信息] orderId={orderId},fund_name={fund_name},OTAccountId={OTAccountId},ot_symbol={ot_symbol }");
OTOrderInfo order = null;
try
{
string endpoint = $"/mao/ot/query";
JObject payload = new JObject
{
{ "fund_name", fund_name },
{ "oid", orderId },
{ "ot_account", OTAccountId },
{ "ot_symbol", ot_symbol }
};
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, _apiBaseUrl + endpoint);
req.Content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage resp = await _httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
_logger.Debug($"[请求OT订单信息] API返回:{content}");
JArray jsonArray = JArray.Parse(content);
JObject json = (JObject)jsonArray[0];
if (json["average_dealt_price"].Type == JTokenType.Null) json["average_dealt_price"] = -1;
if (json["dealt_amount"].Type == JTokenType.Null) json["dealt_amount"] = -1;
if (json["commission"].Type == JTokenType.Null) json["commission"] = -1;
if (json["dealt_volume"].Type == JTokenType.Null) json["dealt_volume"] = -1;
order = new OTOrderInfo
{
Status = json["status"].ToString(),
DealtAveragePrice = json["average_dealt_price"].ToObject<decimal>(),
DealtAmount = json["dealt_amount"].ToObject<decimal>(),
DealtVolume = json["dealt_volume"].ToObject<decimal>(),
Commission = json["commission"].ToObject<decimal>(),
UpdateTimestamp = json["last_update"].ToObject<DateTime>()
};
order.DealtVolume = order.DealtAmount * order.DealtAveragePrice;
}
catch (Exception e)
{
_logger.LogError($"[请求OT订单信息] 发生异常{e.Message}\n{e.StackTrace}");
}
return order;
}
public async Task<OTOrderInfo> QueryOrderFromMongo(string orderId)
{
_logger.Debug($"[请求MongoDB订单信息] orderId={orderId}");
OTOrderInfo order = null;
try
{
string endpoint = $"/mao/mongo/query/{orderId}";
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, _apiBaseUrl + endpoint);
HttpResponseMessage resp = await _httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
_logger.Debug($"[请求MongoDB订单信息] API返回:{content}");
JObject json = JObject.Parse(content);
json["orderId"] = json["_id"];
if (json["SubmitPrice"].Type == JTokenType.Null) json["SubmitPrice"] = -1;
if (json["SubmitAmount"].Type == JTokenType.Null) json["SubmitAmount"] = -1;
if (json["SubmitVolume"].Type == JTokenType.Null) json["SubmitVolume"] = -1;
if (json["DealtAveragePrice"].Type == JTokenType.Null) json["DealtAveragePrice"] = -1;
if (json["DealtAmount"].Type == JTokenType.Null) json["DealtAmount"] = -1;
if (json["Commission"].Type == JTokenType.Null) json["Commission"] = -1;
order = json.ToObject<OTOrderInfo>();
order.SubmitVolume = order.SubmitAmount * order.SubmitPrice;
}
catch (Exception e)
{
_logger.LogError($"[请求OT订单信息] 发生异常{e.Message}\n{e.StackTrace}");
}
return order;
}
public async Task<bool> UpdateMongoOrder(OTOrderInfo order)
{
try
{
string endpoint = $"/mao/mongo/update/{order.OrderId}";
JObject payload = JObject.FromObject(order);
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Put, _apiBaseUrl + endpoint);
req.Content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage resp = await _httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
_logger.Debug($"[更新MongoDB订单信息] API返回:{content}");
return true;
}
catch (Exception e)
{
_logger.LogError($"[更新MongoDB订单信息] 发生异常{e.Message}\n{e.StackTrace}");
return false;
}
}
}
}
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OTWebSocket.API.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Services
{
/// <summary>
/// RabbitMQ 封装服务
/// </summary>
public class MQConsumerService
{
private readonly ILogger _logger;
private readonly RedisService _redisService;
public MQConsumerService(
RedisService redisService,
ILogger<MQConsumerService> logger
)
{
_logger = logger;
_redisService = redisService;
_logger.LogInformation($"MQConsumerService 初始化完毕");
}
/// <summary>
/// TradeCenter队列消费者
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public async Task<bool> TradeCenterMessageHandler(string msg)
{
bool result = true;
_logger.Debug($"[MQ] 收到队列Queue消息:{msg}");
try
{
MQMessage message = new MQMessage(msg);
_logger.Debug($"[MQ] 解析消息成功:{JObject.FromObject(message)}");
switch (message.Name)
{
case "RemoveLiveOrder": // 订单完成,可以删除,由陈涛发起
result = await RemoveLiveOrder(message.Params);
break;
default:
_logger.LogWarning($"消息未处理:{msg}");
break;
}
}
catch (Exception e)
{
_logger.LogError($"[MQ] 消费消息错误:{msg}\r\n{e.Message}\r\n{e.StackTrace}");
result = false;
}
return result;
}
public async Task<bool> RemoveLiveOrder(JObject paramsJson)
{
_logger.Debug($"[MQ] 收到清除订单缓存信息:{paramsJson}");
try
{
string orderID = paramsJson["orderId"].ToString();
OTOrderInfo order = await _redisService.LockGetOrder(orderID);
if (order != null)
{
if (!order.Comment.Equals("执行订单缓存删除") && !order.Comment.EndsWith("[备删]"))
{
order.Comment = "准备删除缓冲信息[备删]";
await _redisService.SetLiveOrder(order, true);
_logger.Debug($"[MQ] 设置清除订单缓存信息成功:{paramsJson}");
}
else
{
_logger.Debug($"[MQ] 无需设置清除订单缓存:{JObject.FromObject(order)}");
}
}
await _redisService.ReleaseOrder(orderID);
await _redisService.RemoveLiveOrder(orderID);
}
catch (Exception e)
{
_logger.LogError($"[MQ] 解析消息数据异常:{paramsJson},{e.Message}\n{e.StackTrace}");
return false;
}
return true;
}
}
}
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using OTWebSocket.API.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace OTWebSocket.API.Services
{
public class MongoDBService
{
private readonly IMongoDatabase _db;
public MongoDBService(IOptions<MongoOptions> options, ILogger<MongoDBService> logger)
{
logger?.Debug("开始 MongoDB 服务配置");
var _mongoClient = new MongoClient(options.Value.ConnectionAddress);
_db = _mongoClient.GetDatabase(options.Value.DataBase);
logger?.Debug("MongoDB 服务配置完成");
}
public async Task<List<LiveOrderRecord>> GetLiveOrderIds()
{
var collect = _db.GetCollection<LiveOrderRecord>("LiveOrderRecord");
if (collect != null)
{
return await collect.AsQueryable().ToListAsync();
}
return null;
}
public async Task DeleteOrderRecordAsync(string orderId)
{
var collect = _db.GetCollection<LiveOrderRecord>("LiveOrderRecord");
if (collect != null)
{
await collect.DeleteOneAsync(x=>x.Id.Equals(orderId));
}
}
public async Task<OTOrderInfo> GetOTOrder(Expression<Func<OTOrderInfo, bool>> filter)
{
var collect = _db.GetCollection<OTOrderInfo>("OTOrderInfo");
if (collect != null)
{
var cursor = await collect.FindAsync(filter);
return await cursor.FirstOrDefaultAsync();
}
return null;
}
public async Task UpdateOTOrderAsync(OTOrderInfo order)
{
var collect = _db.GetCollection<OTOrderInfo>("OTOrderInfo");
if (collect != null)
{
await collect.ReplaceOneAsync(x=>x.OrderId.Equals(order.OrderId), order);
}
}
public async Task<bool> InsertLogAsync(string id, string logs)
{
var collect = _db.GetCollection<OrderLog>("OrderLog");
OrderLog orderLog = new OrderLog(id, logs);
await collect.InsertOneAsync(orderLog);
return true;
}
}
}
namespace OTWebSocket.API.Services
{
/// <summary>
/// MongoDB配置对象
/// </summary>
public class MongoOptions
{
/// <summary>
/// 数据库名称
/// </summary>
public string DataBase { get; set; }
/// <summary>
/// 连接字符串
/// </summary>
public string ConnectionAddress { get; set; }
}
}
This diff is collapsed.
namespace OTWebSocket.API.Services
{
/// <summary>
/// Redis配置对象实体
/// </summary>
public class RedisOptions
{
/// <summary>
/// 连接信息,参见json配置信息
/// </summary>
public string Connection { get; set; }
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; }
}
public class ApiOptions
{
public string ApiBaseUrl { get; set; }
}
}
using CSRedis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using OTWebSocket.API.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Services
{
public class RedisService
{
private readonly ILogger<RedisService> _logger;
private readonly CSRedisClient _redisClient;
public RedisService(IOptions<RedisOptions> options, ILogger<RedisService> logger)
{
_logger = logger;
logger?.Debug($"开始 Redis 服务配置");
_redisClient = new CSRedisClient(string.IsNullOrEmpty(options.Value.Password) ? options.Value.Connection : $"{options.Value.Connection},password={options.Value.Password}");
logger?.Debug($"Redis 服务配置完成");
}
public async Task<long> DelAsync(params string[] key) => await _redisClient.DelAsync(key);
public async Task<Dictionary<string, string>> HGetAllAsync(string key) => await _redisClient.HGetAllAsync(key);
public async Task<string> HGetAsync(string key, string field) => await _redisClient.HGetAsync(key, field);
public async Task<bool> SetLiveOrder(OTOrderInfo order, bool force = false)
{
try
{
if (string.IsNullOrEmpty(order.OrderId)) return false;
_logger.Debug($"尝试写入redis缓存{JObject.FromObject(order)}");
await _redisClient.HSetAsync("LiveOrders", order.OrderId, order);
await _redisClient.HSetAsync($"OrderLogs:{order.OrderId}", DateTime.UtcNow.ToString("yyMMddHHmmssffffff"), order);
_logger.Debug($"写入redis缓存完毕");
return true;
}
catch (Exception e)
{
_logger.LogError(e, $"添加/修改缓冲订单到Redis失败。Message = {e.Message}\n{e.StackTrace}");
}
return false;
}
public async Task<OTOrderInfo> GetLiveOrder(string orderId)
{
try
{
if (string.IsNullOrEmpty(orderId)) return null;
string jsonStr = await _redisClient.HGetAsync("LiveOrders", orderId);
if (!string.IsNullOrEmpty(jsonStr))
{
JObject json = JObject.Parse(jsonStr);
return json.ToObject<OTOrderInfo>();
}
}
catch (Exception e)
{
_logger.LogError(e, $"获取缓冲订单信息失败。orderId={orderId}\nMessage = {e.Message}\n{e.StackTrace}");
}
return null;
}
/// <summary>
/// 锁定订单缓存数据
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
public async Task<OTOrderInfo> LockGetOrder(string orderId)
{
while (!await _redisClient.HSetNxAsync("OrderLock", orderId, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.ffff")))
{
string jsonStr = await _redisClient.HGetAsync("OrderLock", orderId);
//如果超时则删除锁
if (DateTime.TryParse(jsonStr, out DateTime time))
{
if (time.AddMilliseconds(3000) < DateTime.UtcNow)
{
await _redisClient.HDelAsync("OrderLock", orderId);
}
}
else
{
await _redisClient.HDelAsync("OrderLock", orderId);
}
}
return await GetLiveOrder(orderId);
}
/// <summary>
/// 释放订单缓存订单锁,返回释放前订单信息
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
public async Task<OTOrderInfo> ReleaseOrder(string orderId)
{
OTOrderInfo ret = await GetLiveOrder(orderId);
await _redisClient.HDelAsync("OrderLock", orderId);
return ret;
}
public async Task<bool> RemoveLiveOrder(string orderID)
{
try
{
if (string.IsNullOrEmpty(orderID)) return false;
OTOrderInfo order = await GetLiveOrder(orderID);
if (order != null)
{
await _redisClient.HDelAsync("LiveOrders", orderID);
}
return true;
}
catch (Exception e)
{
_logger.LogError(e, $"删除缓冲订单到Redis失败。Message = {e.Message}\n{e.StackTrace}");
}
return false;
}
/// <summary>
/// 获取LiveOrder集合,每次500条
/// </summary>
/// <param name="cursor"></param>
/// <returns></returns>
public async Task<RedisScan<(string field, string value)>> HScanLiveOrder(int cursor)
{
try
{
if (cursor < 0) cursor = 0;
return await _redisClient.HScanAsync("LiveOrders", cursor, count: 500);
}
catch (Exception e)
{
_logger.LogError(e, $"批量获取缓冲订单失败。Message = {e.Message}\n{e.StackTrace}");
}
return null;
}
/// <summary>
/// 获取OrderLog集合
/// </summary>
/// <returns></returns>
public async Task<string[]> LoadOrderLogKeys()
{
try
{
return await _redisClient.KeysAsync("OrderLogs*");
}
catch (Exception e)
{
_logger.LogError(e, $"获取OrderLog集合失败。Message = {e.Message}\n{e.StackTrace}");
}
return null;
}
}
}
This diff is collapsed.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using MO.RabbitMQ;
using MO.RabbitMQ.Enum;
using OTWebSocket.API.Addons;
using OTWebSocket.API.Services;
using OTWebSocket.API.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API
{
public class Startup
{
private WebSocketService _webSocketService;
private OrderTracer _orderTracer;
private ILogger<Startup> _logger;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
if (Configuration["DebugLogging"] != null && Configuration["DebugLogging"].Equals("ON"))
{
Environment.SetEnvironmentVariable("debuglogging", "ON");
}
else
{
Environment.SetEnvironmentVariable("debuglogging", "OFF");
}
services.AddRouting();
services.AddControllers();
services.AddMvcCore().AddNewtonsoftJson();
#region 业务类服务
services.AddSingleton<WebSocketService>();
services.Configure<ApiOptions>(o => {
o.ApiBaseUrl = Configuration.GetSection("ApiConfig").Get<ApiOptions>().ApiBaseUrl;
});
services.AddSingleton<ApiService>();
services.Configure<RedisOptions>(o => {
o.Connection = Configuration.GetSection("Redis").Get<RedisOptions>().Connection;
o.Password = Configuration.GetSection("Redis").Get<RedisOptions>().Password;
});
services.AddSingleton<RedisService>();
services.Configure<MongoOptions>(o => {
o.ConnectionAddress = Configuration.GetSection("MongoDBConfig").Get<MongoOptions>().ConnectionAddress;
o.DataBase = Configuration.GetSection("MongoDBConfig").Get<MongoOptions>().DataBase;
});
services.AddSingleton<MongoDBService>();
services.AddMatrixOneMQ(Configuration.GetSection("MORabbitMQ"));
services.AddSingleton<MQConsumerService>();
services.AddSingleton<OrderTracer>();
#endregion
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("debug", new OpenApiInfo
{
Version = "v1",
Description = "1-Token WebsocketHub Swagger接口文档 V1 基于Asp.net Core 3.1",
Title = "调试接口",
});
c.SwaggerDoc("system", new OpenApiInfo
{
Version = "v1",
Description = "1-Token WebsocketHub Swagger接口文档 V1 基于Asp.net Core 3.1",
Title = "系统管理接口",
});
c.SwaggerDoc("other", new OpenApiInfo
{
Version = "v1",
Description = "1-Token WebsocketHub Swagger接口文档 V1 基于Asp.net Core 3.1",
Title = "其他接口",
});
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"OTWebSocket.API.xml"));
c.EnableAnnotations();
c.DocumentFilter<EnumDocumentFilter>();
});
}
public void Configure(
WebSocketService webSocketService,
IHostApplicationLifetime appLifetime,
ILogger<Startup> logger,
IApplicationBuilder app,
OrderTracer orderTracer,
MQConsumerService consumerService)
{
_orderTracer = orderTracer;
_logger = logger;
_logger?.Debug("=========== 系统服务正在启动 ===========");
_webSocketService = webSocketService;
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
var path = Configuration.GetSection("Swagger:VirtualPath").Value;
string scheme = "https";
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT").Equals("Development"))
{
scheme = "http";//本地测试使用http协议
}
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((doc, req) => doc.Servers = new List<OpenApiServer> {
new OpenApiServer { Url = $"{scheme}://{req.Host.Value}{path}" }
});
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{path}/swagger/debug/swagger.json", "调试接口");
c.SwaggerEndpoint($"{path}/swagger/system/swagger.json", "服务管理");
c.SwaggerEndpoint($"{path}/swagger/other/swagger.json", "其他接口");
});
//MQ消息消费配置
app.UseMatrixOneMQReceiveMethod(new MO.RabbitMQ.Options.RabbitMQReceiveOptions()
{
Options = new List<MO.RabbitMQ.Options.ReceiveOption>() {
new MO.RabbitMQ.Options.ReceiveOption() {//需要启动查单任务的订单(进行中)消息
Exchange = "onetoken.transient.task",
ExchangeType = ExchangeTypeEnum.Direct,
RoutingKey = "WebSocketHub",
Queue = "onetoken.WebSocketHub",
ReceiveMethod = consumerService.TradeCenterMessageHandler
}
}
});
}
private void OnStarted()
{
_logger.LogWarning($"[服务器状态]WebHost 已经启动({DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff})");
_orderTracer.LiveOrdersCheck();
_orderTracer.StartPoll();
_webSocketService.Start();
}
private void OnStopping()
{
_logger.LogWarning($"[服务器状态]WebHost 开始停止({DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff})");
_webSocketService.Stop();
_orderTracer.StopPoll();
}
private void OnStopped()
{
_logger.LogWarning($"[服务器状态]WebHost 已经关闭({DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff})");
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Types
{
public class AccountInfo
{
public int Id { get; set; }
public string FundName { get; set; }
public string OTKey { get; set; }
public string OTSerect { get; set; }
public BindingInfo[] Accounts { get; set; }
}
public class BindingInfo
{
public string OTAccount { get; set; }
public string ApiKey { get; set; }
public ExchangeEnum Exchange { get; set; }
}
}

using System;
namespace OTWebSocket.API.Types
{
[Flags]
public enum ChannelStatusFlag
{
None = 0,
Created = 1 << 0,
Ready = 1 << 1,
Authorizing = 1 << 2,
Authorized = 1 << 3,
Connecting = 1 << 4,
Connected = 1 << 5,
Closing = 1 << 6,
Closed = 1 << 7,
Senting = 1 << 8,
Receiving = 1 << 9,
Error = 1 << 10,
Disposing = 1 << 11,
Disposed = 1 << 12
}
}
namespace OTWebSocket.API.Types
{
/// <summary>
/// 命令枚举
/// </summary>
public enum Command
{
/// <summary>
/// 未知命令
/// </summary>
Unknown,
/// <summary>
/// 订阅账户消息
/// </summary>
Sub_Info,
/// <summary>
/// 订阅订单消息
/// </summary>
Sub_Order,
/// <summary>
/// ping
/// </summary>
Ping
}
/// <summary>
/// 命令结构
/// </summary>
public struct CommandStruct
{
/// <summary>
/// 命令
/// </summary>
public Command Command;
/// <summary>
/// 所需参数 Json 串
/// </summary>
public string JsonParams;
/// <summary>
/// 生成时间记录
/// </summary>
public Timestamp Timestamp;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Types
{
/// <summary>
/// 交易所枚举
/// </summary>
public enum ExchangeEnum
{
Unknown,
Binance,
Binancef
}
public static partial class Extensions
{
/// <summary>
/// 字符串获得交易所代码枚举
/// </summary>
/// <param name="str">字符串</param>
/// <returns></returns>
public static ExchangeEnum GetExchangeEnum(this string str)
{
if (string.IsNullOrEmpty(str)) return ExchangeEnum.Unknown;
switch (str.ToUpper())
{
case "BN":
case "BINANCE":
return ExchangeEnum.Binance;
case "Binancef":
return ExchangeEnum.Binancef;
default:
return ExchangeEnum.Unknown;
}
}
/// <summary>
/// 获取交易所全名
/// </summary>
/// <param name="exchange"></param>
/// <returns></returns>
public static string GetFullName(this ExchangeEnum exchange)
{
string ret = exchange switch
{
ExchangeEnum.Binance => "Binance",
ExchangeEnum.Binancef=> "Binancef",
_ => null,
};
return ret;
}
/// <summary>
/// 根据交易所枚举获取交易所缩写字串
/// </summary>
/// <param name="exchange">交易所枚举</param>
/// <returns>缩写字串</returns>
public static string GetShortName(this ExchangeEnum exchange)
{
string ret = exchange switch
{
ExchangeEnum.Binance => "BN",
ExchangeEnum.Binancef => "BNCEF",
_ =>null
};
return ret;
}
}
}
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Types
{
public static partial class Extensions
{
public static long Nonce;
/// <summary>
/// 调试日志外部方法,用于记录调试日志
/// </summary>
/// <param name="_logger"></param>
/// <param name="msg"></param>
/// <param name="simple">简洁模式,超过255个字符将被忽略</param>
/// <returns></returns>
public static bool Debug(this ILogger _logger, string msg, bool simple = false)
{
string a = Environment.GetEnvironmentVariable("debuglogging");
if (string.IsNullOrEmpty(a))
{
a = "OFF";
Environment.SetEnvironmentVariable("debuglogging", a);
}
if (a.Equals("ON"))
{
if (simple)
{
int length = msg.Length;
if (length > 255)
msg = msg.Substring(0, 255) + " ..." + length + " bytes totally";
}
_logger.LogInformation(msg);
}
else
{
int length = msg.Length;
if (length > 15)
msg = msg.Substring(0, 15) + " ..." + length + " bytes totally";
_logger.LogInformation($"--临时日志已关闭--:{msg}");
}
return true;
}
/// <summary>
/// 获得*遮挡的字符串,主要用于保密信息日志输出
/// </summary>
/// <param name="str"></param>
/// <param name="length"></param>
/// <returns></returns>
public static string ToSecureMaskString(this string str, int length = 5)
{
if (!string.IsNullOrEmpty(str) && str.Length > length)
{
return str.Substring(0, length) + "*****";
}
else
return "*****";
}
}
}
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
namespace OTWebSocket.API.Types
{
public class LiveOrderRecord
{
public LiveOrderRecord(string OrderId)
{
Id = OrderId;
LastUpdateTime = DateTime.UtcNow;
}
/// <summary>
/// MongoDB主键字段
/// </summary>
[BsonId]
public string Id { get; set; }
public DateTime LastUpdateTime;
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization;
namespace OTWebSocket.API.Types
{
/// <summary>
/// 内部专用时间日期类型,整型毫秒级UTC时间戳
/// </summary>
[JsonConverter(typeof(TimestampJsonConverter))]
[BsonSerializer(typeof(TimestampSerializer))]
public class Timestamp
{
private long value;
public DateTime DateTime => new DateTime(1970, 1, 1, 0, 0, 0, 0).AddMilliseconds(value);
public Timestamp(DateTime dateTime)
{
long v = (long)(dateTime - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
if (v < 0) v = 0;
value = v;
}
public Timestamp(long v)
{
if (v < 0) v = 0;
value = v;
}
public bool Add(long v)
{
value += v;
if (value < 0)
{
value = 0;
return false;
}
return true;
}
public bool Set(long v)
{
value = v;
if (value < 0)
{
value = 0;
return false;
}
return true;
}
public long TimeStamp => value;
public override string ToString()
{
return value.ToString();
}
public string ToString(string format)
{
DateTime datetime = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddMilliseconds(value);
return datetime.ToString(format);
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static implicit operator Timestamp(long v)
{
return new Timestamp(v);
}
public static implicit operator Timestamp(DateTime dateTime)
{
long v = (long)(dateTime - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
if (v < 0) v = 0;
return new Timestamp(v);
}
public static Timestamp Now => DateTime.UtcNow;
public static Timestamp Min => 0;
public static Timestamp Empty => 0;
public static Timestamp operator +(Timestamp a, long b)
{
return new Timestamp(a.value + b);
}
public static Timestamp operator -(Timestamp a, long b)
{
return new Timestamp(a.value - b);
}
public static long operator -(Timestamp a, Timestamp b)
{
return a.value - b.value;
}
public static bool operator ==(Timestamp a, Timestamp b)
{
return a.value == b.value;
}
public static bool operator !=(Timestamp a, Timestamp b)
{
return a.value != b.value;
}
public static bool operator >(Timestamp a, Timestamp b)
{
return a.value > b.value;
}
public static bool operator <(Timestamp a, Timestamp b)
{
return a.value < b.value;
}
public static bool operator >=(Timestamp a, Timestamp b)
{
return a.value >= b.value;
}
public static bool operator <=(Timestamp a, Timestamp b)
{
return a.value <= b.value;
}
}
public class TimestampJsonConverter : JsonConverter<Timestamp>
{
public override void WriteJson(JsonWriter writer, [AllowNull] Timestamp value, JsonSerializer serializer)
{
try
{
writer.WriteValue(long.Parse(value.ToString()));
}
catch
{
writer.WriteValue(0);
}
}
public override Timestamp ReadJson(JsonReader reader, Type objectType, [AllowNull] Timestamp existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
if (reader.Value == null) return 0;
return new Timestamp((long)reader.Value);
}
catch
{
return 0;
}
}
}
public class TimestampSerializer : IBsonSerializer<Timestamp>
{
public Timestamp Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
long v = 0;
v = context.Reader.CurrentBsonType switch
{
MongoDB.Bson.BsonType.Double => (long)context.Reader.ReadDouble(),
MongoDB.Bson.BsonType.String => ParseString(context.Reader.ReadString()),
MongoDB.Bson.BsonType.DateTime => context.Reader.ReadDateTime(),
MongoDB.Bson.BsonType.Int32 => (long)context.Reader.ReadInt32(),
MongoDB.Bson.BsonType.Timestamp => context.Reader.ReadTimestamp(),
MongoDB.Bson.BsonType.Int64 => context.Reader.ReadInt64(),
_ => 0,
};
return new Timestamp(v);
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Timestamp value)
{
long v = 0;
if (value == null) v = 0;
else
v = value.TimeStamp;
context.Writer.WriteInt64(v);
}
object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
return Deserialize(context, args);
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
long v = 0;
if (value == null)
v = 0;
else
long.TryParse(value.ToString(), out v);
context.Writer.WriteInt64(v);
}
public Type ValueType { get => typeof(Timestamp); }
private long ParseString(string str)
{
long v = 0;
if (DateTime.TryParse(str, out DateTime dateTime))
{
v = new Timestamp(dateTime).TimeStamp;
}
else if (long.TryParse(str, out long value))
{
v = value;
}
return v;
}
}
}

using Newtonsoft.Json.Linq;
using System;
namespace OTWebSocket.API.Types
{
/// <summary>
/// Message Queue 消息对象
/// </summary>
public class MQMessage
{
private static long sid = 0;
public MQMessage()
{ }
/// <summary>
///
/// </summary>
/// <param name="msg"></param>
public MQMessage(string msg)
{
try
{
JObject msgJson = JObject.Parse(msg);
Name = msgJson["Name"].ToString();
Params = (JObject)msgJson["Params"];
SendTime = DateTime.Parse(msgJson["SendTime"].ToString());
}
catch (Exception e)
{
Name = "Unknown type Message";
Params = new JObject
{
{"Message", e.Message },
{"StackTrace", e.StackTrace },
{"msg", msg }
};
}
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="params"></param>
public MQMessage(string name, JObject @params)
{
Name = name;
Params = @params;
SendTime = DateTime.Now;
if (!Params.ContainsKey("sid"))
Params.Add("sid", sid++);
else
Params["sid"] = sid++;
if (sid == long.MaxValue) sid = 0;
}
/// <summary>
///
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public JObject Params { get; set; }
/// <summary>
///
/// </summary>
public DateTime SendTime { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public string ToJsonString()
{
JObject jsonString = new JObject
{
{ "Name", Name },
{ "Params", Params },
{ "SendTime", SendTime }
};
return jsonString.ToString();
}
}
}
This diff is collapsed.
namespace OTWebSocket.API.Types
{
/// <summary>
/// 成交记录信息
/// </summary>
public class OTDealInfo
{
/// <summary>
/// 交易记录唯一标识,用于去重
/// </summary>
public string Id { get; set; }
/// <summary>
/// 成交价格
/// </summary>
public decimal DealtPrice { get; set; }
/// <summary>
/// 成交数量
/// </summary>
public decimal DealtAmount { get; set; }
/// <summary>
/// 成交金额
/// </summary>
public decimal DealtVolume { get; set; }
/// <summary>
/// 手续费币种
/// </summary>
public string CommissionCurrency { get; set; }
/// <summary>
/// 手续费
/// </summary>
public decimal Commission { get; set; }
/// <summary>
/// 成交时间
/// </summary>
public Timestamp TradeTime { get; set; }
}
}
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
namespace OTWebSocket.API.Types
{
/// <summary>
/// OT 订单信息对象
/// </summary>
public class OTOrderInfo
{
/// <summary>
/// 订单编号,订单提交时传入的ClientOrderId,订单提交前务必先进行MongDB的持久化!
/// </summary>
[BsonId]
public string OrderId { get; set; }
/// <summary>
/// 基金名,注意:基金名与账号是多对多的关系
/// </summary>
public string FundName { get; set; }
/// <summary>
/// 账号,注意:基金名与账号是多对多的关系
/// </summary>
public string OTAccount { get; set; }
/// <summary>
/// 交易所
/// </summary>
public string OTExchange { get; set; }
/// <summary>
/// 交易所绑定的Key
/// </summary>
public string ExchangeApiKey { get; set; }
/// <summary>
/// OT交易对
/// </summary>
public string OTSymbol { get; set; }
/// <summary>
/// 目标交易所的订单号
/// </summary>
public string ExchangeId { get; set; }
/// <summary>
/// 交易方向,buy/sale
/// </summary>
public string Side { get; set; }
/// <summary>
/// 交易类型,限价/市价,limit/market
/// </summary>
public string TradeType { get; set; }
/// <summary>
/// 提交价格
/// </summary>
public decimal SubmitPrice { get; set; }
/// <summary>
/// 提交数量
/// </summary>
public decimal SubmitAmount { get; set; }
/// <summary>
/// 提交的哪个啥,市价单的价格?
/// </summary>
public decimal SubmitVolume { get; set; }
/// <summary>
/// 提交时间
/// </summary>
public Timestamp SubmitTimestamp { get; set; }
/// <summary>
/// 成交均价
/// </summary>
public decimal DealtAveragePrice { get; set; }
/// <summary>
/// 成交量
/// </summary>
public decimal DealtAmount { get; set; }
/// <summary>
/// 成交额
/// </summary>
public decimal DealtVolume { get; set; }
/// <summary>
/// 交易所完成时间
/// </summary>
public Timestamp FinishTimestamp { get; set; }
/// <summary>
/// 交易所更新时间
/// </summary>
public Timestamp UpdateTimestamp { get; set; }
/// <summary>
/// OT更新时间
/// </summary>
public Timestamp OTUpdateTimestamp { get; set; }
/// <summary>
/// 手续费
/// </summary>
public decimal Commission { get; set; }
/// <summary>
/// 手续费币种
/// </summary>
public string CommissionCurrency { get; set; }
/// <summary>
/// 附加信息,主要用于记录日志信息
/// </summary>
public string Comment { get; set; }
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; }
/// <summary>
/// 标签
/// </summary>
public string Tags { get; set; }
/// <summary>
/// 成交明细
/// </summary>
public List<OTDealInfo> Deals { get; set; }
/// <summary>
/// 完成态判断,即订单信息不会更新的状态
/// </summary>
public bool IsFinished()
{
return Status != null && (
Status.Equals("end")
|| Status.Equals("dealt")
|| Status.Equals("withdrawn")
|| Status.Equals("part-deal-withdrawn")
|| Status.Equals("error-order")
);
}
}
}

using MongoDB.Bson.Serialization.Attributes;
using System;
namespace OTWebSocket.API.Types
{
public class OrderLog
{
public OrderLog()
{ }
public OrderLog(string OrderId, string logs)
{
Id = OrderId;
LastUpdateTime = Timestamp.Now;
Logs = logs;
}
/// <summary>
/// MongoDB主键字段
/// </summary>
[BsonId]
public string Id { get; set; }
public string Logs { get; set; }
public Timestamp LastUpdateTime;
}
}
{
"AllowedHosts": "*",
"ApiConfig": {
"ApiBaseUrl": "https://apit.matrixone.io/onetokenapi/api"
},
"DebugLogging": "ON",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"MongoDBConfig": {
"ConnectionAddress": "mongodb://13.115.26.128:27017",
"DataBase": "OneToken"
},
"MORabbitMQ": {
"Host": "13.115.26.128",
"Port": "5672",
"User": "tradecenter",
"Pwd": "tradecenter",
"Publishs": [
{
"Name": "TRANSIENT", //根据 Name 唯一标识通道,不能重复命名
"Exchange": "onetoken.transient.task", //RabbitMQ 交换机名称
"ExchangeType": "direct", //RabbitMQ 交换机类型[direct,fanout,headers,topic]
"RoutingKey": [ "orderUpdate", "WebSocketHub" ] //路由主键集合
}
]
},
"Redis": {
"Connection": "13.115.26.128:6379,poolsize=50,ssl=false,writeBuffer=102400,defaultDatabase=10",
"Password": "MukAzxGMOL2"
},
"Swagger": {
"VirtualPath": ""
}
}
\ No newline at end of file
{
"AllowedHosts": "*",
"ApiConfig": {
"ApiBaseUrl": "https://apit.matrixone.io/onetokenapi/api"
},
"DebugLogging": "ON",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"MongoDBConfig": {
"ConnectionAddress": "mongodb://172.10.0.18:27017",
"DataBase": "OneToken"
},
"MORabbitMQ": {
"Host": "172.18.0.231",
"Port": "5672",
"User": "tradecenter",
"Pwd": "tradecenter",
"Publishs": [
{
"Name": "TRANSIENT", //根据 Name 唯一标识通道,不能重复命名
"Exchange": "onetoken.transient.task", //RabbitMQ 交换机名称
"ExchangeType": "direct", //RabbitMQ 交换机类型[direct,fanout,headers,topic]
"RoutingKey": [ "orderUpdate", "WebSocketHub" ] //路由主键集合
}
]
},
"Redis": {
"Connection": "172.10.0.18:6379,poolsize=50,ssl=false,writeBuffer=102400,defaultDatabase=10",
"Password": "MukAzxGMOL2"
},
"Swagger": {
"VirtualPath": "/otwebsocket"
}
}
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target name="MOLog" xsi:type="MOLog">
<layout xsi:type="JsonLayout">
<attribute name="id" layout="${sequenceid}" />
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:upperCase=true}"/>
<attribute name="message" layout="${message}" />
<attribute name="exception" layout="${exception:format=tostring}" />
</layout>
</target>
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--Skip non-critical Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" maxLevel="Info" final="true" />
<logger name="System.Net.Http.*" maxLevel="Info" final="true" />
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="MOLog" />
<!-- BlackHole without writeTo -->
</rules>
</nlog>
\ No newline at end of file
body {
background-color: #1e1e1e;
margin: 0px;
padding: 5px;
font-size: 16px;
font-family: Courier New, Courier, monospace
}
.border {
border: 1px solid #686868;
display: flex;
justify-content: center;
}
.progress_frame {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
background-color: RGBA(0,0,0,0.8);
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loginscreen {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
background-color: #1e1e1e;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.errorscreen {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
background-color: #1e1e1e;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
@keyframes progress_bar_anim {
0% {
background-image: radial-gradient(#007acc,5%,#007acc,65%,#007acc00);
}
30% {
background-image: radial-gradient(#007acc,5%,#007acc,70%,#007acc00);
}
60% {
background-image: radial-gradient(#007acc,5%,#007acc,75%,#007acc00);
}
90% {
background-image: radial-gradient(#007acc,5%,#007acc,80%,#007acc00);
}
100% {
background-image: radial-gradient(#007acc,5%,#007acc,85%,#007acc00);
}
}
.progress_bar {
background-image: radial-gradient(#007acc,15%,#007acc,45%,#007acc00);
animation-name: progress_bar_anim;
animation-duration: 0.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-direction: alternate;
border-radius: 32px;
margin-bottom: 16px;
height: 64px;
width: 64px;
}
.progress_text {
margin-bottom: 64px;
color: #ffffff;
font-size: 16px;
}
.frame {
margin: 5px;
padding: 5px;
}
.loginframe {
height: 80px;
margin-bottom: 120px;
padding-top: 15px;
padding-bottom: 15px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.darkBackground {
background-color: #252526;
}
.titleText {
font-size: 18px;
color: #ffffff;
}
.whiteText {
color: #ffffff;
}
.grayText {
color: #686868;
}
.row {
display: inline-flex;
justify-content: start;
align-items: center;
flex-direction: row;
}
.filterbar {
width: 100%;
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: start;
}
.edit_psw {
min-width: 100px;
border: 1px solid #007acc;
margin: 1px;
margin-left: 5px;
margin-right: 5px;
padding: 1px;
}
.edit_in_or {
min-width: 100px;
border: 1px solid #007acc;
margin: 1px;
margin-left: 5px;
margin-right: 5px;
padding: 1px;
}
.edit_ex_or {
min-width: 100px;
border: 1px solid #007acc;
margin: 1px;
margin-left: 5px;
margin-right: 5px;
padding: 1px;
}
.loglist {
display: flex;
flex-direction: column;
justify-content: start;
min-height: 85vh;
height: 85vh;
width: 100%;
}
.scrollarea {
overflow-y: scroll;
width: 100%;
height: 100%;
}
.logrow {
width: 99%;
display: inline-flex;
flex-direction: row;
justify-content: start;
border-bottom: 1px solid #686868;
}
.logitem {
display: inline-block;
text-align: start;
font-size: 14px;
}
.button {
padding: 5px;
margin: 5px;
border: 1px solid #686868;
background-color: #1e1e1e;
display: flex;
justify-content: center;
cursor: default;
user-select: none;
}
.button:hover {
border: 1px solid #007acc;
background-color: #252526;
}
.button:active {
background-color: #007acc;
border: 1px solid #007acc;
}
.wide_button {
padding: 1px;
padding-left: 30px;
padding-right: 30px;
}
.smallbutton {
padding: 1px;
padding-left: 8px;
padding-right: 8px;
margin: 1px;
border: 1px solid #686868;
background-color: #1e1e1e;
display: flex;
color: #ffffff;
justify-content: center;
cursor: default;
user-select: none;
}
.smallbutton:hover {
border: 1px solid #007acc;
background-color: #252526;
}
.smallbutton:active {
background-color: #007acc;
border: 1px solid #007acc;
}
.disablebutton {
padding: 1px;
padding-left: 8px;
padding-right: 8px;
margin: 1px;
border: 1px solid #686868;
background-color: #1e1e1e;
color: #686868;
display: flex;
justify-content: center;
cursor: default;
user-select: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>OneToken WebsocketHub Console</title>
<script type="text/javascript" src="js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery/jquery.cookie.min.js"></script>
<script type="text/javascript" src="js/jquery/jquery.md5.js"></script>
<script type="text/javascript" src="js/vue/vue.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<link rel="stylesheet" href="css/mo.css" />
</head>
<body>
<div class="row">
<div class="frame titleText">OneToken WebsocketHub Console</div>
<div id="btn_swagger" class="button whiteText">Swagger</div>
<div id="btn_clean_all_logs" class="button whiteText">Clean all logs</div>
<div id="btn_clean_unloaded_logs" class="button whiteText">Clean unloaded logs</div>
<div id="field_file" class="edit_psw whiteText" contenteditable="true"></div>
<div id="btn_changedate" class="button whiteText">GO</div>
</div>
<div class="frame border whiteText darkBackground" style="flex-direction:column;">
<div class="filterbar">
filters:
include
<div class="edit_in_or" contenteditable="true"></div>
<div class="edit_in_or" contenteditable="true"></div>
<div class="edit_in_or" contenteditable="true"></div>
and exclude
<div class="edit_ex_or" contenteditable="true"></div>
<div class="edit_ex_or" contenteditable="true"></div>
<div class="edit_ex_or" contenteditable="true"></div>
<div id="btn_apply" class="smallbutton whiteText" style="text-align:center;">Apply</div>
<div id="btn_clean" class="smallbutton whiteText" style="text-align:center;">Clean</div>
</div>
<div id="list_logs" class="loglist">
<div class="row">
<div v-on:click="reset" class="button wide_button whiteText">Reset</div>
</div>
<div id="btn_loadnext" v-on:click="loadnext" class="button whiteText" style="text-align:center;">Load next >></div>
<div class="logrow">
<div class="logitem" style="min-width:100px;">Id</div>
<div class="logitem" style="min-width:260px;">Time</div>
<div class="logitem" style="min-width:60px;">Level</div>
<div class="logitem" style="width:calc(100% - 330px);text-align: center;">Message</div>
</div>
<div class="scrollarea">
<div class="logrow" v-for="log in logs">
<div class="logitem" style="color:#007acc;min-width:100px;">{{ log.id }}^</div>
<div class="logitem" style="color:#007acc;min-width:260px;">{{ log.time }}^</div>
<div class="logitem" style="color:#007acc;min-width:60px;">{{ log.level }}^</div>
<div class="logitem" style="width:calc(100% - 330px);word-wrap:break-word;overflow-wrap:break-word;white-space:pre-line;">{{ log.message }}</div>
</div>
</div>
<div id="btn_loadprevious" v-on:click="loadprevious" class="button whiteText" style="text-align:center;">Load previous >></div>
</div>
</div>
<div class="progress_frame">
<div class="progress_bar"></div>
<div class="progress_text">LOADING</div>
</div>
<div class="loginscreen">
<div class="frame border whiteText darkBackground loginframe">
<div>请输入密码</div>
<input id="input_psw" maxlength="18" type="password" class="edit_psw whiteText darkBackground" style="margin: 10px;" />
<div id="btn_login" class="disablebutton" style="text-align:center;margin:10px;">Login</div>
<div id="field_pswerror" style="color:red;font-size:12px;margin:5px;height:20px;"></div>
</div>
</div>
<div class="errorscreen">
<div class="frame border whiteText darkBackground loginframe">
<div style="color:red;margin-left:50px;margin-right:50px;">出错啦!</div>
<div id="field_error" style="color:red;"></div>
<div id="btn_refresh" class="smallbutton" style="text-align:center;margin:10px;">刷新</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
var BaseUrl = window.location.hostname
if (BaseUrl == "localhost")
BaseUrl = window.location.protocol + "//" + window.location.host + "/"
else
BaseUrl = window.location.protocol + "//" + window.location.host + "/otwebsocket/"
var MOToken = $.cookie('MOToken');
var loadedlogs = [];
var loglistVue = {};
var lastfile = "";
var firstfile = "";
$(document).ready(() => {
$("#btn_swagger").click((e) => {
window.open('swagger/index.html', '_blank');
})
$("#btn_clean_all_logs").click((e) => {
var psw = $("#input_psw").val()
var url = BaseUrl + "api/Logs/ClearLog"
$(".progress_frame").css("visibility", "visible")
$.ajax({
url: url,
headers: {
"MOToken": MOToken
},
success: e => {
loadedlogs = [];
lastfile = "";
firstfile = "";
loglistVue.logs = [];
loadlogs();
},
error: (e) => {
neterror(e)
},
complete: e => {
$(".progress_frame").css("visibility", "hidden")
}
})
})
$("#btn_clean_unloaded_logs").click((e) => {
var psw = $("#input_psw").val()
var url = BaseUrl + "api/Logs/ClearLog?afterfile=" + lastfile
$(".progress_frame").css("visibility", "visible")
$.ajax({
url: url,
headers: {
"MOToken": MOToken
},
success: e => {
loadedlogs = [];
lastfile = "";
firstfile = "";
loglistVue.logs = [];
loadlogs();
},
error: (e) => {
neterror(e)
},
complete: e => {
$(".progress_frame").css("visibility", "hidden")
}
})
})
$("#btn_apply").click((e) => {
updatelist()
})
$("#btn_changedate").click((e) => {
if ($("#field_file").text() != "") {
loadedlogs = [];
lastfile = "";
firstfile = "";
loglistVue.logs = [];
loadlogs($("#field_file").text(), 0);
}
})
$("#btn_clean").click((e) => {
$(".edit_in_or").text("");
$(".edit_ex_or").text("");
loglistVue.logs = loadedlogs
})
$("#btn_login").click((e) => {
if ($("#btn_login").hasClass("smallbutton")) {
login($("#input_psw").val());
}
})
$("#input_psw").on("keydown", (e) => {
if (e.keyCode == 13 && $("#input_psw").val().trim() != "") {
login($("#input_psw").val());
}
})
$("#input_psw").on("input propertychange", (e) => {
var text = $("#input_psw").val().trim()
if (text != "") {
$("#btn_login").removeClass("disablebutton")
$("#btn_login").addClass("smallbutton")
$("#field_pswerror").text("")
} else {
$("#btn_login").removeClass("smallbutton")
$("#btn_login").addClass("disablebutton")
}
})
loglistVue = new Vue({
el: '#list_logs',
data: {
logs: []
},
methods: {
reset: () => {
loadedlogs = [];
logs = [];
loadlogs();
},
loadnext: () => {
loadlogs(lastfile, -1)
},
loadprevious: () => {
loadlogs(firstfile, 1)
}
}
})
if (undefined == MOToken || MOToken == "") {
$(".loginscreen").css("visibility", "visible")
$("#input_psw").focus();
}
else {
loadlogs();
}
})
function updatelist() {
var newlogs = []
var filterIn = []
var filterEx = []
var filter = $(".edit_in_or").toArray();
for (var idx in filter) {
var kw = filter[idx].innerText.trim();
if (kw != "") {
filterIn.push(kw)
}
}
filter = $(".edit_ex_or").toArray();
for (var idx in filter) {
var kw = filter[idx].innerText.trim();
if (kw != "") {
filterEx.push(kw)
}
}
loadedlogs.forEach(x => {
var hit = false;
for (var idx in filterIn) {
if (x.message.includes(filterIn[idx])) {
hit = true;
break;
}
}
hit = hit || filterIn.length == 0;
if (hit) {
for (var idx in filterEx) {
if (x.message.includes(filterEx[idx])) {
hit = false;
break;
}
}
if (hit) {
newlogs.push(x)
}
}
})
loglistVue.logs = newlogs
}
function loadlogs(basefile, type) {
var url = BaseUrl + "api/Logs/GetLog"
if (undefined == type) {
type = 0
}
if (undefined == basefile) {
basefile = ""
}
url += "?basefile=" + basefile + "&type=" + type
$(".progress_frame").css("visibility", "visible")
$.ajax({
url: url,
headers: {
"MOToken": MOToken
},
success: e => {
if (e.file != "") {
if (type < 0) {
lastfile = e.file;
loadedlogs = e.logs.reverse().concat(loadedlogs)
}
if (type > 0) {
$("#field_file").text(e.file)
firstfile = e.file;
loadedlogs = loadedlogs.concat(e.logs.reverse())
}
if (type == 0) {
console.log(e)
$("#field_file").text(e.file)
firstfile = e.file;
lastfile = e.file;
loadedlogs = loadedlogs.concat(e.logs.reverse())
}
updatelist();
}
},
error: (e) => {
neterror(e)
},
complete: e => {
$(".progress_frame").css("visibility", "hidden")
}
})
}
function neterror(e) {
switch (e.status) {
case 401:
{
$(".loginscreen").css("visibility", "visible")
}
break;
default:
{
$(".errorscreen").css("visibility", "visible")
}
break;
}
}
function login(psw) {
var md5psw = $.md5(psw)
var url = BaseUrl + "api/Console/Login"
$(".progress_frame").css("visibility", "visible")
$.ajax({
url: url,
method: "POST",
data: { code: md5psw },
success: e => {
if (e.result == "ok") {
MOToken = e.token
$.cookie('MOToken', MOToken);
$(".loginscreen").css("visibility", "hidden")
loadlogs();
} else {
$("#input_psw").val("")
$("#field_pswerror").text("登录失败")
$("#btn_login").removeClass("smallbutton")
$("#btn_login").addClass("disablebutton")
}
},
error: (e) => {
neterror(e)
},
complete: e => {
$(".progress_frame").css("visibility", "hidden")
}
})
}
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));
/*! jquery.cookie v1.4.1 | MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OTWebSocket.API", "OTWebSocket.API\OTWebSocket.API.csproj", "{01E9BE3B-020E-4F53-B052-8789D4AF7AA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{47E8A6AA-F77C-4706-913C-3B1AB39F98B4}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{01E9BE3B-020E-4F53-B052-8789D4AF7AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01E9BE3B-020E-4F53-B052-8789D4AF7AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01E9BE3B-020E-4F53-B052-8789D4AF7AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01E9BE3B-020E-4F53-B052-8789D4AF7AA1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CB301D3A-86F5-4670-B049-0BFDF249C4FF}
EndGlobalSection
EndGlobal
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment