首页
/ YahooFinanceApi 金融数据获取实战指南:从入门到企业级应用

YahooFinanceApi 金融数据获取实战指南:从入门到企业级应用

2026-03-16 02:03:27作者:齐添朝

金融数据获取的困境与解决方案

作为金融科技开发者,你是否曾面临这些挑战:需要构建股票分析系统却找不到稳定数据源?尝试抓取金融网站数据却频繁遭遇反爬机制?开发量化交易策略时受限于数据获取效率?YahooFinanceApi 正是为解决这些痛点而生——这是一个基于 .NET Standard 2.0 的雅虎财经 API 封装库,提供稳定、高效的金融数据访问能力。

本文将带你全面掌握这个强大工具的使用方法,从基础查询到企业级应用优化,让你轻松应对各类金融数据需求。

核心价值解析:为什么选择 YahooFinanceApi

YahooFinanceApi 为金融数据获取提供了一站式解决方案,其核心优势体现在三个方面:

1. 全功能数据覆盖

数据类型 具体内容 应用场景
实时行情 实时价格、成交量、市值等 实时监控面板、价格预警系统
历史数据 日/周/月K线、调整后收盘价 技术分析、回测系统
基本面数据 市盈率、股息率、财务指标 价值投资分析
事件数据 分红记录、股票拆分历史 长期投资回报计算

2. 开发效率提升

  • 简化的API设计:通过直观的方法链构建复杂查询
  • 强类型数据模型:编译时类型检查减少运行时错误
  • 异步优先:全面支持async/await模式,提升应用响应性

3. 企业级可靠性

  • 自动错误恢复:内置重试机制应对临时网络问题
  • 数据验证:自动过滤异常值和不完整数据
  • 轻量级设计:无外部依赖,易于集成到任何.NET项目

快速上手:从零开始的第一个金融数据应用

环境准备与安装

YahooFinanceApi 支持所有符合 .NET Standard 2.0 的环境,包括 .NET Core 2.0+、.NET Framework 4.6.1+ 等。

通过 NuGet 安装:

Install-Package YahooFinanceApi

或使用 .NET CLI:

dotnet add package YahooFinanceApi

如需从源码构建:

git clone https://gitcode.com/gh_mirrors/ya/YahooFinanceApi
cd YahooFinanceApi
dotnet build

基础查询:获取单只股票信息

让我们从获取微软公司(MSFT)的基本信息开始:

using System;
using System.Threading.Tasks;
using YahooFinanceApi;

class BasicStockQuery
{
    static async Task Main()
    {
        try
        {
            // 创建查询请求
            var query = Yahoo.Symbols("MSFT")
                            .Fields(Field.Symbol, 
                                   Field.RegularMarketPrice, 
                                   Field.FiftyTwoWeekHigh,
                                   Field.FiftyTwoWeekLow,
                                   Field.DividendYield);
            
            // 执行异步查询
            var securities = await query.QueryAsync();
            
            // 提取结果
            var msft = securities["MSFT"];
            
            // 输出关键信息
            Console.WriteLine($"股票代码: {msft[Field.Symbol]}");
            Console.WriteLine($"当前价格: {msft[Field.RegularMarketPrice]:C}");
            Console.WriteLine($"52周范围: {msft[Field.FiftyTwoWeekLow]:C} - {msft[Field.FiftyTwoWeekHigh]:C}");
            Console.WriteLine($"股息率: {msft[Field.DividendYield]}%");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"查询失败: {ex.Message}");
        }
    }
}

[!TIP] 首次使用时,建议先验证网络连接和API可用性。可以先查询像"AAPL"或"MSFT"这样的热门股票,确认基础功能正常。

场景化应用:构建实用金融工具

场景一:投资组合监控系统

构建一个实时监控多只股票的投资组合价值的工具:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YahooFinanceApi;

class PortfolioMonitor
{
    // 投资组合数据结构
    private class Holding
    {
        public string Symbol { get; set; }
        public int Shares { get; set; }
        public decimal PurchasePrice { get; set; }
    }
    
