TrainQuery.API 2.0.0

dotnet add package TrainQuery.API --version 2.0.0
                    
NuGet\Install-Package TrainQuery.API -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TrainQuery.API" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TrainQuery.API" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="TrainQuery.API" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TrainQuery.API --version 2.0.0
                    
#r "nuget: TrainQuery.API, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TrainQuery.API@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TrainQuery.API&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TrainQuery.API&version=2.0.0
                    
Install as a Cake Tool

TrainQuery.API - 12306火车票查询类库

📦 NuGet包信息

  • 包名称: TrainQuery.API
  • 当前版本: 2.0.0
  • 作者: 孟煌喆
  • 目标框架: .NET Framework 4.8.1
  • 依赖项: Newtonsoft.Json (≥13.0.3)

🚀 快速开始

安装包

# Package Manager Console
Install-Package TrainQuery.API

# .NET CLI
dotnet add package TrainQuery.API

# PackageReference
<PackageReference Include="TrainQuery.API" Version="1.0.0" />

基础使用示例

using TrainQuery.API;
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        using (var service = new TrainQueryService())
        {
            // 订阅调试信息
            service.DebugMessage += (sender, message) => 
                Console.WriteLine($"[DEBUG] {DateTime.Now:HH:mm:ss} {message}");
            
            // 1. 初始化车站数据(必须首先调用)
            Console.WriteLine("正在初始化车站数据...");
            bool initSuccess = await service.InitializeStationsAsync();
            
            if (!initSuccess)
            {
                Console.WriteLine("车站数据初始化失败,请检查网络连接。");
                return;
            }
            Console.WriteLine("车站数据初始化成功!");
            
            // 2. 查询车票(示例:广州南 到 深圳北)
            Console.WriteLine("\n正在查询车票...");
            var tickets = await service.QueryDirectTicketsAsync(
                fromStation: "广州南",
                toStation: "深圳北",
                date: "2026-01-21" // 更新为未来的日期
            );
            
            // 3. 显示结果
            Console.WriteLine($"找到 {tickets.Count} 个车次");
            if (tickets.Count > 0)
            {
                Console.WriteLine("前几个车次信息:");
                foreach (var ticket in tickets)
                {
                    Console.WriteLine($"\n车次: {ticket.TrainNo}");
                    Console.WriteLine($"  区间: {ticket.FromStation} → {ticket.ToStation}");
                    Console.WriteLine($"  时间: {ticket.DepartureTime} - {ticket.ArrivalTime}");
                    Console.WriteLine($"  历时: {ticket.Duration}");
                    Console.WriteLine($"  状态: {ticket.Status}");
                    
                    // 显示有余票的座位
                    if (ticket.SeatAvailability != null)
                    {
                        foreach (var seat in ticket.SeatAvailability)
                        {
                            if (seat.Value != "无" && seat.Value != "--")
                            {
                                Console.WriteLine($"  {seat.Key}: {seat.Value}");
                            }
                        }
                    }
                }
            }
        }
        Console.WriteLine("\n按任意键退出...");
        Console.ReadKey();
    }
}

📚 详细使用说明

1. 初始化服务

// 创建服务实例(推荐使用using语句确保资源释放)
using var service = new TrainQueryService();

// 可选:订阅调试信息
service.DebugMessage += (sender, message) => 
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}");

// 可选:订阅车站加载完成事件
service.StationsLoaded += (sender, count) => 
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 已加载 {count} 个车站");

2. 车站数据初始化

重要:必须首先调用此方法!

try
{
    // 初始化车站数据
    bool success = await service.InitializeStationsAsync();

    if (!success)
    {
        // 初始化失败,可能是网络问题
        Console.WriteLine("车站数据初始化失败,请检查网络连接");
        return;
    }
    Console.WriteLine("车站数据初始化成功,可以开始查询。");
}
catch (Exception ex)
{
    Console.WriteLine($"初始化过程中发生异常: {ex.Message}");
}

