using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.Helpers; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Web12306 { public class TrainSuggestion : IHttpHandler { #region Implementation of IHttpHandler /// /// 通过实现 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。 /// /// 对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session 和 Server)的引用。 public void ProcessRequest(HttpContext context) { var request = context.Request; context.Response.StatusCode = 404; if (request.UrlReferrer == null || !Regex.IsMatch(request.UrlReferrer.Host, @"^.*?\.(fishlee\.net|liebao\.cn)$")) return; //check code var r = request.QueryString["r"]; if (string.IsNullOrEmpty(r)) return; var data = request.Form["data"]; if (string.IsNullOrEmpty(data)) return; var crc = GetTextCrc(data); if (!r.EndsWith(crc + "")) { return; } var origin = request.Headers["Origin"]; var callback = request.QueryString["calllback"]; if (!string.IsNullOrEmpty(origin)) { if (Regex.IsMatch(origin, @"^(https?://.*?\.(fishlee\.net|liebao\.cn)|chrome-extension://.*)$")) context.Response.AddHeader("Access-Control-Allow-Origin", origin); else { //非法提交 return; } } else { return; } context.Response.ContentType = string.IsNullOrEmpty(callback) ? "application/json" : "application/javascript"; context.Response.BufferOutput = false; context.Response.StatusCode = 200; try { var ri = JsonConvert.DeserializeObject(data); var suggestion = GetSuggestionResponseContent(ri); if (!string.IsNullOrEmpty(callback)) { context.Response.Write(callback + "("); } context.Response.Write(suggestion); if (!string.IsNullOrEmpty(callback)) { context.Response.Write(callback + ");"); } } catch (Exception ex) { return; } } int GetTextCrc(string txt) { var crc = 0; for (var i = 0; i < txt.Length - 1; i += 2) { crc += txt[i] ^ txt[i + 1]; if (crc > 48360) crc -= 36048; } if (txt.Length % 2 == 1) crc += txt[txt.Length - 1]; if (crc > 48360) crc -= 36048; crc = crc ^ 5299; return crc; } /// /// 获取一个值,该值指示其他请求是否可以使用 实例。 /// /// /// 如果 实例可再次使用,则为 true;否则为 false。 /// public bool IsReusable { get { return true; } } #endregion #region 生成建议 static JsonSerializerSettings _camelJsonSetting = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; string GetSuggestionResponseContent(RequestInfo ri) { if (ri.CheckIllegal()) return string.Empty; return JsonConvert.SerializeObject(GetSuggestionResponseContentCore(ri), Formatting.None, _camelJsonSetting); } SuggestionResponse GetSuggestionResponseContentCore(RequestInfo ri) { //预处理 PreProcessRequestData(ri); //获得所有的可替换站点 var alllines = ri.Stops.Values.SelectMany(s => GetAlternativeLines(s, ri.Date)).Where(s => s != null).ToArray(); //对线路进行分组排序 var lineGrouped = alllines.GroupBy(s => s.LineName).Select(s => { var list = s.ToList(); var f = list[0]; return new AlternativeLineGroup(f.From.Name, f.To.Name, f.Date, list, f.IsCrossDate); }).ToList(); //过滤 FilteAlternativeLine(ri, lineGrouped); //填充数据 FillExtraInfo(ri, lineGrouped); //排序 SortLineRecommand(ri, lineGrouped); return new SuggestionResponse() { Key = "盗用可耻,鄙视无耻的各个国产IT同行!", Accepted = true, Groups = lineGrouped }; } void PreProcessRequestData(RequestInfo ri) { ri.PreprocessData(); } void FilteAlternativeLine(RequestInfo ri, List line) { for (var i = line.Count - 1; i >= 0; i--) { var train = ri.Stops[line[i].Lines[0].TrainCode]; var maxAllowElapsedTime = train.TrainInfo.elapsedTime.Value.Add(train.TrainInfo.elapsedTime.MaxAddTime); for (var j = line[i].Lines.Count - 1; j >= 0; j--) { var current = line[i].Lines[j]; //如果同时是发站和到站,则删除不推荐 if (current.From.IsOriginal && current.To.IsOriginal) line[i].Lines.RemoveAt(j); //如果时间会超过原来最大的允许值,则排除 else if (current.ElapsedTime > maxAllowElapsedTime) line[i].Lines.RemoveAt(j); } if (line[i].Lines.Count == 0) { line.RemoveAt(i); } } } /// /// 根据停靠站获得权重 /// /// /// int GetLineStationEndpointWeight(List lineList) { var point = 0; foreach (var line in lineList) { if (line.From.IsEndPoint && line.To.IsEndPoint) point += 2; else if (line.From.IsEndPoint || line.To.IsEndPoint) point += 1; } return point; } void SortLineRecommand(RequestInfo ri, List line) { line.Sort((x, y) => { var ceo1 = GetLineStationEndpointWeight(x.Lines); var ceo2 = GetLineStationEndpointWeight(y.Lines); if (ceo1 != ceo2) return ceo2 - ceo1; if (x.Lines.Count != y.Lines.Count) return y.Lines.Count - x.Lines.Count; //再看停靠时间 var ep1 = (int)x.Lines.Min(s => s.From.StopTime + s.To.StopTime).TotalSeconds; var ep2 = (int)y.Lines.Min(s => s.From.StopTime + s.To.StopTime).TotalSeconds; if (ep1 != ep2) return ep2 - ep1; //再看价格 var ap1 = x.Lines.Min(s => s.Radio); var ap2 = y.Lines.Min(s => s.Radio); if (ap1 != ap2) return ap1 - ap2; return 0; }); //最多推荐5条线路 if (line.Count > 5) line.RemoveRange(5, line.Count - 5); //再对组内排序 foreach (var current in line) { current.Lines.Sort((x, y) => { //再看停靠时间 var ep1 = (int)(x.From.StopTime + x.To.StopTime).TotalSeconds; var ep2 = (int)(y.From.StopTime + y.To.StopTime).TotalSeconds; if (ep1 != ep2) return ep2 - ep1; //再看价格 var ap1 = (int)x.ExtraPrice; var ap2 = (int)y.ExtraPrice; if (ap1 != ap2) return ap1 - ap2; return 0; }); } } void FillExtraInfo(RequestInfo ri, List line) { for (var i = line.Count - 1; i >= 0; i--) { for (var j = line[i].Lines.Count - 1; j >= 0; j--) { var train = ri.Stops[line[i].Lines[j].TrainCode]; var current = line[i].Lines[j]; var originalTime = train.TrainInfo.elapsedTime.Value; //如果时间会超过原来最大的允许值,则排除 var priceExtraRadio = ((current.ElapsedTime - originalTime).TotalSeconds / (1.0 * originalTime.TotalSeconds)); current.BasePriceSeat = Cn12306SuggestionUtility.BaseSeatCodes.First(s => train.TrainInfo.ticketMap.ContainsKey(s)); current.BasePrice = train.TrainInfo.ticketMap[current.BasePriceSeat].price / 10.0; current.ExtraPrice = Math.Round(priceExtraRadio * current.BasePrice, 2); current.Radio = (int)(priceExtraRadio * 100); } } } List GetAlternativeLines(TrainInfoItem train, DateTime requestDate) { var info = train.TrainInfo; var stops = train.StopInfos; var indexFrom = Array.FindIndex(stops, s => s.station_name == info.from.name); var indexTo = Array.FindIndex(stops, indexFrom + 1, s => s.station_name == info.to.name); if (indexFrom == -1 || indexTo == -1) return null; var altFrom = new List(); var altTo = new List(); var maxAddTime = train.TrainInfo.elapsedTime.MaxAddTime; if (indexFrom != 0) { var stopTimeFrom = stops[indexFrom].StopTime; var arriveTime = stops[indexFrom].ArriveTime; for (var i = 0; i < indexFrom; i++) { //如果isEnabled为true,则说明会停靠这个站点。不能推荐更短的。 //如果时间过久,则不做推荐 if (stops[i].isEnabled || stops[i].StartTime - arriveTime > maxAddTime) continue; var stopTime = i == 0 ? TimeSpan.Zero : stops[i].StopTime; if (i != 0) { //对于非始发站,停靠时间相同或更短,则不做推荐。 if (stopTime < stopTimeFrom && i < indexFrom - 3) continue; } //添加到推荐列表中 altFrom.Add(new AlternativeStation() { ArriveTime = stops[i].StartTime, IsEndPoint = i == 0, IsFromStation = true, Name = stops[i].station_name, StopTime = stopTime, TrainCode = info.code, Date = stops[i].StartTime, IsCrossDate = requestDate != stops[i].StartTime.Date }); } //排序 altFrom.Sort(new AlternativeStationComparer()); if (altFrom.Count > 5) altFrom.RemoveRange(5, altFrom.Count - 5); } if (indexTo != stops.Length - 1) { var stopTimeTo = stops[indexTo].StopTime; var startTime = stops[indexTo].StartTime; for (var i = stops.Length - 1; i >= indexTo; i--) { //如果isEnabled为true,则说明会停靠这个站点。不能推荐更短的。 //如果时间过久,则不做推荐 if (stops[i].isEnabled || stops[i].ArriveTime - startTime > maxAddTime) continue; var stopTime = i == 0 ? TimeSpan.Zero : stops[i].StopTime; if (i != stops.Length - 1) { //对于非终到站,停靠时间相同或更短,则不做推荐。 if (stopTime < stopTimeTo && i > indexTo + 3) continue; } //添加到推荐列表中 altTo.Add(new AlternativeStation() { ArriveTime = stops[i].ArriveTime, IsEndPoint = i == stops.Length - 1, IsFromStation = false, Name = stops[i].station_name, StopTime = stopTime, TrainCode = info.code }); } altTo.Sort(new AlternativeStationComparer()); if (altTo.Count > 5) altTo.RemoveRange(5, altTo.Count - 5); } //plus 原始线路 altFrom.Add(new AlternativeStation { ArriveTime = stops[indexFrom].StartTime, IsEndPoint = indexFrom == 0, IsFromStation = true, Name = stops[indexFrom].station_name, StopTime = indexFrom == 0 ? TimeSpan.Zero : stops[indexFrom].StopTime, TrainCode = info.code, IsOriginal = true, IsCrossDate = false, Date = stops[indexFrom].StartTime }); altTo.Add(new AlternativeStation { ArriveTime = stops[indexTo].ArriveTime, IsEndPoint = indexTo == stops.Length - 1, IsFromStation = false, Name = stops[indexTo].station_name, StopTime = indexTo == stops.Length - 1 ? TimeSpan.Zero : stops[indexFrom].StopTime, TrainCode = info.code, IsOriginal = true }); var lines = altFrom.CrossJoin(altTo).Where(s => !s.Key.IsOriginal || !s.Value.IsOriginal).Select(s => new AlternativeLine(s.Key, s.Value, s.Key.Date, s.Key.IsCrossDate) { TrainCode = s.Key.TrainCode }).ToList(); return lines; } #endregion } public static class Cn12306SuggestionUtility { public static double TimeRangeLimitRadio = double.Parse(System.Configuration.ConfigurationManager.AppSettings["12306_trainsuggestion_maxradio"]); public static readonly char[] BaseSeatCodes = "O219PM6430".ToArray(); public static TimeSpan GetTimeSpace(TimeSpan t1, TimeSpan t2) { var t = t2 - t1; if (t.Ticks < 0) t = t.Add(new TimeSpan(1, 0, 0, 0)); return t; } /// /// 生成两个序列的交叉 /// /// /// /// /// public static IEnumerable> CrossJoin(this IEnumerable t1, IEnumerable t2) { foreach (var t in t1) { foreach (var tt in t2) { yield return new KeyValuePair(t, tt); } } } public static int GetStopTime(string stopTimeStr) { var m = Regex.Match(stopTimeStr, @"(\d+)分钟"); return m.Success ? int.Parse(m.Groups[1].Value) : 0; } public static TimeSpan GetStopTimeSpan(string stopTimeStr) { var m = Regex.Match(stopTimeStr, @"(\d+)分钟"); return m.Success ? new TimeSpan(0, int.Parse(m.Groups[1].Value), 0) : TimeSpan.Zero; } public static int GetTimeValue(string time) { var m = Regex.Match(time, @"0?(\d+):0?(\d+)"); return m.Success ? int.Parse(m.Groups[1].Value) * 60 + int.Parse(m.Groups[2].Value) : 0; } public static TimeSpan GetTimeValueSpan(string time) { var m = Regex.Match(time, @"0?(\d+):0?(\d+)"); return m.Success ? new TimeSpan(int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value), 0) : TimeSpan.Zero; } public static int GetTimeSpace(string time1, string time2) { var x1 = GetTimeValue(time1); var x2 = GetTimeValue(time2); return x1 <= x2 ? x2 - x1 : x2 + 24 * 60 - x1; } } public class SuggestionResponse { public bool Accepted { get; set; } public string Key { get; set; } public List Groups { get; set; } } public class AlternativeLineGroup { public string FromText { get; set; } public string ToText { get; set; } public string Date { get; set; } public List Lines { get; set; } public bool IsCrossDate { get; set; } /// /// 创建 的新实例(AlternativeLineGroup) /// /// /// /// /// public AlternativeLineGroup(string fromText, string toText, string date, List lines, bool isCrossDate) { FromText = fromText; ToText = toText; Date = date; Lines = lines; IsCrossDate = isCrossDate; } } public class AlternativeStation { public string TrainCode { get; set; } public string Code { get; set; } public string Name { get; set; } public bool IsFromStation { get; set; } public bool IsEndPoint { get; set; } public DateTime ArriveTime { get; set; } public TimeSpan StopTime { get; set; } public bool IsOriginal { get; set; } /// /// 是否跨天 /// public bool IsCrossDate { get; set; } /// /// 日期 /// public DateTime Date { get; set; } } public class AlternativeLine { public AlternativeStation From { get; private set; } public AlternativeStation To { get; private set; } public string LineName { get; private set; } public string TrainCode { get; set; } TimeSpan? _elTimeSpan; public double ExtraPrice { get; set; } public char BasePriceSeat { get; set; } public double BasePrice { get; set; } public int Radio { get; set; } public string Date { get; set; } public bool IsCrossDate { get; set; } public TimeSpan ElapsedTime { get { if (!_elTimeSpan.HasValue) { _elTimeSpan = To.ArriveTime - From.ArriveTime; if (_elTimeSpan.Value.Ticks < 0) _elTimeSpan = _elTimeSpan.Value.Add(new TimeSpan(1, 0, 0, 0)); } return _elTimeSpan.Value; } } /// /// 创建 的新实例(AlternativeLine) /// /// /// public AlternativeLine(AlternativeStation from, AlternativeStation to, DateTime date, bool isCrossDate) { From = from; To = to; IsCrossDate = isCrossDate; Date = date.ToString("yyyy-MM-dd"); LineName = From.Name + ";" + To.Name + (isCrossDate ? ";" + Date : ""); } } public class AlternativeStationComparer : IComparer { #region Implementation of IComparer /// /// 比较两个对象并返回一个值,指示一个对象是小于、等于还是大于另一个对象。 /// /// /// 一个有符号整数,指示 的相对值,如下表所示。 值 含义 小于零 小于 。 零 等于 。 大于零 大于 。 /// /// 要比较的第一个对象。要比较的第二个对象。 public int Compare(AlternativeStation x, AlternativeStation y) { if (x.IsEndPoint ^ y.IsEndPoint) return x.IsEndPoint ? -1 : 1; return x.StopTime < y.StopTime ? -1 : 1; } #endregion } public class SuggestItemComparer : IComparer { #region Implementation of IComparer /// /// 比较两个对象并返回一个值,指示一个对象是小于、等于还是大于另一个对象。 /// /// /// 一个有符号整数,指示 的相对值,如下表所示。 值 含义 小于零 小于 。 零 等于 。 大于零 大于 。 /// /// 要比较的第一个对象。要比较的第二个对象。 public int Compare(SuggestItem x, SuggestItem y) { if (x.EndPoint ^ y.EndPoint) return x.EndPoint ? -1 : 1; return x.StopTime - y.StopTime; } #endregion } public class SuggestItem { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("code")] public string Code { get; set; } [JsonProperty("ep")] public bool EndPoint { get; set; } [JsonProperty("st")] public int StopTime { get; set; } } public class RequestInfo { public string Key { get; set; } public string From { get; set; } public string To { get; set; } public DateTime Date { get; set; } [JsonProperty("tt")] public string[] SelectedTrain { get; set; } [JsonProperty("ts")] public string[] SelectedSeats { get; set; } public Dictionary Stops { get; set; } /// /// 检测是否有非法请求,有则返回true /// /// public bool CheckIllegal() { if (Key != "stupid360") return true; if (Stops.Values.Any(s => (s.TrainInfo.code == s.TrainInfo.from.code && s.TrainInfo.to.code == s.TrainInfo.end.code))) return true; //add 2014年9月4日 - 检测非法席别 if (SelectedSeats == null) return true; if (SelectedSeats.Length > 0) { if (Stops.Values.Any(s => !SelectedSeats.Any(x => s.TrainInfo.ticketMap.ContainsKey(x[0])))) return true; } return false; } public void PreprocessData() { foreach (var trainInfoItem in Stops.Values) { trainInfoItem.PreprocessData(Date); } } } public class TrainInfoItem { [JsonProperty("info")] public TrainInfo TrainInfo { get; set; } [JsonProperty("stops")] public StopInfo[] StopInfos { get; set; } public void PreprocessData(DateTime requestDate) { TrainInfo.from.TimePoint = requestDate.Date.Add(Cn12306SuggestionUtility.GetTimeValueSpan(TrainInfo.from.time)); //查找开始站 var startIndex = Array.FindIndex(StopInfos, s => s.isEnabled); TrainInfo.from.Index = startIndex; TrainInfo.to.Index = Array.FindIndex(StopInfos, startIndex + 1, s => !s.isEnabled); if (TrainInfo.to.Index == -1) TrainInfo.to.Index = StopInfos.Length - 1; StopInfos[startIndex].StartTime = TrainInfo.from.TimePoint; if (startIndex > 0) { StopInfos[startIndex].ArriveTime = requestDate.Add(StopInfos[startIndex].ArriveTimePoint); if (StopInfos[startIndex].ArriveTime > StopInfos[startIndex].StartTime) StopInfos[startIndex].ArriveTime = StopInfos[startIndex].ArriveTime.AddDays(-1); } var tempDate = requestDate; for (var i = startIndex - 1; i >= 0; i--) { var sto = StopInfos[i]; var stn = StopInfos[i + 1]; sto.StartTime = tempDate.Add(sto.StartTimePoint); if (sto.StartTime > stn.StartTime) { tempDate = tempDate.AddDays(-1); sto.StartTime = sto.StartTime.AddDays(-1); } if (i > 0) { sto.ArriveTime = tempDate.Add(sto.ArriveTimePoint); if (sto.ArriveTime > sto.StartTime) { tempDate = tempDate.AddDays(-1); sto.ArriveTime = sto.ArriveTime.AddDays(-1); } } } tempDate = requestDate; for (var i = startIndex + 1; i < StopInfos.Length; i++) { var sto = StopInfos[i]; var stn = StopInfos[i - 1]; sto.ArriveTime = tempDate.Add(sto.ArriveTimePoint); if (sto.ArriveTime < stn.StartTime) { tempDate = tempDate.AddDays(1); sto.ArriveTime = sto.ArriveTime.AddDays(1); } if (i < StopInfos.Length - 1) { sto.StartTime = tempDate.Add(sto.StartTimePoint); if (sto.StartTime < sto.ArriveTime) { tempDate = tempDate.AddDays(1); sto.StartTime = sto.StartTime.AddDays(1); } } } var startTime = StopInfos.Select(s => s.StartTime).ToArray(); var stopTime = StopInfos.Select(s => s.StopTime).ToArray(); } } public class StopInfo { public string start_station_name { get; set; } public string arrive_time { get; set; } public string station_train_code { get; set; } public string station_name { get; set; } public string train_class_name { get; set; } public string service_type { get; set; } public string start_time { get; set; } public string stopover_time { get; set; } public string end_station_name { get; set; } public string station_no { get; set; } public bool isEnabled { get; set; } /// /// 到达时间 /// public DateTime ArriveTime { get; set; } /// /// 发车时间 /// public DateTime StartTime { get; set; } TimeSpan? _stopTimeSpan; public TimeSpan StopTime { get { if (!_stopTimeSpan.HasValue) { _stopTimeSpan = Cn12306SuggestionUtility.GetStopTimeSpan(stopover_time); } return _stopTimeSpan.Value; } } TimeSpan? _arriveTime, _startTime; public TimeSpan ArriveTimePoint { get { return (_arriveTime ?? (_arriveTime = Cn12306SuggestionUtility.GetTimeValueSpan(arrive_time))).Value; } } public TimeSpan StartTimePoint { get { return (_startTime ?? (_startTime = Cn12306SuggestionUtility.GetTimeValueSpan(start_time))).Value; } } } public class SeatTicketInfo { public string code { get; set; } public string name { get; set; } public int price { get; set; } public int count { get; set; } } public class SimpleStationInfo { public string code { get; set; } public string name { get; set; } } public class DetailStationInfo : SimpleStationInfo { public string fromStationNo { get; set; } public bool endpoint { get; set; } public string time { get; set; } /// /// 具体时间 /// public DateTime TimePoint { get; set; } /// /// 在停靠站中的索引 /// public int Index { get; set; } } public class TrainInfo { public string id { get; set; } public string code { get; set; } public int available { get; set; } public SimpleStationInfo start { get; set; } public DetailStationInfo from { get; set; } public DetailStationInfo to { get; set; } public Elapsedtime elapsedTime { get; set; } public SimpleStationInfo end { get; set; } public string ypinfo { get; set; } public string ypinfo_ex { get; set; } public string locationCode { get; set; } public int controlDay { get; set; } public string supportCard { get; set; } public string saleTime { get; set; } public string secureStr { get; set; } public DateTime? selltime { get; set; } public string date { get; set; } public string limitSellInfo { get; set; } public SeatTicketInfo[] tickets { get; set; } public Dictionary ticketMap { get; set; } } public class Elapsedtime { public string days { get; set; } public string total { get; set; } TimeSpan? _elTimeSpan; [JsonIgnore] public TimeSpan Value { get { if (_elTimeSpan == null) { _elTimeSpan = Cn12306SuggestionUtility.GetTimeValueSpan(total); } return _elTimeSpan.Value; } } TimeSpan? _maxAddTime; /// /// 最大允许加时 /// [JsonIgnore] public TimeSpan MaxAddTime { get { if (_maxAddTime == null) { _maxAddTime = new TimeSpan(0, 0, 0, (int)(Value.TotalSeconds * Cn12306SuggestionUtility.TimeRangeLimitRadio)); } return _maxAddTime.Value; } } } }