    static async Task Main()
    {
        // 定义投资组合
        var portfolio = new List<Holding>
        {
            new Holding { Symbol = "AAPL", Shares = 10, PurchasePrice = 150.25m },
            new Holding { Symbol = "MSFT", Shares = 15, PurchasePrice = 220.50m },
            new Holding { Symbol = "AMZN", Shares = 5, PurchasePrice = 135.75m }
        };
        
        try
        {
            // 批量查询所有股票
            var symbols = portfolio.Select(h => h.Symbol).ToArray();
            var securities = await Yahoo.Symbols(symbols)
                                      .Fields(Field.Symbol, Field.RegularMarketPrice)
                                      .QueryAsync();
            
            // 计算投资组合价值
            decimal totalValue = 0;
            decimal totalCost = 0;
            
            Console.WriteLine("投资组合状态:");
            Console.WriteLine("------------------------");
            
            foreach (var holding in portfolio)
            {
                var security = securities[holding.Symbol];
                var currentPrice = (decimal)security[Field.RegularMarketPrice];
                var marketValue = currentPrice * holding.Shares;
                var costBasis = holding.PurchasePrice * holding.Shares;
                var gainLoss = marketValue - costBasis;
                var gainLossPercent = (gainLoss / costBasis) * 100;
                
                totalValue += marketValue;
                totalCost += costBasis;
                
                Console.WriteLine($"{holding.Symbol}:");
                Console.WriteLine($"  持仓: {holding.Shares}股");
                Console.WriteLine($"  购买价: {holding.PurchasePrice:C}");
                Console.WriteLine($"  当前价: {currentPrice:C}");
                Console.WriteLine($"  市值: {marketValue:C}");
                Console.WriteLine($"  盈亏: {gainLoss:C} ({gainLossPercent:F2}%)");
                Console.WriteLine();
            }
            
            var totalGainLoss = totalValue - totalCost;
            var totalGainLossPercent = (totalGainLoss / totalCost) * 100;
            
            Console.WriteLine("投资组合总计:");
            Console.WriteLine($"  总成本: {totalCost:C}");
            Console.WriteLine($"  总市值: {totalValue:C}");
            Console.WriteLine($"  总盈亏: {totalGainLoss:C} ({totalGainLossPercent:F2}%)");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"监控系统错误: {ex.Message}");
        }
    }
}

适用场景评估:

  • 个人投资者日常监控
  • 投资顾问向客户展示投资状况
  • 小型基金管理工具

使用注意事项:

  • 实时价格有15分钟延迟,不适用于日内交易
  • 大量持仓时考虑分批查询,避免请求被限制
  • 实现定期自动刷新时需设置合理间隔(建议≥60秒)

场景二:技术分析数据获取

获取历史K线数据进行技术分析:

using System;
using System.Linq;
using System.Threading.Tasks;
using YahooFinanceApi;

class TechnicalAnalysisData
{
    static async Task Main()
    {
        try
        {
            // 设置时间范围(过去90天)
            var endDate = DateTime.Now;
            var startDate = endDate.AddDays(-90);
            
            // 获取特斯拉(TSLA)的日线数据
            var history = await Yahoo.GetHistoricalAsync(
                symbol: "TSLA",
                startDate: startDate,
                endDate: endDate,
                period: Period.Daily);
            
            if (history == null || !history.Any())
            {
                Console.WriteLine("未获取到历史数据");
                return;
            }
            
            // 计算简单移动平均线(SMA)
            var closingPrices = history.Select(c => c.Close).ToList();
            var sma20 = CalculateSMA(closingPrices, 20);
            var sma50 = CalculateSMA(closingPrices, 50);
            
            // 输出最近10个交易日数据
            Console.WriteLine("日期\t\t开盘价\t最高价\t最低价\t收盘价\t成交量\t20日SMA\t50日SMA");
            Console.WriteLine("-----------------------------------------------------------------------");
            
            var recentData = history.OrderByDescending(c => c.DateTime).Take(10).OrderBy(c => c.DateTime);
            int index = closingPrices.Count - 1;
            
            foreach (var candle in recentData)
            {
                var sma20Value = index >= 19 ? sma20[index - 19] : null;
                var sma50Value = index >= 49 ? sma50[index - 49] : null;
                
                Console.WriteLine($"{candle.DateTime:yyyy-MM-dd}\t" +
                              $"{candle.Open:F2}\t{candle.High:F2}\t{candle.Low:F2}\t{candle.Close:F2}\t" +
                              $"{candle.Volume:N0}\t" +
                              $"{(sma20Value.HasValue ? sma20Value.Value:F2) : "N/A"}\t" +
                              $"{(sma50Value.HasValue ? sma50Value.Value:F2) : "N/A"}");
                
                index--;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"获取技术分析数据失败: {ex.Message}");
        }
    }
    
