diff --git a/.gitignore b/.gitignore index a93625e..e84a839 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ ipch/ modules.json *.sdf *.opensdf - +WebService/ #OS junk files Thumbs.db *.DS_Store @@ -76,5 +76,6 @@ deploy/ /Mobile12306New/css/_dev /Mobile12306New/assets/js/debug.js wwwroot/ - +*.sln.ide wwwroot/ +App_Data/ \ No newline at end of file diff --git a/12306.sln b/12306.sln index 97353a2..757550d 100644 --- a/12306.sln +++ b/12306.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web12306", "Web12306\Web12306.csproj", "{56406C67-2B6F-4152-9EC0-E6D80E86B96D}" EndProject @@ -24,6 +24,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployTools", "DeployTools\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceFileUtility", "SourceFileUtility\SourceFileUtility.csproj", "{BB9C6747-DC69-4EF1-A94C-85D8193B7105}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainInfomationProviderService", "TrainInfomationProviderService\TrainInfomationProviderService.csproj", "{C385D043-316A-4F05-A6B9-E70BF0ED8DB6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FishLib", "FishLib", "{5EBCE730-C2C7-478B-BD0C-C4BE3BC69C06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSLib.Network.NET4", "..\..\Private\iFish\FSLib.Network\FSLib.Network.NET4.csproj", "{3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSLib_NET4", "..\..\Private\iFish\FSLib\FSLib_NET4.csproj", "{D46393A2-AEAB-4876-AEBF-84B993E66227}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSLib.Extension", "..\..\Private\iFishOs\FSLib.Extension\src\FSLib.Extension.csproj", "{6D4588AA-A0FD-4364-972C-22B0AB7938F9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "车站车次信息", "车站车次信息", "{89D11B10-8909-4682-A182-6FE4C1664B4E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "浏览器扩展", "浏览器扩展", "{05734BC4-9A31-46C8-B45E-C5FD72B0A83B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WWW", "WWW", "{5879D3F4-E7BB-4192-B4E3-9266AEB27D90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,8 +70,36 @@ Global {BB9C6747-DC69-4EF1-A94C-85D8193B7105}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB9C6747-DC69-4EF1-A94C-85D8193B7105}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB9C6747-DC69-4EF1-A94C-85D8193B7105}.Release|Any CPU.Build.0 = Release|Any CPU + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6}.Release|Any CPU.Build.0 = Release|Any CPU + {3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA}.Release|Any CPU.Build.0 = Release|Any CPU + {D46393A2-AEAB-4876-AEBF-84B993E66227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D46393A2-AEAB-4876-AEBF-84B993E66227}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D46393A2-AEAB-4876-AEBF-84B993E66227}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D46393A2-AEAB-4876-AEBF-84B993E66227}.Release|Any CPU.Build.0 = Release|Any CPU + {6D4588AA-A0FD-4364-972C-22B0AB7938F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D4588AA-A0FD-4364-972C-22B0AB7938F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D4588AA-A0FD-4364-972C-22B0AB7938F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D4588AA-A0FD-4364-972C-22B0AB7938F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {56406C67-2B6F-4152-9EC0-E6D80E86B96D} = {5879D3F4-E7BB-4192-B4E3-9266AEB27D90} + {0C7635A7-78F5-459D-BBDE-CEEC51E546B9} = {F6960416-F825-4800-8FD4-C72908A4A6CC} + {9D141603-1A9D-4EEC-82D8-C473EA436839} = {05734BC4-9A31-46C8-B45E-C5FD72B0A83B} + {4B41EA5E-B7CD-4FC6-B9E3-9AE5222695F6} = {5879D3F4-E7BB-4192-B4E3-9266AEB27D90} + {E958D106-A3EE-46AF-B3E5-E62FC96F2F94} = {F6960416-F825-4800-8FD4-C72908A4A6CC} + {BB9C6747-DC69-4EF1-A94C-85D8193B7105} = {F6960416-F825-4800-8FD4-C72908A4A6CC} + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6} = {89D11B10-8909-4682-A182-6FE4C1664B4E} + {3D34B2D8-36F9-4E16-BF0F-A8905F8FE8BA} = {5EBCE730-C2C7-478B-BD0C-C4BE3BC69C06} + {D46393A2-AEAB-4876-AEBF-84B993E66227} = {5EBCE730-C2C7-478B-BD0C-C4BE3BC69C06} + {6D4588AA-A0FD-4364-972C-22B0AB7938F9} = {5EBCE730-C2C7-478B-BD0C-C4BE3BC69C06} + EndGlobalSection EndGlobal diff --git a/TrainInfomationProviderService/App.config b/TrainInfomationProviderService/App.config new file mode 100644 index 0000000..dab3f38 --- /dev/null +++ b/TrainInfomationProviderService/App.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/TrainInfomationProviderService/InformationUpdaterService.Designer.cs b/TrainInfomationProviderService/InformationUpdaterService.Designer.cs new file mode 100644 index 0000000..9590576 --- /dev/null +++ b/TrainInfomationProviderService/InformationUpdaterService.Designer.cs @@ -0,0 +1,37 @@ +namespace TrainInfomationProviderService +{ + partial class InformationUpdaterService + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/TrainInfomationProviderService/InformationUpdaterService.cs b/TrainInfomationProviderService/InformationUpdaterService.cs new file mode 100644 index 0000000..9afd237 --- /dev/null +++ b/TrainInfomationProviderService/InformationUpdaterService.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace TrainInfomationProviderService +{ + public partial class InformationUpdaterService : ServiceBase + { + public InformationUpdaterService() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + } + + protected override void OnStop() + { + } + } +} diff --git a/TrainInfomationProviderService/Program.cs b/TrainInfomationProviderService/Program.cs new file mode 100644 index 0000000..6a12574 --- /dev/null +++ b/TrainInfomationProviderService/Program.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; +using System.Web.Hosting; +using System.Windows.Forms; +using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo; +using TrainInfomationProviderService.StationInfo.Entities; +using TrainInfomationProviderService.TrainInfo; +using TrainInfomationProviderService.TrainInfo.Entities; + +namespace TrainInfomationProviderService +{ + internal static class Program + { + /// + /// 应用程序的主入口点。 + /// + private static void Main(string[] args) + { + if (args.Length > 0 && args[0].IsIgnoreCaseEqualTo("--service")) + { + var servicesToRun = new ServiceBase[] + { + new InformationUpdaterService() + }; + ServiceBase.Run(servicesToRun); + } + else + { + Trace.Listeners.Add(new ConsoleTraceListener()); + + StationManager.Instance.Init(); + TrainInfoManager.Instance.Init(); + //搜索? + var searchProvider = new TrainInfoSearchProvider(TrainInfoManager.Instance.Storage); + //runtime mode + MessageBox.Show(StationManager.Instance.Storage.Version.ToString()); + } + } + } +} diff --git a/TrainInfomationProviderService/Properties/AssemblyInfo.cs b/TrainInfomationProviderService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..17f846f --- /dev/null +++ b/TrainInfomationProviderService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("TrainInfomationProviderService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TrainInfomationProviderService")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("dfc810f3-cc44-4ce0-8a92-8a616319071f")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TrainInfomationProviderService/RunTimeContext.cs b/TrainInfomationProviderService/RunTimeContext.cs new file mode 100644 index 0000000..973a74b --- /dev/null +++ b/TrainInfomationProviderService/RunTimeContext.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Hosting; + +namespace TrainInfomationProviderService +{ + class RunTimeContext + { + public static string ServiceRoot { get; private set; } + + public static string DataStorageRoot { get; private set; } + + /// + /// 是否是web环境 + /// + public static bool IsWeb { get; private set; } + + + static RunTimeContext() + { + Trace.TraceInformation("[CONTEXT] 正在初始化上下文"); + + IsWeb = System.Web.Hosting.HostingEnvironment.IsHosted; + Trace.TraceInformation("[CONTEXT] WEB模式:{0}", IsWeb); + + if (IsWeb) + { + ServiceRoot = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath; + DataStorageRoot = HostingEnvironment.MapPath(ConfigurationManager.AppSettings["dataStoragePath"]); + } + else + { + ServiceRoot = System.Reflection.Assembly.GetEntryAssembly().GetLocation(); + DataStorageRoot = ConfigurationManager.AppSettings["dataStoragePath"]; + if (!Path.IsPathRooted(DataStorageRoot)) + DataStorageRoot = PathUtility.Combine(ServiceRoot, DataStorageRoot); + } + Directory.CreateDirectory(DataStorageRoot); + + Trace.TraceInformation("[CONTEXT] 服务根目录:{0}", ServiceRoot); + Trace.TraceInformation("[CONTEXT] 数据根目录:{0}", DataStorageRoot); + } + } +} diff --git a/TrainInfomationProviderService/StationInfo/Entities/BasicStation.cs b/TrainInfomationProviderService/StationInfo/Entities/BasicStation.cs new file mode 100644 index 0000000..2f9e0b0 --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/Entities/BasicStation.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.StationInfo.Entities +{ + public class BasicStation + { + /// + /// 名称 + /// + [JsonProperty("n")] + public string Name { get; set; } + + /// + /// 车站编号 + /// + [JsonProperty("c")] + public string Code { get; set; } + } +} diff --git a/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs b/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs new file mode 100644 index 0000000..44de369 --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.StationInfo.Entities +{ + public class SameStationCollection : List> + { + private SameStationCollection() + { + + } + + public HashSet FindSameStations(string code) + { + return this.FirstOrDefault(s => s.Contains(code)); + } + + private static SameStationCollection _instance; + private static readonly object _lockObject = new object(); + + /// + /// 获得实例对象 + /// + public static SameStationCollection Instance + { + get + { + if (_instance == null) + { + lock (_lockObject) + { + if (_instance == null) + { + var file = Path.Combine(RunTimeContext.DataStorageRoot, "samestation.json"); + if (File.Exists(file)) + { + _instance = JsonConvert.DeserializeObject(File.ReadAllText(file)); + } + else + { + _instance = new SameStationCollection(); + } + } + } + } + + return _instance; + } + } + } +} diff --git a/TrainInfomationProviderService/StationInfo/Entities/StationDetailInfo.cs b/TrainInfomationProviderService/StationInfo/Entities/StationDetailInfo.cs new file mode 100644 index 0000000..37de391 --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/Entities/StationDetailInfo.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.StationInfo.Entities +{ + public class StationDetailInfo : BasicStation + { + [JsonProperty("p")] + public string PyFull { get; set; } + + [JsonProperty("y")] + public string Py { get; set; } + + [JsonProperty("s")] + public int Sort { get; set; } + } +} diff --git a/TrainInfomationProviderService/StationInfo/Entities/StationStorage.cs b/TrainInfomationProviderService/StationInfo/Entities/StationStorage.cs new file mode 100644 index 0000000..831c4f0 --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/Entities/StationStorage.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.StationInfo.Entities +{ + public class StationStorage + { + /// + /// 创建 的新实例(StationStorage) + /// + [JsonConstructor] + protected StationStorage() + { + Stations = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public StationStorage(int version, IEnumerable detail) + { + Version = version; + Stations = detail.ToDictionary(s => s.Code, s => s, StringComparer.OrdinalIgnoreCase); + StationNameMap = detail.ToDictionary(s => s.Name, s => s, StringComparer.OrdinalIgnoreCase); + } + + [JsonProperty("v")] + public int Version { get; set; } + + [JsonProperty("s")] + public Dictionary Stations { get; set; } + + [JsonIgnore] + public Dictionary StationNameMap { get; private set; } + + + [OnDeserialized] + void DeserializeCallback(StreamingContext context) + { + Init(); + } + + /// + /// 初始化 + /// + protected void Init() + { + StationNameMap = Stations.ToDictionary(s => s.Value.Name, s => s.Value, StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/TrainInfomationProviderService/StationInfo/StationManager.cs b/TrainInfomationProviderService/StationInfo/StationManager.cs new file mode 100644 index 0000000..74fa008 --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/StationManager.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Caching; +using System.Web.Hosting; +using FSLib.Network.Http; +using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo.Entities; + +namespace TrainInfomationProviderService.StationInfo +{ + /// + /// + public class StationManager + { + private static readonly object _lockObject = new object(); + private static StationManager _stationManager; + + public static StationManager Instance + { + get + { + if (_stationManager == null) + { + lock (_lockObject) + { + if (_stationManager == null) + { + _stationManager = new StationManager(); + } + } + } + + return _stationManager; + } + } + + /// + /// 创建 的新实例(StationManager) + /// + public StationManager() + { + Trace.TraceInformation("[STATION] 车站管理器对象已新建"); + } + + + private string _dataFilePath; + + /// + /// 仓库 + /// + public StationStorage Storage + { + get + { + if (RunTimeContext.IsWeb) + { + return HostingEnvironment.Cache["_12306stations"] as StationStorage; + } + return _storage; + } + } + + public void Init() + { + Trace.TraceInformation("[STATION] 正在初始化"); + + _dataFilePath = PathUtility.Combine(RunTimeContext.DataStorageRoot, "stations.json"); + Trace.TraceInformation("[STATION] 目标缓存文件路径:{0}", _dataFilePath); + + if (RunTimeContext.IsWeb) + { + Trace.TraceInformation("[STATION] WEB模式"); + + InitWeb(); + } + else + { + Trace.TraceInformation("[STATION] 服务模式"); + InitService(); + } + } + + #region web环境 + + /// + /// web环境初始化 + /// + void InitWeb() + { + Trace.TraceInformation("[STATION] 初始化WEB模式"); + LoadCache(); + } + + void LoadCache() + { + Trace.TraceInformation("[STATION] 正在检查缓存刷新"); + _storage = null; + if (File.Exists(_dataFilePath)) + { + Trace.TraceInformation("[STATION] 正在加载缓存"); + _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + Trace.TraceInformation("[STATION] 缓存加载完成"); + } + + if (Storage != null) + { + Trace.TraceInformation("[STATION] 正在加入HttpRuntime缓存"); + HostingEnvironment.Cache.Add("_12306stations", Storage, new CacheDependency(_dataFilePath), + Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, + (_1, _2, _3) => + { + Trace.TraceInformation("[STATION] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); + LoadCache(); + }); + Trace.TraceInformation("[STATION] HttpRuntime缓存加入完成"); + } + } + + + #endregion + + #region 服务模式 + + private Timer _checkStationTimer; + private StationStorage _storage; + + /// + /// 服务模式初始化 + /// + void InitService() + { + if (_dataFilePath.AsFileInfo().Exists) + { + Trace.TraceInformation("[STATION] 正在加载文件缓存数据"); + _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + Trace.TraceInformation("[STATION] 文件缓存数据加载完成"); + } + var lastestVersion = GetLatestVersionFromWeb(); + var needUpdate = _storage == null || _storage.Version < lastestVersion; + Trace.TraceInformation("[STATION] 最新版本:{0}", lastestVersion); + + if (needUpdate) + { + Trace.TraceInformation("[STATION] 正在刷新缓存"); + RefreshStationInfo(lastestVersion); + Trace.TraceInformation("[STATION] 缓存数据已刷新"); + } + _checkStationTimer = new Timer(_ => CheckStationVersion(), null, new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); + } + + void RefreshStationInfo(int version) + { + Trace.TraceInformation("[STATION] 正在获得最新车站信息"); + var html = new HttpClient().Create(HttpMethod.Get, "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js", null, null, "").Send(); + if (!html.IsValid()) + { + Trace.TraceError("[STATION] 车站信息获得失败。错误:{0}", html.Exception); + + return; + } + Trace.TraceInformation("[STATION] 正在分析车站信息"); + + var mcs = Regex.Matches(html.Result, @"@([^\|]+)\|([^\|]+)\|([^\|]+)\|([^\|]+)\|([^\|]+)\|(\d+)"); + var result = new List(mcs.Count); + foreach (Match mc in mcs) + { + var s = new StationDetailInfo() + { + Code = mc.GetGroupValue(3), + Name = mc.GetGroupValue(2), + Py = mc.GetGroupValue(5), + PyFull = mc.GetGroupValue(4), + Sort = mc.GetGroupValue(6).ToInt32() + }; + result.Add(s); + } + _storage = new StationStorage(version, result); + Trace.TraceInformation("[STATION] 车站信息分析完成,{0} 车站。正在缓存车站信息", result.Count); + + //save + File.WriteAllText(_dataFilePath, JsonConvert.SerializeObject(Storage)); + Trace.TraceInformation("[STATION] 车站信息缓存完成。目标文件 {0}", _dataFilePath); + } + + void CheckStationVersion() + { + var lastestVersion = GetLatestVersionFromWeb(); + if (Storage == null || Storage.Version < lastestVersion) + { + RefreshStationInfo(lastestVersion); + } + _checkStationTimer.Change(new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); + } + + /// + /// 从网络加载最新的版本号 + /// + /// + public int GetLatestVersionFromWeb() + { + var ctx = new HttpClient().Create(HttpMethod.Get, "https://kyfw.12306.cn/otn/leftTicket/init", "https://kyfw.12306.cn/otn/leftTicket/init").Send(); + if (!ctx.IsValid()) + return 0; + + var version = (int)(Regex.Match(ctx.Result, @"station_name\.js\?station_version=([\d\.]+)").GetGroupValue(1).ToSingle() * 100000); + + return version; + } + + + #endregion + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs b/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs new file mode 100644 index 0000000..ff8dbb8 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class IndexStorage + { + /// + /// 创建 的新实例(IndexStorage) + /// + public IndexStorage() + { + DateIndices = new HashSet(StringComparer.OrdinalIgnoreCase); + TrainInfoStorages = new Dictionary(); + } + + public int Version { get; set; } + + public HashSet DateIndices { get; set; } + + [JsonIgnore] + public Dictionary TrainInfoStorages { get; set; } + + [OnDeserialized] + void Init(StreamingContext ctx) + { + var dataLoader = new WebDataProvider(); + TrainInfoStorages.Clear(); + + var baseDate = DateTime.Now.Date; + DateIndices = DateIndices.Where(s => s.ToDateTime() >= baseDate).ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var dateIndex in DateIndices.ToArray()) + { + var data = dataLoader.LoadCache(dateIndex); + if (data == null) + DateIndices.Remove(dateIndex); + else TrainInfoStorages.Add(dateIndex, data); + } + } + + /// + /// 移除过时的信息 + /// + internal void RemoveOutdateStorage() + { + var baseDate = DateTime.Now.Date; + var outdates = DateIndices.Where(s => s.ToDateTime() < baseDate).ToArray(); + + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在检查过期数据"); + foreach (var outdate in outdates) + { + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据 {0}", outdate); + DateIndices.Remove(outdate); + if (TrainInfoStorages.ContainsKey(outdate)) + TrainInfoStorages.Remove(outdate); + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 完成移除过期数据 {0}", outdate); + } + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据"); + } + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/Train.cs b/TrainInfomationProviderService/TrainInfo/Entities/Train.cs new file mode 100644 index 0000000..0402bab --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/Train.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo; +using TrainInfomationProviderService.StationInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class Train : IEquatable + { + /// + /// ID + /// + [JsonProperty("i")] + public string Id { get; set; } + + /// + /// 列车编号 + /// + [JsonProperty("c")] + public string Code { get; set; } + + /// + /// 发站 + /// + [JsonProperty("f")] + public string From { get; set; } + + /// + /// 到站 + /// + [JsonProperty("t")] + public string To { get; set; } + + [JsonProperty("s")] + public List TrainStops { get; set; } + + #region Implementation of IEquatable + + /// + /// 指示当前对象是否等于同一类型的另一个对象。 + /// + /// + /// 如果当前对象等于 参数,则为 true;否则为 false。 + /// + /// 与此对象进行比较的对象。 + public bool Equals(Train other) + { + return Key == other.Key; + } + + private string _key; + private string Key + { + get + { + if (string.IsNullOrEmpty(_key)) + { + _key = Id + Code + From + To; + } + + return _key; + } + } + + /// + /// 用作特定类型的哈希函数。 + /// + /// + /// 当前 的哈希代码。 + /// + public override int GetHashCode() + { + return Key.GetHashCode(); + } + + /// + /// 确定指定的 是否等于当前的 。 + /// + /// + /// 如果指定的对象等于当前对象,则为 true;否则为 false。 + /// + /// 要与当前对象进行比较的对象。 + public override bool Equals(object obj) + { + if (obj == null || !(obj is Train)) + return false; + + return Key == (obj as Train).Key; + } + + #endregion + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainInfoStorage.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainInfoStorage.cs new file mode 100644 index 0000000..9690f35 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainInfoStorage.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainInfoStorage + { + + [JsonProperty("l")] + public List Trains { get; set; } + + [JsonIgnore] + public Dictionary TrainsCodeMap { get; private set; } + + [JsonIgnore] + public Dictionary TrainsIdMap { get; private set; } + + /// + /// 车站-发车车次信息 + /// + [JsonIgnore] + public Dictionary> StationLeftTrainData { get; private set; } + + /// + /// 车站-发车车次信息 + /// + [JsonIgnore] + public Dictionary> StationArriveTrainData { get; private set; } + + /// + /// 站点路由表 + /// + [JsonIgnore] + public Dictionary> StationRouteMap { get; private set; } + + /// + /// 创建 的新实例(TrainInfoStorage) + /// + public TrainInfoStorage(IEnumerable trains) + { + Trains = trains.ToList(); + Init(); + } + + [JsonConstructor] + protected TrainInfoStorage() + { + } + + [OnDeserialized] + void DeserializeCallback(StreamingContext context) + { + Init(); + } + + /// + /// 初始化 + /// + public void Init() + { + TrainsCodeMap = Trains.GroupBy(s => s.Code).ToDictionary(s => s.Key, s => + { + var arr = s.ToArray(); + //if (arr.Length > 1) + //{ + // Trace.TraceWarning("[TRAIN_INFO_STORAGE] 警告:车次编号 {0} 出现重复数据,请查证,重复次数:{1}", s.Key, arr.Length - 1); + //} + return arr.ToArray(); + }, StringComparer.OrdinalIgnoreCase); + TrainsIdMap = Trains.GroupBy(s => s.Id).ToDictionary(s => s.Key, s => + { + var arr = s.ToArray(); + //if (arr.Length > 1) + //{ + // Trace.TraceWarning("[TRAIN_INFO_STORAGE] 警告:车次ID {0} 出现重复数据,请查证,重复次数:{1}", s.Key, arr.Length - 1); + //} + return arr; + }, StringComparer.OrdinalIgnoreCase); + } + + /// + /// 初始化车站->车次信息 + /// + public void InitStationTrainData() + { + StationLeftTrainData = new Dictionary>(); + StationArriveTrainData = new Dictionary>(); + foreach (var train in Trains) + { + for (int i = 0; i < train.TrainStops.Count; i++) + { + var stop = train.TrainStops[i]; + + if (i > 0) + StationArriveTrainData.GetValue(stop.Code, _ => new List()).Add(train); + if (i < train.TrainStops.Count - 1) + StationLeftTrainData.GetValue(stop.Code, _ => new List()).Add(train); + } + } + } + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs new file mode 100644 index 0000000..d9cb508 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainStop + { + [JsonProperty("c")] + public string Code { get; set; } + + [JsonProperty("i")] + public int Index { get; set; } + + [JsonProperty("a")] + public TimeSpan? Arrive { get; set; } + + [JsonProperty("l")] + public TimeSpan? Left { get; set; } + + [JsonProperty("d")] + public int Days { get; set; } + + } +} diff --git a/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs b/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs new file mode 100644 index 0000000..e0defa7 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Caching; +using System.Web.Hosting; +using FSLib.Network.Http; +using Newtonsoft.Json; +using TrainInfomationProviderService.TrainInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo +{ + public class TrainInfoManager + { + private static readonly object _lockObject = new object(); + private static TrainInfoManager _stationManager; + + public static TrainInfoManager Instance + { + get + { + if (_stationManager == null) + { + lock (_lockObject) + { + if (_stationManager == null) + { + _stationManager = new TrainInfoManager(); + } + } + } + + return _stationManager; + } + } + + /// + /// 创建 的新实例(StationManager) + /// + protected TrainInfoManager() + { + Trace.TraceInformation("[TAININFOMANGER] 车站管理器对象已新建"); + } + + private string _dataFilePath; + + /// + /// 仓库 + /// + public IndexStorage Storage + { + get + { + if (RunTimeContext.IsWeb) + { + return HostingEnvironment.Cache["_12306trains"] as IndexStorage; + } + return _storage; + } + } + + public void Init() + { + Trace.TraceInformation("[TAININFOMANGER] 正在初始化"); + + _dataFilePath = PathUtility.Combine(RunTimeContext.DataStorageRoot, "trains.json"); + Trace.TraceInformation("[TAININFOMANGER] 目标缓存文件路径:{0}", _dataFilePath); + + if (RunTimeContext.IsWeb) + { + Trace.TraceInformation("[TAININFOMANGER] WEB模式"); + + InitWeb(); + } + else + { + Trace.TraceInformation("[TAININFOMANGER] 服务模式"); + InitService(); + } + } + + + #region web环境 + + /// + /// web环境初始化 + /// + void InitWeb() + { + Trace.TraceInformation("[TAININFOMANGER] 初始化WEB模式"); + LoadCache(); + } + + void LoadCache() + { + Trace.TraceInformation("[TAININFOMANGER] 正在检查缓存刷新"); + _storage = null; + if (File.Exists(_dataFilePath)) + { + Trace.TraceInformation("[TAININFOMANGER] 正在加载缓存"); + _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + Trace.TraceInformation("[TAININFOMANGER] 缓存加载完成"); + } + + if (Storage != null) + { + Trace.TraceInformation("[TAININFOMANGER] 正在加入HttpRuntime缓存"); + HostingEnvironment.Cache.Add("_12306trains", Storage, new CacheDependency(_dataFilePath), + Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, + (_1, _2, _3) => + { + Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); + LoadCache(); + }); + Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存加入完成"); + } + } + + + #endregion + + #region 服务模式 + + private Timer _checkStationTimer; + private IndexStorage _storage; + +#if DEBUG + private bool _ininitalized = true; +#else + private bool _ininitalized = false; +#endif + + /// + /// 服务模式初始化 + /// + void InitService() + { + if (_dataFilePath.AsFileInfo().Exists) + { + Trace.TraceInformation("[TAININFOMANGER] 正在加载文件缓存数据"); + _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + Trace.TraceInformation("[TAININFOMANGER] 文件缓存数据加载完成"); + } + else + { + _storage = new IndexStorage(); + } + var lastestVersion = GetLatestVersionFromWeb(); + var needUpdate = _storage == null || _storage.Version < lastestVersion; + Trace.TraceInformation("[TAININFOMANGER] 最新版本:{0}", lastestVersion); + + if (needUpdate || !_ininitalized) + { + Trace.TraceInformation("[TAININFOMANGER] 正在刷新缓存"); + RefreshTrainInfo(lastestVersion); + Trace.TraceInformation("[TAININFOMANGER] 缓存数据已刷新"); + } + _ininitalized = true; + _checkStationTimer = new Timer(_ => CheckTrainVersion(), null, new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); + } + + void RefreshTrainInfo(int version) + { + Trace.TraceInformation("[TAININFOMANGER] 正在刷新车次信息"); + var loader = new WebDataProvider(); + try + { + Storage.Version = version; + loader.LoadTrainInfo(Storage); + } + catch (Exception ex) + { + Trace.TraceError("TAININFOMANGER] 车次信息更新出错:{0}", ex); + } + + //Storage = new TrainInfoStorage(); + Trace.TraceInformation("[TAININFOMANGER] 车次信息分析完成,车站。正在缓存车站信息"); + + //save + File.WriteAllText(_dataFilePath, JsonConvert.SerializeObject(Storage)); + Trace.TraceInformation("[TAININFOMANGER] 车站信息缓存完成。目标文件 {0}", _dataFilePath); + } + + void CheckTrainVersion() + { + var lastestVersion = GetLatestVersionFromWeb(); + if (Storage == null || Storage.Version < lastestVersion) + { + RefreshTrainInfo(lastestVersion); + } + _checkStationTimer.Change(new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); + } + + /// + /// 从网络加载最新的版本号 + /// + /// + public int GetLatestVersionFromWeb() + { + var ctx = new HttpClient().Create(HttpMethod.Get, "https://kyfw.12306.cn/otn/queryTrainInfo/init", "https://kyfw.12306.cn/otn/queryTrainInfo/init").Send(); + if (!ctx.IsValid()) + return 0; + + var version = (int)(Regex.Match(ctx.Result, @"train_list\.js\?scriptVersion=([\d\.]+)").GetGroupValue(1).ToSingle() * 100000); + + return version; + } + + + #endregion + + } +} diff --git a/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs b/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs new file mode 100644 index 0000000..5cd88f5 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TrainInfomationProviderService.TrainInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo +{ + public class TrainInfoSearchProvider + { + public IndexStorage Storage { get; private set; } + + public TrainInfoSearchProvider(IndexStorage storage) + { + Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 正在创建车站-车次信息索引对象"); + Storage = storage; + + foreach (var trainInfoStorage in storage.TrainInfoStorages) + { + Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 正在初始化 {0} 车站-车次信息索引对象", trainInfoStorage.Key); + trainInfoStorage.Value.InitStationTrainData(); + } + Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 完成创建车站-车次信息索引对象"); + } + } +} diff --git a/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs b/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs new file mode 100644 index 0000000..f7581c6 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using FSLib.Network.Http; +using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo; +using TrainInfomationProviderService.TrainInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo +{ + class WebDataProvider + { + class TrainListInfoItem + { + public string station_train_code { get; set; } + + public string train_no { get; set; } + } + + class TrainListData : Dictionary>> + { + + } + + + public class TrainStopWebInfo + { + public string arrive_time { get; set; } + public string station_name { get; set; } + public string start_time { get; set; } + public string stopover_time { get; set; } + public string station_no { get; set; } + public bool isEnabled { get; set; } + + public TrainStop ToTrainStop(int index, ref TimeSpan? prevTimeSpan, ref int days) + { + var st = new TrainStop(); + + st.Left = start_time[0] == '-' ? null : start_time.ToTimeSpanNullable(); + st.Arrive = arrive_time[0] == '-' ? null : arrive_time.ToTimeSpanNullable(); + if (days > 0) + { + if (st.Left != null) + st.Left = st.Left.Value.AddDays(days); + if (st.Arrive != null) + st.Arrive = st.Arrive.Value.AddDays(days); + } + if (prevTimeSpan != null && st.Arrive != null) + { + if (st.Arrive.Value < prevTimeSpan.Value) + { + st.Arrive = st.Arrive.Value.Add(new TimeSpan(24, 0, 0)); + if (st.Left != null) + st.Left = st.Left.Value.Add(new TimeSpan(24, 0, 0)); + days++; + } + prevTimeSpan = st.Arrive; + } + st.Days = days; + if (prevTimeSpan != null && st.Left != null) + { + if (st.Left.Value < prevTimeSpan.Value) + { + st.Left = st.Left.Value.Add(new TimeSpan(24, 0, 0)); + days++; + } + } + prevTimeSpan = st.Left; + + st.Index = index; + st.Code = StationManager.Instance.Storage.StationNameMap.GetValue(station_name).SelectValue(s => s.Code) ?? ""; + + return st; + } + } + + private string _dataFolder; + private string _sourceArchiveFolder; + + /// + /// 创建 的新实例(WebDataProvider) + /// + public WebDataProvider() + { + _dataFolder = PathUtility.Combine(RunTimeContext.DataStorageRoot, "trains"); + _sourceArchiveFolder = PathUtility.Combine(_dataFolder, "sources"); + Directory.CreateDirectory(_dataFolder); + Directory.CreateDirectory(_sourceArchiveFolder); + } + + + public void LoadTrainInfo(IndexStorage indexStorage) + { + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 正在获得最新车次信息"); + var html = new HttpClient().Create(HttpMethod.Get, "https://kyfw.12306.cn/otn/resources/js/query/train_list.js?scriptVersion=1.1051", null, null, "").Send(); + if (!html.IsValid()) + { + Trace.TraceError("[TRAIN_DATA_WEB_PROVIDER] 车次信息获得失败。错误:{0}", html.Exception); + + return; + } + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 正在缓存原始来源"); + File.WriteAllText(PathUtility.Combine(_sourceArchiveFolder, "train_list_" + indexStorage.Version + ".js"), html.Result); + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 原始来源缓存成功"); + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 正在分析车次信息"); + + //移除非JSON部分 + var fromIndex = html.Result.IndexOf('{'); + var endIndex = html.Result.LastIndexOf('}'); + var body = html.Result.Substring(fromIndex, endIndex - fromIndex + 1); + + var data = JsonConvert.DeserializeObject(body); + var station = StationManager.Instance.Storage.StationNameMap; + + foreach (var date in data.Keys) + { + var dateSt = data[date]; + var curStorage = indexStorage.TrainInfoStorages.GetValue(date); + var curTrainList = curStorage.SelectValue(s => s.Trains); + //所有的车次 + var alltrains = dateSt.Values.SelectMany(s => s).Select(s => + { + var i = new Train(); + var m = Regex.Match(s.station_train_code, @"([A-Z]\d+)\((.*?)\-(.*?)\)"); + i.Code = m.GetGroupValue(1); + i.From = station.GetValue(m.Groups[2].Value.Trim()).SelectValue(x => x.Code); + i.To = station.GetValue(m.Groups[3].Value.Trim()).SelectValue(x => x.Code); + i.Id = s.train_no; + + return !string.IsNullOrEmpty(i.From) && !string.IsNullOrEmpty(i.To) ? i : null; + }).Except(curTrainList ?? new List()).ExceptNull().ToList(); + //加载车次停靠站信息 + var index = 0; + foreach (var alltrain in alltrains) + { + LoadTrainStopInfo(++index, alltrains.Count, date, alltrain); + } + if (curStorage == null) + { + curStorage = new TrainInfoStorage(alltrains); + indexStorage.TrainInfoStorages.Add(date, curStorage); + } + else + { + curTrainList.AddRange(alltrains); + curStorage.Init(); + } + + //缓存 + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 开始缓存车次数据 {0}", date); + var filepath = PathUtility.Combine(_dataFolder, date + ".json"); + File.WriteAllText(filepath, JsonConvert.SerializeObject(curStorage)); + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 车次数据缓存成功!"); + + indexStorage.DateIndices.SafeAdd(date); + indexStorage.TrainInfoStorages.AddOrUpdate(date, curStorage); + + GC.Collect(); + } + + + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 车次信息分析完成,已加载{0}日车次信息", indexStorage.DateIndices.Count); + + return; + } + + public TrainInfoStorage LoadCache(string date) + { + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 正在加载文件缓存 {0}", date); + var file = PathUtility.Combine(_dataFolder, date + ".json"); + if (File.Exists(file)) + { + return JsonConvert.DeserializeObject(File.ReadAllText(file)); + } + + return null; + } + + internal void LoadTrainStopInfo(int index, int totalcount, string date, Train train) + { + Trace.TraceInformation("[TRAIN_STOP_INFO_LOAER] [{2:#0}/{3:#0}] 正在加载车次 {1}/{0} 信息", train.Code, date, index, totalcount); + + var tryCount = 0; + while (tryCount++ < 50) + { + var ctx = new HttpClient().Create( + HttpMethod.Get, + string.Format("https://kyfw.12306.cn/otn/czxx/queryByTrainNo?train_no={0}&from_station_telecode={1}&to_station_telecode={2}&depart_date={3}", train.Id, train.From, train.To, date), + null, + null, + new + { + data = new { data = new List() } + }).Send(); + if (!ctx.IsValid() || ctx.Result.data == null) + { + Trace.TraceWarning("[TRAIN_STOP_INFO_LOAER] 第 {0} 次加载失败!", tryCount); + Thread.Sleep(1000); + continue; + } + + TimeSpan? prevTimeSpan = null; + int days = 0; + train.TrainStops = ctx.Result.data.data.Select((obj, idx) => obj.ToTrainStop(idx + 1, ref prevTimeSpan, ref days)).ToList(); + Thread.Sleep(200); + break; + } + + Trace.TraceInformation("[TRAIN_STOP_INFO_LOAER] 车次 {1}/{0} 信息加载过程结束", train.Code, date); + } + + } +} diff --git a/TrainInfomationProviderService/TrainInfomationProviderService.csproj b/TrainInfomationProviderService/TrainInfomationProviderService.csproj new file mode 100644 index 0000000..8db9aca --- /dev/null +++ b/TrainInfomationProviderService/TrainInfomationProviderService.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + {C385D043-316A-4F05-A6B9-E70BF0ED8DB6} + Exe + Properties + TrainInfomationProviderService + TrainInfomationProviderService + v4.5 + 512 + ..\ + true + + + AnyCPU + true + full + false + ..\WebService\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\WebService\ + TRACE + prompt + 4 + + + + + + + False + ..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + Component + + + InformationUpdaterService.cs + + + + + + + + + + + + + + + + + + + + + + + + {6d4588aa-a0fd-4364-972c-22b0ab7938f9} + FSLib.Extension + + + {3d34b2d8-36f9-4e16-bf0f-a8905f8fe8ba} + FSLib.Network.NET4 + + + {d46393a2-aeab-4876-aebf-84b993e66227} + FSLib_NET4 + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。启用“NuGet 程序包还原”可下载这些程序包。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + \ No newline at end of file diff --git a/TrainInfomationProviderService/packages.config b/TrainInfomationProviderService/packages.config new file mode 100644 index 0000000..5cb1720 --- /dev/null +++ b/TrainInfomationProviderService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Web12306/TrainSuggestion.cs b/Web12306/TrainSuggestion.cs index 3feadd0..140e7f2 100644 --- a/Web12306/TrainSuggestion.cs +++ b/Web12306/TrainSuggestion.cs @@ -776,8 +776,8 @@ namespace Web12306 } } - var startTime = StopInfos.Select(s => s.StartTime).ToArray(); - var stopTime = StopInfos.Select(s => s.StopTime).ToArray(); + //var startTime = StopInfos.Select(s => s.StartTime).ToArray(); + //var stopTime = StopInfos.Select(s => s.StopTime).ToArray(); } } diff --git a/Web12306/css/base.css b/Web12306/css/base.css index 17cd3e6..bd8f823 100644 --- a/Web12306/css/base.css +++ b/Web12306/css/base.css @@ -52,7 +52,7 @@ label { body { line-height: 1; - background: url(../images/page-bg.png) repeat-x top left; + background-color: #f8f8f8; font: 14px Helvetica, Arial, "Microsoft Yahei", SimSun, sans-serif; } @@ -181,7 +181,7 @@ a:active { display: block; color: #fff; cursor: pointer; - } + } .header-bar .user-nav li.selected > a { color: #f17206; diff --git a/Web12306/index.html b/Web12306/index.html index 6f3353c..b3c5f9d 100644 --- a/Web12306/index.html +++ b/Web12306/index.html @@ -8,6 +8,7 @@
+