Light12306/Web12306/Servers/TrainSuggestion/TrainSuggestionHandler.cs
2015-07-30 16:01:49 +08:00

418 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace Web12306.Servers.TrainSuggestion
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Hosting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class TrainSuggestionHandler : IHttpHandler
{
#region Implementation of IHttpHandler
/// <summary>
/// 通过实现 <see cref="T:System.Web.IHttpHandler"/> 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。
/// </summary>
/// <param name="context"><see cref="T:System.Web.HttpContext"/> 对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session 和 Server的引用。</param>
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 = true;
context.Response.StatusCode = 200;
try
{
var ri = JsonConvert.DeserializeObject<RequestInfo>(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)
{
//log error
var sb = new StringBuilder();
sb.AppendLine(string.Format("请求URL {0}", context.Request.Url));
sb.AppendLine("数据:");
sb.AppendLine(data);
sb.AppendLine();
sb.AppendLine("错误信息:");
sb.AppendLine(ex.ToString());
var log = DateTime.Now.Ticks + ".log";
var path = HostingEnvironment.MapPath("~/errors/tr/");
path = PathUtility.Combine(path, log);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllText(path, sb.ToString());
}
}
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;
}
/// <summary>
/// 获取一个值,该值指示其他请求是否可以使用 <see cref="T:System.Web.IHttpHandler"/> 实例。
/// </summary>
/// <returns>
/// 如果 <see cref="T:System.Web.IHttpHandler"/> 实例可再次使用,则为 true否则为 false。
/// </returns>
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) ?? new List<AlternativeLine>()).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<AlternativeLineGroup> 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);
}
}
}
/// <summary>
/// 根据停靠站获得权重
/// </summary>
/// <param name="lineList"></param>
/// <returns></returns>
int GetLineStationEndpointWeight(List<AlternativeLine> 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<AlternativeLineGroup> 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<AlternativeLineGroup> 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<AlternativeLine> 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<AlternativeStation>();
var altTo = new List<AlternativeStation>();
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,
TrainID = info.id,
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,
TrainID = info.id,
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,
TrainID = info.id,
IsCrossDate = false,
Date = stops[indexFrom].StartTime
});
altTo.Add(new AlternativeStation
{
ArriveTime = stops[indexTo].ArriveTime,
IsEndPoint = indexTo == stops.Length - 1,
IsFromStation = false,
TrainID = info.id,
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, TrainId = s.Key.TrainID }).ToList();
return lines;
}
#endregion
}
}