    // 计算简单移动平均线
    private static decimal?[] CalculateSMA(List<decimal> prices, int period)
    {
        var sma = new decimal?[prices.Count];
        
        for (int i = period - 1; i < prices.Count; i++)
        {
            var sum = prices.GetRange(i - period + 1, period).Sum();
            sma[i] = sum / period;
        }
        
        return sma;
    }
}

适用场景评估:

  • 技术指标计算(移动平均线、RSI等)
  • 交易策略回测
  • 股价走势分析报告

使用注意事项:

  • 历史数据请求范围不宜过大,单次请求建议不超过1年日线数据
  • 不同周期数据(日线/周线/月线)需选择合适的Period参数
  • 注意调整后收盘价(AdjustedClose)与普通收盘价的区别

场景三:分红与拆分历史分析

对于长期投资者,分红和股票拆分历史是评估投资回报的重要依据:

using System;
using System.Threading.Tasks;
using YahooFinanceApi;

class DividendAndSplitAnalyzer
{
    static async Task Main()
    {
        string symbol = "JNJ"; // 强生公司,著名的股息贵族
        var startDate = new DateTime(2010, 1, 1);
        var endDate = DateTime.Now;
        
        try
        {
            // 获取分红历史
            var dividends = await Yahoo.GetDividendsAsync(symbol, startDate, endDate);
            
            // 获取拆分历史
            var splits = await Yahoo.GetSplitsAsync(symbol, startDate, endDate);
            
            Console.WriteLine($"强生公司({symbol})分红与拆分历史分析 ({startDate:yyyy} - {endDate:yyyy})");
            Console.WriteLine("==============================================");
            
            // 分析分红数据
            if (dividends != null && dividends.Any())
            {
                Console.WriteLine("\n分红历史:");
                Console.WriteLine("日期\t\t分红金额(美元)\t年股息率*");
                
                decimal totalDividends = 0;
                DateTime? lastExDate = null;
                decimal? lastPrice = null;
                
                foreach (var dividend in dividends.OrderBy(d => d.DateTime))
                {
                    totalDividends += dividend.Dividend;
                    
                    // 估算股息率(需要当时的股价数据)
                    string yieldStr = "N/A";
                    
                    Console.WriteLine($"{dividend.DateTime:yyyy-MM-dd}\t{dividend.Dividend:F2}\t\t{yieldStr}");
                }
                
                int years = endDate.Year - startDate.Year;
                if (years > 0)
                {
                    decimal avgAnnualDividend = totalDividends / years;
                    Console.WriteLine($"\n年度平均分红: {avgAnnualDividend:F2}美元");
                    Console.WriteLine($"分红次数: {dividends.Count}次");
                }
            }
            else
            {
                Console.WriteLine("\n未找到分红历史数据");
            }
            
            // 分析拆分历史
            if (splits != null && splits.Any())
            {
                Console.WriteLine("\n股票拆分历史:");
                Console.WriteLine("日期\t\t拆分比例(后:前)");
                
                foreach (var split in splits.OrderBy(s => s.DateTime))
                {
                    Console.WriteLine($"{split.DateTime:yyyy-MM-dd}\t{split.AfterSplit}:{split.BeforeSplit}");
                }
            }
            else
            {
                Console.WriteLine("\n未找到股票拆分历史");
            }
            
            Console.WriteLine("\n* 股息率基于除息日股价估算");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"分析失败: {ex.Message}");
        }
    }
}

适用场景评估:

  • 股息投资策略评估
  • 长期投资回报计算
  • 股票历史表现分析

使用注意事项:

  • 分红数据可能不包含特殊股息,需额外验证
  • 拆分历史对长期持仓成本计算至关重要
  • 股息率计算需要结合当时股价,需额外获取历史价格数据

