diff --git a/docs/electric-vehicle-feature.md b/docs/electric-vehicle-feature.md index 6d08e996..ffcf843e 100644 --- a/docs/electric-vehicle-feature.md +++ b/docs/electric-vehicle-feature.md @@ -125,7 +125,11 @@ public enum GasolineGrade **GasolinePriceService.cs** - CRUD 操作 - **GetLatestPriceAsync** - 获取最新油价 -- **RefreshGasolinePricesAsync** - 刷新油价(调用 Tanshu API) +- **RefreshGasolinePricesAsync** - 刷新油价(调用 GasolinePriceRefresher) + +**GasolinePriceRefresher.cs** +- 内部服务(不暴露为 HTTP API) +- **RefreshGasolinePricesAsync** - 调用 Tanshu API 刷新油价,供后台任务使用 #### 油电对比算法 @@ -406,9 +410,10 @@ export default { ## 后台任务 **GasolinePriceRefreshWorker** -- 执行时间:每天凌晨 2:00 +- 执行时间:每天晚上 21:00 - 功能:刷新全国各省市油价数据 - 数据源:Tanshu API (https://api.tanshuapi.com/api/youjia/v1/index) +- 权限:`RefreshGasolinePricesAsync` 方法添加 `[AllowAnonymous]` 属性,允许后台任务调用 ## 构建状态 diff --git "a/implementation/01_Lottery_\345\244\215\345\274\217\346\212\225\346\263\250\345\257\274\345\205\245\345\212\237\350\203\275.md" "b/implementation/01_Lottery_\345\244\215\345\274\217\346\212\225\346\263\250\345\257\274\345\205\245\345\212\237\350\203\275.md" deleted file mode 100644 index 96432a73..00000000 --- "a/implementation/01_Lottery_\345\244\215\345\274\217\346\212\225\346\263\250\345\257\274\345\205\245\345\212\237\350\203\275.md" +++ /dev/null @@ -1,210 +0,0 @@ -# Implementation Plan - -[Overview] -实现彩票购买系统中复式快乐8和双色球的导入功能。 - -本功能旨在扩展现有彩票系统的投注能力,支持用户通过Web界面手动输入复式投注号码,系统自动计算所有可能的组合并保存。该功能将集成到现有的彩票模块中,利用现有的数据结构和业务逻辑,提供用户友好的多行输入界面,支持双色球和快乐8两种彩票类型的复式投注。 - -[Types] -扩展现有的彩票类型系统以支持复式投注。 - -详细类型定义: - -1. **复式投注输入DTO** -```csharp -public class CompoundLotteryInputDto -{ - public int Period { get; set; } - public string LotteryType { get; set; } - public List RedNumbers { get; set; } // 红球号码列表 - public List BlueNumbers { get; set; } // 蓝球号码列表(仅双色球) - public LotteryKL8PlayType? PlayType { get; set; } // 快乐8玩法类型 -} -``` - -2. **复式投注响应DTO** -```csharp -public class CompoundLotteryResultDto -{ - public int TotalCombinations { get; set; } - public decimal TotalAmount { get; set; } - public List CreatedLotteries { get; set; } -} -``` - -3. **快乐8玩法类型枚举**(已存在) -```csharp -public enum LotteryKL8PlayType -{ - Select1 = 1, // 选一 - Select2 = 2, // 选二 - Select3 = 3, // 选三 - Select4 = 4, // 选四 - Select5 = 5, // 选五 - Select6 = 6, // 选六 - Select7 = 7, // 选七 - Select8 = 8, // 选八 - Select9 = 9, // 选九 - Select10 = 10 // 选十 -} -``` - -[Files] -创建新文件并修改现有文件以支持复式投注功能。 - -详细文件修改计划: - -**新文件创建:** -1. `src/DFApp.Application.Contracts/Lottery/CompoundLotteryInputDto.cs` - 复式投注输入DTO -2. `src/DFApp.Application.Contracts/Lottery/CompoundLotteryResultDto.cs` - 复式投注响应DTO -3. `src/DFApp.Application.Contracts/Lottery/ICompoundLotteryService.cs` - 复式投注服务接口 -4. `src/DFApp.Application/Lottery/CompoundLotteryService.cs` - 复式投注服务实现 -5. `DFApp.Vue/src/views/lottery/components/CompoundLotteryInput.vue` - 前端复式投注输入组件 - -**现有文件修改:** -1. `src/DFApp.Application.Contracts/Lottery/ILotteryService.cs` - 添加复式投注服务引用 -2. `src/DFApp.Application/Lottery/LotteryService.cs` - 集成复式投注服务 -3. `DFApp.Vue/src/views/lottery/index.vue` - 添加复式投注界面 -4. `DFApp.Vue/src/api/lottery.ts` - 添加复式投注API调用 - -**配置更新:** -1. `src/DFApp.Application/DFAppApplicationModule.cs` - 注册复式投注服务 -2. `src/DFApp.HttpApi/Controllers/LotteryController.cs` - 添加复式投注API端点 - -[Functions] -扩展彩票服务以支持复式投注的组合计算和保存。 - -详细函数定义: - -**新函数:** -1. `CalculateCompoundCombination(CompoundLotteryInputDto dto)` - 计算复式投注组合 - - 文件路径:`src/DFApp.Application/Lottery/CompoundLotteryService.cs` - - 功能:根据输入号码计算所有可能的组合 - - 参数:CompoundLotteryInputDto - - 返回:CompoundLotteryResultDto - -2. `GenerateSSQCombinations(List reds, List blues)` - 生成双色球组合 - - 文件路径:`src/DFApp.Application/Lottery/CompoundLotteryService.cs` - - 功能:计算双色球复式投注的所有组合 - - 参数:红球列表、蓝球列表 - - 返回:组合列表 - -3. `GenerateKL8Combinations(List numbers, LotteryKL8PlayType playType)` - 生成快乐8组合 - - 文件路径:`src/DFApp.Application/Lottery/CompoundLotteryService.cs` - - 功能:计算快乐8复式投注的所有组合 - - 参数:号码列表、玩法类型 - - 返回:组合列表 - -4. `ValidateCompoundInput(CompoundLotteryInputDto dto)` - 验证复式输入 - - 文件路径:`src/DFApp.Application/Lottery/CompoundLotteryService.cs` - - 功能:验证输入号码的有效性和合理性 - - 参数:CompoundLotteryInputDto - - 返回:验证结果 - -**修改函数:** -1. `CreateLotteryBatch(List dtos)` - 扩展以支持复式投注 - - 文件路径:`src/DFApp.Application/Lottery/LotteryService.cs` - - 修改:增加对复式投注数据的处理逻辑 - -[Classes] -创建新的服务类来处理复式投注业务逻辑。 - -详细类定义: - -**新类:** -1. **CompoundLotteryService** - 复式投注服务 - - 文件路径:`src/DFApp.Application/Lottery/CompoundLotteryService.cs` - - 继承:ApplicationService - - 实现:ICompoundLotteryService - - 关键方法: - - CalculateCompoundCombination - - GenerateSSQCombinations - - GenerateKL8Combinations - - ValidateCompoundInput - -2. **CompoundLotteryInput** - 前端输入组件 - - 文件路径:`DFApp.Vue/src/views/lottery/components/CompoundLotteryInput.vue` - - 类型:Vue组件 - - 功能:提供多行号码输入界面 - - 属性:彩票类型、期号、号码列表 - -**修改类:** -1. **LotteryService** - 现有彩票服务 - - 文件路径:`src/DFApp.Application/Lottery/LotteryService.cs` - - 修改:注入CompoundLotteryService,提供复式投注入口 - -[Dependencies] -添加必要的数学组合计算库支持。 - -依赖修改详情: - -**新依赖:** -1. 组合数学计算库(可选): - - 如果需要高性能组合计算,可引入 `MathNet.Numerics` - - 或者使用现有的组合算法实现 - -**现有依赖利用:** -1. 利用现有的ABP框架基础设施 -2. 利用现有的EntityFramework Core数据访问 -3. 利用现有的Vue.js前端框架 - -[Testing] -创建单元测试验证复式投注的组合计算正确性。 - -测试方案: - -**测试文件创建:** -1. `test/DFApp.Application.Tests/Lottery/CompoundLotteryService_Tests.cs` - 复式投注服务测试 -2. `DFApp.Vue/src/views/lottery/components/__tests__/CompoundLotteryInput.spec.ts` - 前端组件测试 - -**测试用例:** -1. 双色球复式组合计算测试 - - 输入:7个红球,2个蓝球 - - 预期:7选6 × 2 = 14种组合 -2. 快乐8复式组合计算测试 - - 输入:11个号码,选10玩法 - - 预期:11选10 = 11种组合 -3. 输入验证测试 - - 无效号码格式 - - 超出范围号码 - - 重复号码 -4. 边界条件测试 - - 最小/最大号码数量 - - 空输入处理 - -[Implementation Order] -按照依赖关系顺序实现复式投注功能。 - -实现步骤: - -1. **第一步:后端DTO和服务接口** - - 创建CompoundLotteryInputDto和CompoundLotteryResultDto - - 创建ICompoundLotteryService接口 - - 注册服务到依赖注入容器 - -2. **第二步:组合计算算法** - - 实现GenerateSSQCombinations方法 - - 实现GenerateKL8Combinations方法 - - 添加输入验证逻辑 - -3. **第三步:服务集成** - - 实现CompoundLotteryService完整功能 - - 修改LotteryService集成复式投注 - - 添加API控制器端点 - -4. **第四步:前端组件** - - 创建CompoundLotteryInput.vue组件 - - 实现多行号码输入界面 - - 添加输入格式验证 - -5. **第五步:界面集成** - - 在彩票管理页面集成复式投注组件 - - 添加API调用逻辑 - - 实现结果展示 - -6. **第六步:测试验证** - - 编写和运行单元测试 - - 进行集成测试 - - 用户验收测试 - -这个实现顺序确保了技术依赖的正确解决,从基础的数据结构开始,逐步构建业务逻辑,最后完成用户界面集成。 diff --git "a/implementation/02_Lottery_\346\225\260\346\215\256\350\216\267\345\217\226\346\216\245\345\217\243\346\265\213\350\257\225.md" "b/implementation/02_Lottery_\346\225\260\346\215\256\350\216\267\345\217\226\346\216\245\345\217\243\346\265\213\350\257\225.md" deleted file mode 100644 index 0bf01d6a..00000000 --- "a/implementation/02_Lottery_\346\225\260\346\215\256\350\216\267\345\217\226\346\216\245\345\217\243\346\265\213\350\257\225.md" +++ /dev/null @@ -1,188 +0,0 @@ -# 彩票数据获取接口测试 - -## 概述 - -为了解决 `LotteryResultTimer` 中 `GetLotteryResult` 方法无法抓取数据的问题,我们创建了一个可以手动调用的接口,并添加了详细的日志记录功能。 - -## 新增文件 - -### 后端文件 - -1. **src/DFApp.Application.Contracts/Lottery/LotteryDataFetchDto.cs** - - 定义了彩票数据获取的请求和响应DTO - -2. **src/DFApp.Application.Contracts/Lottery/ILotteryDataFetchService.cs** - - 定义了彩票数据获取服务的接口 - -3. **src/DFApp.Application/Lottery/LotteryDataFetchService.cs** - - 实现了彩票数据获取服务,包含手动调用接口 - -### 前端文件 - -1. **DFApp.Vue/src/api/lotteryDataFetch.ts** - - 前端API调用封装 - -2. **DFApp.Vue/src/views/lottery/data-fetch.vue** - - 数据获取测试页面 - -3. **DFApp.Vue/src/router/modules/lottery.ts** - - 添加了数据获取测试页面的路由配置 - -## 修改的文件 - -1. **src/DFApp.Application/Background/LotteryResultTimer.cs** - - 添加了详细的日志记录,便于排查问题 - -## 接口说明 - -### 1. 手动获取彩票数据 - -**接口地址**: `POST /api/app/lottery-data-fetch/fetch-lottery-data` - -**请求参数**: -```json -{ - "dayStart": "2023-01-01", // 开始日期 - "dayEnd": "2023-01-31", // 结束日期 - "pageNo": 1, // 页码 - "lotteryType": "ssq", // 彩票类型: ssq(双色球) 或 kl8(快乐8) - "saveToDatabase": false // 是否保存到数据库 -} -``` - -**响应结果**: -```json -{ - "success": true, - "message": "成功获取到 10 条数据", - "data": { /* 彩票数据 */ }, - "savedCount": 0, - "requestUrl": "https://www.cwl.gov.cn/...", - "statusCode": 200, - "responseTime": 1500 -} -``` - -### 2. 获取双色球最新数据 - -**接口地址**: `GET /api/app/lottery-data-fetch/fetch-ssq-latest-data` - -**响应结果**: 同上,但自动设置为今天的数据和双色球类型 - -### 3. 获取快乐8最新数据 - -**接口地址**: `GET /api/app/lottery-data-fetch/fetch-kl8-latest-data` - -**响应结果**: 同上,但自动设置为今天的数据和快乐8类型 - -### 4. 测试API连接 - -**接口地址**: `GET /api/app/lottery-data-fetch/test-lottery-api-connection` - -**请求参数**: -- `lotteryType`: 彩票类型,默认为 "ssq" - -**响应结果**: 同上,但不会保存数据到数据库 - -## 使用方法 - -### 1. 通过前端页面测试 - -1. 启动前端项目 -2. 导航到 "彩票管理" -> "数据获取测试" -3. 使用页面上的按钮进行测试: - - 获取双色球最新数据 - - 获取快乐8最新数据 - - 测试API连接 - - 自定义查询 - -### 2. 直接调用API - -可以使用 Postman 或其他API工具直接调用上述接口。 - -## 日志记录 - -### 后端日志 - -所有操作都会在后端生成详细的日志,包括: - -1. **请求信息**: - - 彩票类型 - - 日期范围 - - 页码 - - 请求URL - -2. **HTTP响应信息**: - - 状态码 - - 响应内容长度 - - 响应内容(前500字符) - -3. **数据处理信息**: - - 反序列化结果 - - 数据条数 - - 第一条数据的详细信息 - -4. **错误信息**: - - HTTP请求异常 - - JSON解析异常 - - 数据库操作异常 - -### 前端日志 - -前端页面会显示每次操作的详细结果,包括: - -1. **操作信息**: - - 操作时间 - - 操作类型 - - 彩票类型 - -2. **结果信息**: - - 是否成功 - - 消息内容 - - 数据条数 - - 保存条数 - - 响应时间 - - HTTP状态码 - -3. **详细信息**: - - 请求URL - - 数据预览 - -## 排查问题的步骤 - -1. **测试API连接**: - - 使用 "测试API连接" 功能检查网络连接是否正常 - -2. **检查日志**: - - 查看后端日志,确认请求是否正常发送 - - 检查HTTP响应状态码和内容 - -3. **验证数据格式**: - - 检查返回的JSON格式是否正确 - - 确认数据结构是否符合预期 - -4. **测试不同参数**: - - 尝试不同的日期范围 - - 测试不同的彩票类型 - - 调整页码参数 - -5. **检查数据库连接**: - - 如果需要保存数据,确认数据库连接是否正常 - -## 注意事项 - -1. **API限制**: - - 中国福利彩票官网可能有访问频率限制 - - 建议不要频繁调用接口 - -2. **数据保存**: - - 只有在明确设置 `saveToDatabase: true` 时才会保存数据 - - 测试时建议先不保存数据 - -3. **错误处理**: - - 所有接口都有完善的错误处理 - - 错误信息会记录在日志中 - -4. **性能考虑**: - - 大量数据获取可能需要较长时间 - - 建议分批获取历史数据 \ No newline at end of file diff --git a/src/DFApp.Application/Background/GasolinePriceRefreshWorker.cs b/src/DFApp.Application/Background/GasolinePriceRefreshWorker.cs index d2ba2fea..19cd9165 100644 --- a/src/DFApp.Application/Background/GasolinePriceRefreshWorker.cs +++ b/src/DFApp.Application/Background/GasolinePriceRefreshWorker.cs @@ -9,14 +9,14 @@ namespace DFApp.Background { public class GasolinePriceRefreshWorker : QuartzBackgroundWorkerBase { - private readonly IGasolinePriceService _gasolinePriceService; + private readonly GasolinePriceRefresher _gasolinePriceRefresher; private readonly ILogger _logger; public GasolinePriceRefreshWorker( - IGasolinePriceService gasolinePriceService, + GasolinePriceRefresher gasolinePriceRefresher, ILogger logger) { - _gasolinePriceService = gasolinePriceService; + _gasolinePriceRefresher = gasolinePriceRefresher; _logger = logger; JobDetail = JobBuilder @@ -38,7 +38,7 @@ public override async Task Execute(IJobExecutionContext context) try { - await _gasolinePriceService.RefreshGasolinePricesAsync(); + await _gasolinePriceRefresher.RefreshGasolinePricesAsync(); _logger.LogInformation("油价刷新任务执行成功"); } diff --git a/src/DFApp.Application/ElectricVehicle/GasolinePriceRefresher.cs b/src/DFApp.Application/ElectricVehicle/GasolinePriceRefresher.cs new file mode 100644 index 00000000..8b773087 --- /dev/null +++ b/src/DFApp.Application/ElectricVehicle/GasolinePriceRefresher.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using DFApp.ElectricVehicle; +using DFApp.Configuration; +using Microsoft.Extensions.Logging; +using Volo.Abp; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.DependencyInjection; + +namespace DFApp.ElectricVehicle +{ + public class GasolinePriceRefresher : ITransientDependency + { + private readonly IRepository _repository; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfigurationInfoRepository _configurationInfoRepository; + private readonly ILogger _logger; + + public GasolinePriceRefresher( + IRepository repository, + IHttpClientFactory httpClientFactory, + IConfigurationInfoRepository configurationInfoRepository, + ILogger logger) + { + _repository = repository; + _httpClientFactory = httpClientFactory; + _configurationInfoRepository = configurationInfoRepository; + _logger = logger; + } + + public async Task RefreshGasolinePricesAsync() + { + _logger.LogInformation("开始刷新油价数据(全部省份)"); + + try + { + string apiKey; + try + { + apiKey = await _configurationInfoRepository.GetConfigurationInfoValue("GasPriceApiKey", "DFApp.ElectricVehicle"); + if (string.IsNullOrWhiteSpace(apiKey)) + { + throw new UserFriendlyException("未配置油价API Key,请在系统配置中添加 GasPriceApiKey"); + } + } + catch + { + throw new UserFriendlyException("未配置油价API Key,请在系统配置中添加 GasPriceApiKey"); + } + + var apiUrl = $"https://api.tanshuapi.com/api/youjia/v1/index?key={apiKey}"; + + using var httpClient = _httpClientFactory.CreateClient(); + var response = await httpClient.GetAsync(apiUrl); + + if (!response.IsSuccessStatusCode) + { + throw new UserFriendlyException($"获取油价失败:HTTP {response.StatusCode}"); + } + + var json = await response.Content.ReadAsStringAsync(); + var apiResponse = JsonSerializer.Deserialize(json); + + if (apiResponse?.code != 1) + { + throw new UserFriendlyException($"获取油价失败:{apiResponse?.msg}"); + } + + _logger.LogInformation("API返回数据:{Count} 条", apiResponse?.data?.list?.Count); + + var savedCount = 0; + var repository = (IGasolinePriceRepository)_repository; + + foreach (var item in apiResponse.data.list) + { + var existing = await repository.GetPriceByDateAsync(item.province, DateTime.Parse(item.date)); + + if (existing == null) + { + var gasolinePrice = new GasolinePrice + { + Province = item.province, + Date = DateTime.Parse(item.date), + Price0H = ParseDecimal(item.price0h), + Price89H = ParseDecimal(item.price89h), + Price90H = ParseDecimal(item.price90h), + Price92H = ParseDecimal(item.price92h), + Price93H = ParseDecimal(item.price93h), + Price95H = ParseDecimal(item.price95h), + Price97H = ParseDecimal(item.price97h), + Price98H = ParseDecimal(item.price98h) + }; + + await _repository.InsertAsync(gasolinePrice); + savedCount++; + _logger.LogInformation("保存油价数据:{Province} {Date}", item.province, item.date); + } + else + { + _logger.LogDebug("油价数据已存在:{Province} {Date}", item.province, item.date); + } + } + + _logger.LogInformation("油价刷新完成,新增 {Count} 条记录", savedCount); + } + catch (UserFriendlyException) + { + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "刷新油价失败"); + throw new UserFriendlyException("刷新油价失败:" + ex.Message); + } + } + + private decimal? ParseDecimal(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + if (decimal.TryParse(value, out var result)) + return result; + + return null; + } + } + + public class TanshuApiResponse + { + public int code { get; set; } + public string msg { get; set; } + public TanshuApiData data { get; set; } + } + + public class TanshuApiData + { + public List list { get; set; } + } + + public class TanshuApiPriceItem + { + public string date { get; set; } + public string province { get; set; } + [JsonPropertyName("0h")] + public string price0h { get; set; } + [JsonPropertyName("89h")] + public string price89h { get; set; } + [JsonPropertyName("90h")] + public string price90h { get; set; } + [JsonPropertyName("92h")] + public string price92h { get; set; } + [JsonPropertyName("93h")] + public string price93h { get; set; } + [JsonPropertyName("95h")] + public string price95h { get; set; } + [JsonPropertyName("97h")] + public string price97h { get; set; } + [JsonPropertyName("98h")] + public string price98h { get; set; } + } +} diff --git a/src/DFApp.Application/ElectricVehicle/GasolinePriceService.cs b/src/DFApp.Application/ElectricVehicle/GasolinePriceService.cs index 2de93a39..cb20d8c1 100644 --- a/src/DFApp.Application/ElectricVehicle/GasolinePriceService.cs +++ b/src/DFApp.Application/ElectricVehicle/GasolinePriceService.cs @@ -1,16 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading.Tasks; using DFApp.ElectricVehicle; using DFApp.Permissions; -using DFApp.Configuration; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; -using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; @@ -21,20 +16,17 @@ namespace DFApp.ElectricVehicle public class GasolinePriceService : ApplicationService, IGasolinePriceService { private readonly IRepository _repository; - private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfigurationInfoRepository _configurationInfoRepository; private readonly ILogger _logger; + private readonly GasolinePriceRefresher _gasolinePriceRefresher; public GasolinePriceService( IRepository repository, - IHttpClientFactory httpClientFactory, - IConfigurationInfoRepository configurationInfoRepository, - ILogger logger) + ILogger logger, + GasolinePriceRefresher gasolinePriceRefresher) { _repository = repository; - _httpClientFactory = httpClientFactory; - _configurationInfoRepository = configurationInfoRepository; _logger = logger; + _gasolinePriceRefresher = gasolinePriceRefresher; } public async Task GetLatestPriceAsync(string province) @@ -84,137 +76,18 @@ public async Task> GetListAsync(GetGasolinePric public async Task RefreshGasolinePricesAsync() { - _logger.LogInformation("开始刷新油价数据(全部省份)"); - - try - { - // 1. 从配置获取API Key - string apiKey; - try - { - apiKey = await _configurationInfoRepository.GetConfigurationInfoValue("GasPriceApiKey", "DFApp.ElectricVehicle"); - if (string.IsNullOrWhiteSpace(apiKey)) - { - throw new UserFriendlyException("未配置油价API Key,请在系统配置中添加 GasPriceApiKey"); - } - } - catch - { - throw new UserFriendlyException("未配置油价API Key,请在系统配置中添加 GasPriceApiKey"); - } - - // 2. 调用API(API返回全部省份的数据) - var apiUrl = $"https://api.tanshuapi.com/api/youjia/v1/index?key={apiKey}"; - - using var httpClient = _httpClientFactory.CreateClient(); - var response = await httpClient.GetAsync(apiUrl); - - if (!response.IsSuccessStatusCode) - { - throw new UserFriendlyException($"获取油价失败:HTTP {response.StatusCode}"); - } - - var json = await response.Content.ReadAsStringAsync(); - var apiResponse = JsonSerializer.Deserialize(json); - - if (apiResponse?.code != 1) - { - throw new UserFriendlyException($"获取油价失败:{apiResponse?.msg}"); - } - - _logger.LogInformation("API返回数据:{Count} 条", apiResponse?.data?.list?.Count); - - // 3. 保存到数据库 - var savedCount = 0; - var repository = (IGasolinePriceRepository)_repository; - - foreach (var item in apiResponse.data.list) - { - // 检查是否已存在 - var existing = await repository.GetPriceByDateAsync(item.province, DateTime.Parse(item.date)); - - if (existing == null) - { - var gasolinePrice = new GasolinePrice - { - Province = item.province, - Date = DateTime.Parse(item.date), - Price0H = ParseDecimal(item.price0h), - Price89H = ParseDecimal(item.price89h), - Price90H = ParseDecimal(item.price90h), - Price92H = ParseDecimal(item.price92h), - Price93H = ParseDecimal(item.price93h), - Price95H = ParseDecimal(item.price95h), - Price97H = ParseDecimal(item.price97h), - Price98H = ParseDecimal(item.price98h) - }; - - await _repository.InsertAsync(gasolinePrice); - savedCount++; - _logger.LogInformation("保存油价数据:{Province} {Date}", item.province, item.date); - } - else - { - _logger.LogDebug("油价数据已存在:{Province} {Date}", item.province, item.date); - } - } - - _logger.LogInformation("油价刷新完成,新增 {Count} 条记录", savedCount); - } - catch (UserFriendlyException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "刷新油价失败"); - throw new UserFriendlyException("刷新油价失败:" + ex.Message); - } + await _gasolinePriceRefresher.RefreshGasolinePricesAsync(); } private decimal? ParseDecimal(string value) { if (string.IsNullOrWhiteSpace(value)) return null; - + if (decimal.TryParse(value, out var result)) return result; - + return null; } } - - public class TanshuApiResponse - { - public int code { get; set; } - public string msg { get; set; } - public TanshuApiData data { get; set; } - } - - public class TanshuApiData - { - public List list { get; set; } - } - - public class TanshuApiPriceItem - { - public string date { get; set; } - public string province { get; set; } - [JsonPropertyName("0h")] - public string price0h { get; set; } - [JsonPropertyName("89h")] - public string price89h { get; set; } - [JsonPropertyName("90h")] - public string price90h { get; set; } - [JsonPropertyName("92h")] - public string price92h { get; set; } - [JsonPropertyName("93h")] - public string price93h { get; set; } - [JsonPropertyName("95h")] - public string price95h { get; set; } - [JsonPropertyName("97h")] - public string price97h { get; set; } - [JsonPropertyName("98h")] - public string price98h { get; set; } - } } diff --git a/src/DFApp.Web/Program.cs b/src/DFApp.Web/Program.cs index 5a652f09..e7eca347 100644 --- a/src/DFApp.Web/Program.cs +++ b/src/DFApp.Web/Program.cs @@ -18,8 +18,10 @@ public async static Task Main(string[] args) #else .MinimumLevel.Information() #endif - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) .WriteTo.Async(c => c.Console()) diff --git a/test-electric-vehicle.sh b/test-electric-vehicle.sh deleted file mode 100755 index 46babdfd..00000000 --- a/test-electric-vehicle.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash - -# 电动车成本跟踪功能测试脚本 - -BASE_URL="https://localhost:44369" - -echo "=====================================" -echo "电动车成本跟踪功能测试" -echo "=====================================" -echo "" - -# 1. 获取访问令牌 -echo "1. 获取访问令牌..." -TOKEN=$(curl -k -s -X POST "${BASE_URL}/connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password&client_id=DFApp_Web&client_secret=X!*l}4Ab[K~um%I*#2&username=test&password=1q2w3E*" \ - | jq -r '.access_token') - -if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then - echo "❌ 获取令牌失败" - exit 1 -fi - -echo "✅ 获取令牌成功" -echo "" - -# 2. 获取车辆列表 -echo "2. 获取车辆列表..." -curl -k -s -X GET "${BASE_URL}/api/app/electric-vehicle" \ - -H "Authorization: Bearer $TOKEN" \ - | jq '.totalCount, .items[].{name, brand, totalMileage}' 2>/dev/null || echo "暂无车辆" -echo "" - -# 3. 创建车辆 -echo "3. 创建新车..." -VEHICLE_RESPONSE=$(curl -k -s -X POST "${BASE_URL}/api/app/electric-vehicle" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "测试车辆", - "brand": "比亚迪", - "model": "汉EV", - "licensePlate": "京A88888", - "batteryCapacity": 85.4, - "totalMileage": 10000, - "remark": "测试车辆" - }') - -VEHICLE_ID=$(echo $VEHICLE_RESPONSE | jq -r '.id') -VEHICLE_NAME=$(echo $VEHICLE_RESPONSE | jq -r '.name') - -if [ "$VEHICLE_ID" != "null" ] && [ -n "$VEHICLE_ID" ]; then - echo "✅ 车辆创建成功: $VEHICLE_NAME (ID: $VEHICLE_ID)" -else - echo "❌ 车辆创建失败" - echo $VEHICLE_RESPONSE | jq . -fi -echo "" - -# 4. 创建成本记录 -echo "4. 创建成本记录..." -if [ "$VEHICLE_ID" != "null" ] && [ -n "$VEHICLE_ID" ]; then - COST_RESPONSE=$(curl -k -s -X POST "${BASE_URL}/api/app/electric-vehicle-cost" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"vehicleId\": \"$VEHICLE_ID\", - \"costType\": 1, - \"costDate\": \"$(date +%Y-%m-%d)\", - \"amount\": 500.00, - \"isBelongToSelf\": true, - \"remark\": \"充电费用\" - }") - - COST_ID=$(echo $COST_RESPONSE | jq -r '.id') - - if [ "$COST_ID" != "null" ] && [ -n "$COST_ID" ]; then - echo "✅ 成本记录创建成功 (ID: $COST_ID)" - else - echo "❌ 成本记录创建失败" - fi -else - echo "⚠️ 跳过成本记录创建(车辆ID无效)" -fi -echo "" - -# 5. 创建充电记录 -echo "5. 创建充电记录..." -if [ "$VEHICLE_ID" != "null" ] && [ -n "$VEHICLE_ID" ]; then - CHARGING_RESPONSE=$(curl -k -s -X POST "${BASE_URL}/api/app/electric-vehicle-charging-record" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"vehicleId\": \"$VEHICLE_ID\", - \"chargingDate\": \"$(date +%Y-%m-%d)\", - \"stationName\": \"国家电网充电站\", - \"chargingDuration\": 60, - \"energy\": 45.5, - \"amount\": 150.00, - \"startSOC\": 20, - \"endSOC\": 80, - \"isBelongToSelf\": true - }") - - CHARGING_ID=$(echo $CHARGING_RESPONSE | jq -r '.id') - - if [ "$CHARGING_ID" != "null" ] && [ -n "$CHARGING_ID" ]; then - echo "✅ 充电记录创建成功 (ID: $CHARGING_ID)" - else - echo "❌ 充电记录创建失败" - fi -else - echo "⚠️ 跳过充电记录创建(车辆ID无效)" -fi -echo "" - -# 6. 获取成本记录列表 -echo "6. 获取成本记录列表..." -curl -k -s -X GET "${BASE_URL}/api/app/electric-vehicle-cost" \ - -H "Authorization: Bearer $TOKEN" \ - | jq '.totalCount, .items[] | {costDate, amount, costType}' 2>/dev/null || echo "暂无成本记录" -echo "" - -# 7. 获取油价信息 -echo "7. 获取油价信息..." -GASOLINE_RESPONSE=$(curl -k -s -X GET "${BASE_URL}/api/app/gasoline-price/latest-price?province=山东" \ - -H "Authorization: Bearer $TOKEN") - -echo $GASOLINE_RESPONSE | jq '. | {province, date, price92H, price95H, price98H}' 2>/dev/null || echo "暂无油价数据" -echo "" - -# 8. 油电对比 -echo "8. 油电对比分析..." -COMPARISON_RESPONSE=$(curl -k -s -X GET "${BASE_URL}/api/app/electric-vehicle-cost/oil-cost-comparison?startDate=$(date -d '30 days ago' +%Y-%m-%d)&endDate=$(date +%Y-%m-%d)" \ - -H "Authorization: Bearer $TOKEN") - -echo $COMPARISON_RESPONSE | jq '. | { - electricVehicleTotalCost, - electricVehicleMileage, - electricVehicleCostPerKm, - oilVehicleTotalCost, - savings, - savingsPercentage -}' 2>/dev/null || echo "暂无对比数据" -echo "" - -# 9. 清理测试数据 -echo "9. 清理测试数据..." - -if [ "$COST_ID" != "null" ] && [ -n "$COST_ID" ]; then - curl -k -s -X DELETE "${BASE_URL}/api/app/electric-vehicle-cost/$COST_ID" \ - -H "Authorization: Bearer $TOKEN" >/dev/null 2>&1 - echo "✅ 成本记录已删除" -fi - -if [ "$CHARGING_ID" != "null" ] && [ -n "$CHARGING_ID" ]; then - curl -k -s -X DELETE "${BASE_URL}/api/app/electric-vehicle-charging-record/$CHARGING_ID" \ - -H "Authorization: Bearer $TOKEN" >/dev/null 2>&1 - echo "✅ 充电记录已删除" -fi - -if [ "$VEHICLE_ID" != "null" ] && [ -n "$VEHICLE_ID" ]; then - curl -k -s -X DELETE "${BASE_URL}/api/app/electric-vehicle/$VEHICLE_ID" \ - -H "Authorization: Bearer $TOKEN" >/dev/null 2>&1 - echo "✅ 车辆已删除" -fi - -echo "" -echo "=====================================" -echo "测试完成!" -echo "=====================================" -echo "" -echo "访问地址:" -echo " 前端: http://localhost:8848" -echo " 后端 Swagger: ${BASE_URL}/swagger" -echo " 油电对比页面: http://localhost:8848/electric-vehicle/statistics" -echo ""