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 @@