实战指南:技术原理与高级应用

API工作机制解析

YahooFinanceApi 的核心工作流程基于以下几个步骤:

  1. 请求构建:通过流畅API构建查询参数,包括股票代码、数据字段、时间范围等
  2. URL生成:将查询参数转换为雅虎财经API的请求URL
  3. 网络请求:使用HttpClient发送异步请求获取数据
  4. 数据解析:将CSV格式的响应数据解析为强类型对象
  5. 结果返回:将处理后的数据返回给调用者

[!NOTE] YahooFinanceApi 并不直接提供金融数据,而是作为雅虎财经公开API的封装层。它处理了请求构建、数据解析和错误处理等底层细节,让开发者可以专注于业务逻辑。

性能优化实践

当处理大量数据或高频查询时,这些优化技巧可以显著提升性能:

1. 批量查询优化

// 低效:多次单独查询
var aapl = await Yahoo.Symbols("AAPL").QueryAsync();
var msft = await Yahoo.Symbols("MSFT").QueryAsync();
var goog = await Yahoo.Symbols("GOOG").QueryAsync();

// 高效:单次批量查询
var securities = await Yahoo.Symbols("AAPL", "MSFT", "GOOG", "AMZN", "META")
                          .Fields(Field.Symbol, Field.RegularMarketPrice)
                          .QueryAsync();

性能对比:

  • 单独查询5只股票:约500-800ms(5次网络请求)
  • 批量查询5只股票:约150-250ms(1次网络请求)

2. 字段选择优化

只请求需要的字段,减少数据传输量:

// 优化前:请求所有可用字段
var securities = await Yahoo.Symbols("AAPL").QueryAsync();

// 优化后:只请求需要的字段
var securities = await Yahoo.Symbols("AAPL")
                          .Fields(Field.Symbol, Field.RegularMarketPrice, Field.Volume)
                          .QueryAsync();

3. 连接池管理

对于高频查询场景,使用共享HttpClient实例:

// 创建可共享的HttpClient实例
var httpClient = new HttpClient(new HttpClientHandler
{
    MaxConnectionsPerServer = 10, // 设置连接池大小
    AutomaticDecompression = System.Net.DecompressionMethods.GZip
});

// 在应用程序启动时配置共享HttpClient
Yahoo.HttpClient = httpClient;

// 后续所有查询将使用此HttpClient
var data = await Yahoo.GetHistoricalAsync("AAPL", startDate, endDate, Period.Daily);

错误处理与恢复策略

金融数据获取过程中可能遇到各种异常情况,完善的错误处理机制至关重要:

异常类型分析

异常类型 可能原因 恢复策略
HttpRequestException 网络问题、API不可用 指数退避重试(1s, 2s, 4s间隔)
KeyNotFoundException 无效的股票代码 验证股票代码,使用备选代码
FormatException 数据格式异常 跳过异常数据,记录错误日志
TaskCanceledException 请求超时 增加超时时间,简化查询

高级错误处理实现

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using YahooFinanceApi;

class ResilientDataFetcher
{
    // 指数退避重试策略
    public async Task<Security> GetSecurityWithRetry(string symbol, int maxRetries = 3)
    {
        int retries = 0;
        int delayMs = 1000; // 初始延迟
        
        while (true)
        {
            try
            {
                var securities = await Yahoo.Symbols(symbol)
                                          .Fields(Field.Symbol, Field.RegularMarketPrice)
                                          .QueryAsync();
                                          
                return securities[symbol];
            }
            catch (HttpRequestException ex)
            {
                retries++;
                if (retries >= maxRetries)
                {
                    Console.WriteLine($"已达到最大重试次数({maxRetries}),请求失败");
                    throw new Exception("数据获取失败,请稍后再试", ex);
                }
                
                Console.WriteLine($"请求失败,正在重试({retries}/{maxRetries})...");
                await Task.Delay(delayMs);
                delayMs *= 2; // 指数增加延迟时间
            }
            catch (KeyNotFoundException)
            {
                Console.WriteLine($"股票代码 {symbol} 无效");
                return null;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发生意外错误: {ex.Message}");
                return null;
            }
        }
    }
    