3. 查询直达车票

// 基本查询
var tickets = await service.QueryDirectTicketsAsync(
    fromStation: "南京",           // 出发站(中文站名)
    toStation: "杭州东",           // 到达站(中文站名)
    date: "2026-01-22"            // 日期格式:yyyy-MM-dd
);

// 过滤特定车次类型(例如只查高铁)
var gTickets = await service.QueryDirectTicketsAsync(
    fromStation: "武汉",
    toStation: "长沙南",
    date: "2026-01-22",
    trainTypes: new List<string> { "G" } // 可选:过滤车次类型 (G-高铁, D-动车, 等)
);

4. 查询中转换乘方案

// 查询从 成都东 到 西安北 的中转换乘方案
var transferSolutions = await service.QueryTransferSolutionsAsync(
    fromStation: "成都东",
    toStation: "西安北",
    date: "2026-01-23"
);

Console.WriteLine($"找到 {transferSolutions?.Count ?? 0} 个中转方案");
if (transferSolutions != null && transferSolutions.Count > 0)
{
    // 显示第一个方案详情
    var firstSolution = transferSolutions[0];
    Console.WriteLine($"\n推荐方案:在 {firstSolution.MiddleStationName} 中转");
    Console.WriteLine($"  总旅行时间: {firstSolution.AllDuration}");
    Console.WriteLine($"  中转等待: {firstSolution.WaitTime}");
    Console.WriteLine($"  共 {firstSolution.Segments?.Count ?? 0} 段行程:");
    
    if (firstSolution.Segments != null)
    {
        foreach (var segment in firstSolution.Segments)
        {
            Console.WriteLine($"    • {segment.StationTrainCode}次:{segment.FromStationName} ({segment.StartTime}) → {segment.ToStationName} ({segment.ArriveTime}),历时 {segment.Duration}");
        }
    }
}

5. 查询票价信息

// 查询 G86 次列车 郑州东 到 北京西 的票价
var priceInfo = await service.QueryTicketPriceAsync(
    trainNo: "G86",                // 车次
    fromStation: "郑州东",         // 出发站
    toStation: "北京西",           // 到达站
    date: "2026-01-24"            // 日期
);

if (priceInfo?.Prices != null && priceInfo.Prices.Count > 0)
{
    Console.WriteLine($"车次 {priceInfo.TrainNo} 票价信息 ({priceInfo.Prices.Count} 种席别):");
    // 按价格排序显示
    var sortedPrices = priceInfo.Prices.OrderBy(p => p.Value);
    foreach (var price in sortedPrices)
    {
        if (price.Value > 0)
        {
            Console.WriteLine($"  {price.Key}: ¥{price.Value:F1}");
        }
    }
}
else
{
    Console.WriteLine("未查询到票价信息。");
}

6. 查询列车经停站

// 查询 D939 次列车 昆明南 到 贵阳北 区间的经停站
var stopStations = await service.QueryTrainStopStationsAsync(
    trainNo: "D939",               // 车次
    fromStation: "昆明南",         // 出发站
    toStation: "贵阳北",           // 到达站
    date: "2026-01-25"            // 日期
);

if (stopStations != null && stopStations.Count > 0)
{
    Console.WriteLine($"车次 D939 经停站信息 (共 {stopStations.Count} 站):");
    foreach (var station in stopStations)
    {
        string stationName = station.TryGetValue("station_name", out var name) ? name : "未知车站";
        string arriveTime = station.TryGetValue("arrive_time", out var arrive) ? arrive : "--";
        string startTime = station.TryGetValue("start_time", out var start) ? start : "--";
        string stopTime = station.TryGetValue("stopover_time", out var stop) ? stop : "";
        
        string timeInfo = arriveTime == "--" ? $"出发 {startTime}" : $"到达 {arriveTime}, 出发 {startTime}";
        if (!string.IsNullOrEmpty(stopTime)) timeInfo += $", 停靠 {stopTime}";
        
        Console.WriteLine($"  · {stationName.PadRight(8)} : {timeInfo}");
    }
}

🎯 进阶用法

Windows Forms 应用示例 (优化版)

using TrainQuery.API;
using System;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

public partial class MainForm : Form
{
    private TrainQueryService _service;
    private readonly string _logFilePath = "train_query_debug.log";
    
    public MainForm()
    {
        InitializeComponent();
        _service = new TrainQueryService();
        
        // 订阅调试消息到文本框和日志文件
        _service.DebugMessage += (sender, message) =>
        {
            var fullMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}";
            
            // 更新UI线程上的文本框
            if (txtDebug.InvokeRequired)
            {
                txtDebug.Invoke(new Action(() => 
                    txtDebug.AppendText(fullMessage + Environment.NewLine)));
            }
            else
            {
                txtDebug.AppendText(fullMessage + Environment.NewLine);
            }
            
            // 异步写入日志文件
            Task.Run(() => 
                System.IO.File.AppendAllText(_logFilePath, fullMessage + Environment.NewLine));
        };
        
        // 设置默认查询日期为明天
        dtpDate.Value = DateTime.Now.AddDays(1);
    }
    
    private async void btnQuery_Click(object sender, EventArgs e)
    {
        try
        {
            // 禁用按钮防止重复查询
            btnQuery.Enabled = false;
            btnQuery.Text = "查询中...";
            lblStatus.Text = "正在初始化并查询...";
            dgvResults.Rows.Clear();
            
            // 1. 初始化车站数据
            lblStatus.Text = "正在初始化车站数据...";
            if (!await _service.InitializeStationsAsync())
            {
                MessageBox.Show("车站数据初始化失败,请检查网络连接。", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            
            // 2. 查询车票
            lblStatus.Text = $"正在查询 {txtFrom.Text} 到 {txtTo.Text} 的车票...";
            var tickets = await _service.QueryDirectTicketsAsync(
                txtFrom.Text.Trim(),
                txtTo.Text.Trim(),
                dtpDate.Value.ToString("yyyy-MM-dd")
            );
            
            // 3. 显示结果
            dgvResults.Rows.Clear();
            if (tickets != null && tickets.Count > 0)
            {
                foreach (var ticket in tickets)
                {
                    // 构建座位信息字符串
                    string seatInfo = "";
                    if (ticket.SeatAvailability != null)
                    {
                        var availableSeats = ticket.SeatAvailability
                            .Where(kv => kv.Value != "无" && kv.Value != "--" && !string.IsNullOrEmpty(kv.Value))
                            .Select(kv => $"{kv.Key}:{kv.Value}");
                        seatInfo = string.Join(" ", availableSeats.Take(3)); // 最多显示3种座位
                    }
                    
                    dgvResults.Rows.Add(
                        ticket.TrainNo,
                        ticket.FromStation,
                        ticket.ToStation,
                        ticket.DepartureTime,
                        ticket.ArrivalTime,
                        ticket.Duration,
                        ticket.Status,
                        seatInfo
                    );
                }
                lblStatus.Text = $"查询完成!找到 {tickets.Count} 个车次,双击行查看详情。";
            }
            else
            {
                lblStatus.Text = "未找到符合条件的车次。";
                MessageBox.Show("未找到车次,请确认车站名称和日期是否正确。", "提示", 
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        catch (ArgumentException argEx)
        {
            lblStatus.Text = "参数错误";
            MessageBox.Show($"车站名称可能不正确: {argEx.Message}\n请使用标准车站名如'北京南'、'上海虹桥'。", 
                "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
        catch (Exception ex)
        {
            lblStatus.Text = "查询失败";
            MessageBox.Show($"查询过程中发生错误:\n{ex.Message}", "错误", 
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        finally
        {
            btnQuery.Enabled = true;
            btnQuery.Text = "查询车票";
        }
    }
    
    // 双击车次查看详情
    private async void dgvResults_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
    {
        if (e.RowIndex < 0 || e.RowIndex >= dgvResults.Rows.Count) return;
        
        var trainNo = dgvResults.Rows[e.RowIndex].Cells["colTrainNo"].Value?.ToString();
        var fromStation = dgvResults.Rows[e.RowIndex].Cells["colFromStation"].Value?.ToString();
        var toStation = dgvResults.Rows[e.RowIndex].Cells["colToStation"].Value?.ToString();
        
        if (string.IsNullOrEmpty(trainNo)) return;
        
        var detailForm = new TrainDetailForm(_service, trainNo, fromStation, toStation, dtpDate.Value);
        detailForm.ShowDialog();
    }
    
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _service?.Dispose();
        base.OnFormClosing(e);
    }
}

ASP.NET Core MVC 示例

using TrainQuery.API;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

public class TrainController : Controller
{
    // 查询页面
    public IActionResult Index()
    {
        ViewBag.DefaultDate = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd");
        return View();
    }
    
    // 处理查询请求 (API端点)
    [HttpPost]
    public async Task<IActionResult> QueryTickets([FromBody] QueryRequest request)
    {
        try
        {
            // 每个请求创建独立服务实例
            using var service = new TrainQueryService();
            
            // 可选的调试日志
            // service.DebugMessage += (sender, msg) => _logger.LogInformation(msg);
            
            // 初始化车站数据
            if (!await service.InitializeStationsAsync())
            {
                return Json(new { success = false, error = "车站数据初始化失败" });
            }
            
            // 执行查询
            var tickets = await service.QueryDirectTicketsAsync(
                request.FromStation,
                request.ToStation,
                request.Date
            );
            
            // 返回JSON结果
            return Json(new { 
                success = true, 
                count = tickets?.Count ?? 0,
                data = tickets?.Take(50).ToList() // 限制返回数量
            });
        }
        catch (Exception ex)
        {
            return Json(new { 
                success = false, 
                error = $"查询失败: {ex.Message}" 
            });
        }
    }
    
    // 查询请求模型
    public class QueryRequest
    {
        public string FromStation { get; set; }
        public string ToStation { get; set; }
        public string Date { get; set; }
    }
}

⚠️ 重要注意事项

1. 初始化要求

必须首先调用 InitializeStationsAsync() 方法

// ✅ 正确用法
using var service = new TrainQueryService();
await service.InitializeStationsAsync();  // 必须先调用
var tickets = await service.QueryDirectTicketsAsync(...);

// ❌ 错误用法(会抛出InvalidOperationException)
using var service = new TrainQueryService();
var tickets = await service.QueryDirectTicketsAsync(...);  // 错误!未初始化

2. 异常处理最佳实践

try
{
    using var service = new TrainQueryService();
    
    // 可能抛出异常的调用
    var tickets = await service.QueryDirectTicketsAsync("南京", "上海虹桥", "2026-01-20");
    
    // 处理查询结果...
}
catch (ArgumentException ex)
{
    // 参数错误,如车站不存在
    Console.WriteLine($"参数错误: {ex.Message}");
    Console.WriteLine("提示:请使用'上海虹桥'而非'上海',使用'北京南'而非'北京站'。");
}
catch (InvalidOperationException ex)
{
    // 操作顺序错误,如未初始化
    Console.WriteLine($"操作错误: {ex.Message}");
    Console.WriteLine("提示:请确保在查询前调用InitializeStationsAsync()方法。");
}
catch (System.Net.Http.HttpRequestException ex)
{
    // 网络错误或12306服务不可用
    Console.WriteLine($"网络请求错误: {ex.Message}");
    Console.WriteLine("提示:请检查网络连接,或稍后重试。");
}
catch (Exception ex)
{
    // 其他未预期的错误
    Console.WriteLine($"未知错误: {ex.Message}");
    Console.WriteLine($"详细堆栈: {ex.StackTrace}");
}

3. 线程安全与服务生命周期

// ❌ 避免:在多线程中共享同一个实例
// TrainQueryService 不是线程安全的

// ✅ 推荐:为每个线程或HTTP请求创建独立的实例
public class MyService
{
    public async Task<List<TicketInfo>> QueryAsync(string from, string to, string date)
    {
        // 每个方法调用创建新实例
        using var service = new TrainQueryService();
        await service.InitializeStationsAsync();
        return await service.QueryDirectTicketsAsync(from, to, date);
    }
}

// ✅ 在Web应用中,使用依赖注入的瞬态(Transient)生命周期
// Startup.cs或Program.cs中:
// services.AddTransient<TrainQueryService>();

4. 资源管理

// ✅ 推荐:使用using语句确保资源释放
using (var service = new TrainQueryService())
{
    await service.InitializeStationsAsync();
    var tickets = await service.QueryDirectTicketsAsync(...);
    // 使用服务...
} // 此处自动调用Dispose()

// ✅ 或者:使用C# 8.0的using声明
using var service = new TrainQueryService();
await service.InitializeStationsAsync();
// 使用服务...
// 在作用域结束时自动Dispose

// ✅ 手动管理(如需更精细控制)
var service = new TrainQueryService();
try
{
    await service.InitializeStationsAsync();
    // 使用服务...
}
finally
{
    service.Dispose(); // 确保资源释放
}

🔧 配置和优化

1. 网络超时与重试策略

// 自定义重试逻辑示例
public async Task<List<TicketInfo>> QueryWithRetry(string from, string to, string date, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var service = new TrainQueryService();
            await service.InitializeStationsAsync();
            return await service.QueryDirectTicketsAsync(from, to, date);
        }
        catch (System.Net.Http.HttpRequestException) when (attempt < maxRetries)
        {
            // 网络错误时重试
            Console.WriteLine($"第{attempt}次尝试失败,{ (maxRetries - attempt) }次重试剩余...");
            await Task.Delay(1000 * attempt); // 递增延迟
        }
    }
    throw new Exception($"查询失败,已重试{maxRetries}次");
}

2. 调试与日志记录

// 综合调试配置
service.DebugMessage += (sender, message) => 
{
    var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
    var fullMessage = $"[{timestamp}] {message}";
    
    // 1. 控制台输出
    Console.WriteLine(fullMessage);
    
    // 2. 调试窗口输出(在IDE中)
    System.Diagnostics.Debug.WriteLine(fullMessage);
    
    // 3. 记录到文件(带每日滚动)
    var logFile = $"trainquery_debug_{DateTime.Now:yyyyMMdd}.log";
    System.IO.File.AppendAllText(logFile, fullMessage + Environment.NewLine);
    
    // 4. 发送到监控系统(可选)
    // SendToMonitoringSystem(fullMessage);
};

3. 性能优化建议

// 1. 缓存车站数据(避免重复初始化)
private static TrainQueryService _cachedService;
private static DateTime _lastInitialized = DateTime.MinValue;

public async Task<TrainQueryService> GetInitializedServiceAsync()
{
    if (_cachedService == null || (DateTime.Now - _lastInitialized).TotalMinutes > 30)
    {
        // 创建新实例并初始化
        var service = new TrainQueryService();
        if (await service.InitializeStationsAsync())
        {
            _cachedService = service;
            _lastInitialized = DateTime.Now;
        }
        return service;
    }
    return _cachedService;
}

// 2. 批量查询多个日期
public async Task<Dictionary<string, List<TicketInfo>>> BatchQueryAsync(
    string from, string to, IEnumerable<string> dates)
{
    var results = new Dictionary<string, List<TicketInfo>>();
    using var service = new TrainQueryService();
    await service.InitializeStationsAsync();
    
    // 注意:避免同时发起过多请求,可能触发限流
    foreach (var date in dates)
    {
        try
        {
            var tickets = await service.QueryDirectTicketsAsync(from, to, date);
            results[date] = tickets;
            await Task.Delay(500); // 添加短暂延迟,避免请求过快
        }
        catch (Exception ex)
        {
            Console.WriteLine($"查询日期 {date} 时出错: {ex.Message}");
            results[date] = new List<TicketInfo>();
        }
    }
    return results;
}

🐛 常见问题解决

Q1: 出现"找不到车站代码"错误

原因: 车站名称不正确或车站数据未加载
解决:

  1. 确保已成功调用 InitializeStationsAsync() 方法
  2. 使用正确的中文站名(如"北京南"而非"北京","上海虹桥"而非"上海市虹桥站")
  3. 常见站名示例:
    • "北京南", "北京西", "北京朝阳"
    • "上海虹桥", "上海", "上海南"
    • "广州南", "广州", "广州东"
    • "深圳北", "深圳", "福田"

Q2: 返回HTML页面内容而不是JSON数据

原因: 12306反爬机制触发或请求被重定向
解决:

  1. 等待一段时间后重试(建议2-5秒)
  2. 检查网络环境,避免使用VPN或代理服务器
  3. 确保使用最新版本的类库
  4. 尝试不同的出发/到达站组合进行测试

Q3: 查询速度慢或超时

原因: 网络延迟、12306服务器繁忙或本地网络问题
解决:

  1. 添加加载状态提示,改善用户体验
  2. 在网络状况良好的时段进行查询
  3. 考虑实现客户端缓存,对相同查询缓存结果(建议缓存时间5-10分钟)
  4. 使用异步编程,避免阻塞UI线程

Q4: Newtonsoft.Json版本冲突

原因: 项目中存在多个不同版本的Newtonsoft.Json程序集
解决:


<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        
        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Q5: 在异步方法中调用时出现死锁

原因: 在UI线程上同步等待异步任务完成
解决:

// ❌ 避免:在UI线程上使用.Result或.Wait()
var tickets = service.QueryDirectTicketsAsync(...).Result; // 可能死锁

// ✅ 推荐:使用async/await模式
private async void btnQuery_Click(object sender, EventArgs e)
{
    try
    {
        var tickets = await service.QueryDirectTicketsAsync(...);
        // 更新UI
    }
    catch (Exception ex)
    {
        // 错误处理
    }
}

// ✅ 如果必须在同步方法中调用,使用ConfigureAwait(false)
var tickets = await service.QueryDirectTicketsAsync(...).ConfigureAwait(false);

📋 数据模型说明

TicketInfo(车票信息)

public class TicketInfo
{
    public string TrainNo { get; set; }            // 车次,如 "G1"
    public string FromStation { get; set; }        // 出发站,如 "北京南"
    public string ToStation { get; set; }          // 到达站,如 "上海虹桥"
    public string DepartureTime { get; set; }      // 出发时间,如 "08:00"
    public string ArrivalTime { get; set; }        // 到达时间,如 "12:30"
    public string Duration { get; set; }           // 历时,如 "4小时30分"
    public string Status { get; set; }             // 状态,如 "有票"、"无票"、"列车停运"
    
    // 座位余量信息字典
    // 键:座位类型,如 "商务座"、"一等座"、"二等座"、"硬卧"
    // 值:余票数量,如 "有"、"无"、"5"、"--"
    public Dictionary<string, string> SeatAvailability { get; set; }
    
    // 内部字段(高级用户)
    public string FromStationCode { get; set; }    // 出发站电报码,如 "VNP"
    public string ToStationCode { get; set; }      // 到达站电报码,如 "AOH"
    public string InternalTrainNo { get; set; }    // 内部车次号,用于详细查询
}

TransferSolution(中转换乘方案)

public class TransferSolution
{
    public string AllDuration { get; set; }        // 总历时,如 "6小时20分"
    public int AllDurationMinutes { get; set; }    // 总历时(分钟)
    public string MiddleStationName { get; set; }  // 中转站名称,如 "武汉"
    public string WaitTime { get; set; }           // 中转等待时间,如 "1小时10分"
    public int WaitTimeMinutes { get; set; }       // 中转等待时间(分钟)
    
    // 行程分段列表(通常为2段:出发→中转,中转→到达)
    public List<TransferSegment> Segments { get; set; }
    
    // 总票价组合(多种座位类型组合的总价)
    // 键:组合描述,如 "二等座+二等座"、"一等座+二等座"
    // 值:总价格
    public Dictionary<string, decimal> TotalPrices { get; set; }
}

PriceInfo(票价信息)

public class PriceInfo
{
    public string TrainNo { get; set; }                     // 车次
    public Dictionary<string, decimal> Prices { get; set; } // 座位类型与价格映射
    
    // 常用座位类型键名:
    // "swz"  - 商务座
    // "zy"   - 一等座
    // "ze"   - 二等座
    // "rw"   - 软卧
    // "yw"   - 硬卧
    // "yz"   - 硬座
    // "wz"   - 无座
}

🔄 版本历史

版本 1.0.0 (2024-01-19)

  • 车票查询:支持直达车次查询
  • 中转换乘:智能中转方案查询
  • 票价查询:多席别票价信息
  • 经停站查询:列车停靠站详情
  • 车站数据:自动加载与缓存
  • 错误处理:完善的异常处理机制
  • 调试支持:详细运行时调试信息
  • 框架支持:完整的.NET Framework 4.8.1兼容
  • 依赖管理:Newtonsoft.Json 13.0.3+

📞 技术支持

  • NuGet包页面: TrainQuery.API
  • 问题反馈: 通过NuGet包页面留言或GitHub Issues
  • 注意事项: 本库基于12306官方接口实现,请遵守相关服务条款,合理使用

🚂 关于 12306 接口的说明

本项目在实现车票查询、余票监控等功能时,所调用的火车票数据接口,来源于中国铁路12306官方网站(kyfw.12306.cn)及其相关服务域名。

⚠️ 重要须知与风险提示

请注意以下几点:

  1. 非官方合作接口:本项目使用的接口为12306网站为自身前端功能所提供的服务,并非12306官方对外公开的合作或商业API。因此,不存在官方的《API使用规定》或服务条款。

  2. 接口不确定性:这些接口的结构、参数、返回数据格式可能在不经通知的情况下发生变更,导致项目功能暂时失效。

  3. 访问限制风险:12306系统对高频、非正常访问行为(如自动化脚本频繁查询)设有严格的防护机制,包括但不限于IP封禁、请求限流(返回403/429等状态码)等。

  4. 法律与合规风险:任何未经授权的自动化访问、大规模抓取数据、或干扰系统正常运行的行为,都可能违反《网络安全法》等相关法律法规及12306网站自身的用户协议。

  5. 项目目的:本项目仅限于技术学习与研究交流,旨在理解大型并发系统的设计(如缓存策略、分布式锁等),严禁用于任何商业盈利、大规模抢票、干扰12306系统正常运行或其他非法用途。

📖 技术参考与学习价值

项目中关于高并发、缓存、分布式锁等技术的实现(如责任链模式验证、分布式锁处理、令牌限流等),参考了业界对12306这类高流量系统的技术分析和开源学习项目。这些技术实现本身具有很高的学习价值,与实际使用的数据接口是解耦的。


📄 许可证

MIT License - 详见NuGet包中的LICENSE文件


开始使用: Install-Package TrainQuery.API

最后更新日期: 2026-01-20 23:50

祝你使用愉快!如有问题,请查看常见问题部分或通过NuGet页面反馈。

Product Compatible and additional computed target framework versions.
.NET Framework net481 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 98 1/20/2026
1.0.0 98 1/19/2026

初始版本功能:
     1. 车票查询(直达)
     2. 中转换乘方案
     3. 票价查询
     4. 经停站信息
     5. 车站数据自动加载