    // 带超时控制的历史数据获取
    public async Task<ICandle[]> GetHistoricalWithTimeout(
        string symbol, DateTime startDate, DateTime endDate, Period period, 
        int timeoutSeconds = 10)
    {
        using (var cancellationSource = new CancellationTokenSource())
        {
            cancellationSource.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
            
            try
            {
                return await Yahoo.GetHistoricalAsync(
                    symbol, startDate, endDate, period, 
                    cancellationToken: cancellationSource.Token);
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine($"获取历史数据超时({timeoutSeconds}秒)");
                return null;
            }
        }
    }
}

行业应用案例

案例一:量化交易系统数据层

某量化交易团队使用YahooFinanceApi构建了策略回测与实盘交易的数据层:

// 量化交易系统中的数据服务
public class MarketDataService
{
    private readonly Dictionary<string, ICandle[]> _historicalDataCache = new Dictionary<string, ICandle[]>();
    private readonly object _cacheLock = new object();
    private const int CacheExpirationHours = 24;
    private readonly Dictionary<string, DateTime> _cacheTimestamps = new Dictionary<string, DateTime>();
    
    // 获取历史数据(带缓存)
    public async Task<ICandle[]> GetHistoricalData(
        string symbol, DateTime startDate, DateTime endDate, Period period)
    {
        string cacheKey = $"{symbol}_{startDate:yyyyMMdd}_{endDate:yyyyMMdd}_{period}";
        
        // 检查缓存
        lock (_cacheLock)
        {
            if (_historicalDataCache.TryGetValue(cacheKey, out var cachedData) &&
                _cacheTimestamps.TryGetValue(cacheKey, out var cacheTime) &&
                DateTime.Now - cacheTime < TimeSpan.FromHours(CacheExpirationHours))
            {
                return cachedData;
            }
        }
        
        // 缓存未命中,获取新数据
        var data = await Yahoo.GetHistoricalAsync(symbol, startDate, endDate, period);
        
        // 更新缓存
        lock (_cacheLock)
        {
            _historicalDataCache[cacheKey] = data;
            _cacheTimestamps[cacheKey] = DateTime.Now;
            
            // 清理过期缓存
            CleanupExpiredCache();
        }
        
        return data;
    }
    
    // 清理过期缓存
    private void CleanupExpiredCache()
    {
        var expiredKeys = _cacheTimestamps.Where(kvp => 
            DateTime.Now - kvp.Value > TimeSpan.FromHours(CacheExpirationHours))
            .Select(kvp => kvp.Key)
            .ToList();
            
        foreach (var key in expiredKeys)
        {
            _historicalDataCache.Remove(key);
            _cacheTimestamps.Remove(key);
        }
    }
    
    // 获取实时行情(带重试和故障转移)
    public async Task<decimal> GetRealTimePrice(string symbol)
    {
        var fetcher = new ResilientDataFetcher();
        var security = await fetcher.GetSecurityWithRetry(symbol);
        
        if (security == null)
        {
            // 故障转移:尝试从备选数据源获取
            return await GetPriceFromBackupSource(symbol);
        }
        
        return (decimal)security[Field.RegularMarketPrice];
    }
    
    private async Task<decimal> GetPriceFromBackupSource(string symbol)
    {
        // 实现备选数据源逻辑
        // ...
    }
}

案例二:金融数据分析仪表板

某金融科技公司使用YahooFinanceApi构建了面向零售投资者的数据分析仪表板:

// 金融仪表板数据服务
public class DashboardDataService
{
    public async Task<MarketSummary> GetMarketSummary()
    {
        // 获取主要市场指数
        var indices = await Yahoo.Symbols("^DJI", "^IXIC", "^GSPC", "^RUT")
                               .Fields(Field.Symbol, Field.RegularMarketPrice, 
                                      Field.RegularMarketChange, Field.RegularMarketChangePercent)
                               .QueryAsync();
                               
        // 获取热门股票
        var movers = await Yahoo.Symbols("TSLA", "AAPL", "MSFT", "AMZN", "NVDA", "META")
                              .Fields(Field.Symbol, Field.RegularMarketPrice,
                                     Field.RegularMarketChange, Field.RegularMarketChangePercent,
                                     Field.Volume)
                              .QueryAsync();
                               
        return new MarketSummary
        {
            AsOf = DateTime.Now,
            Indices = indices.Select(kvp => new IndexData
            {
                Symbol = kvp.Key,
                Name = GetIndexName(kvp.Key),
                Price = (decimal)kvp.Value[Field.RegularMarketPrice],
                Change = (decimal)kvp.Value[Field.RegularMarketChange],
                ChangePercent = (decimal)kvp.Value[Field.RegularMarketChangePercent]
            }).ToList(),
            
            TopMovers = movers.Select(kvp => new StockData
            {
                Symbol = kvp.Key,
                Name = GetStockName(kvp.Key),
                Price = (decimal)kvp.Value[Field.RegularMarketPrice],
                Change = (decimal)kvp.Value[Field.RegularMarketChange],
                ChangePercent = (decimal)kvp.Value[Field.RegularMarketChangePercent],
                Volume = (long)kvp.Value[Field.Volume]
            }).OrderByDescending(s => s.ChangePercent).ToList()
        };
    }
    
    // 获取投资组合分析
    public async Task<PortfolioAnalysis> AnalyzePortfolio(List<PortfolioHolding> holdings)
    {
        var symbols = holdings.Select(h => h.Symbol).ToArray();
        var securities = await Yahoo.Symbols(symbols)
                                  .Fields(Field.Symbol, Field.RegularMarketPrice,
                                         Field.MarketCap, Field.PriceToEarnings)
                                  .QueryAsync();
                                  
        // 计算各项指标...
        // ...
        
        return new PortfolioAnalysis
        {
            // 填充分析结果
        };
    }
    
    private string GetIndexName(string symbol)
    {
        return symbol switch
        {
            "^DJI" => "道琼斯工业平均指数",
            "^IXIC" => "纳斯达克综合指数",
            "^GSPC" => "标普500指数",
            "^RUT" => "罗素2000指数",
            _ => symbol
        };
    }
    
    // 其他辅助方法...
}

附录:实用资源

常见问题排查流程图

  1. 数据返回为空

    • 检查股票代码是否正确(区分大小写)
    • 验证日期范围是否有效(开始日期 < 结束日期)
    • 确认网络连接正常
    • 尝试使用不同的股票代码测试
  2. 请求超时

    • 检查网络连接质量
    • 减小请求的数据范围
    • 增加超时时间设置
    • 实现重试机制
  3. 字段值为null

    • 确认该字段对请求的股票可用
    • 检查是否使用了正确的字段枚举
    • 尝试扩大日期范围
    • 验证股票是否在交易时间内

性能优化Checklist

  • [ ] 对多次使用的相同数据实施缓存
  • [ ] 批量查询多个股票而非单独查询
  • [ ] 只请求需要的字段,避免获取冗余数据
  • [ ] 使用共享HttpClient实例并配置连接池
  • [ ] 对高频访问数据实施本地缓存
  • [ ] 实现合理的重试机制和超时控制
  • [ ] 对大型数据集使用分页或分段获取
  • [ ] 监控API响应时间,识别性能瓶颈

常用字段参考表

字段类别 常用字段 说明
基本信息 Symbol, ShortName, Currency 股票代码、简称、货币单位
价格数据 RegularMarketPrice, RegularMarketChange, RegularMarketChangePercent 常规市场价格、变动额、变动百分比
成交量 RegularMarketVolume, AverageDailyVolume3Month 常规成交量、3个月平均日成交量
估值指标 MarketCap, PriceToEarnings, PriceToBook 市值、市盈率、市净率
历史数据 Open, High, Low, Close, AdjustedClose, Volume K线数据:开盘价、最高价、最低价、收盘价、调整后收盘价、成交量
股息相关 DividendYield, TrailingAnnualDividendRate 股息率、最近12个月股息

通过本指南,你已经掌握了YahooFinanceApi的核心功能和高级应用技巧。无论是构建个人投资工具还是企业级金融系统,这个强大的库都能为你提供稳定可靠的数据支持。随着金融科技的不断发展,掌握高效的数据获取技术将成为开发者的重要竞争力。现在就开始你的金融数据应用开发之旅吧!

登录后查看全文
热门项目推荐
相关项目推荐