(六——下)RestAPI 毛子(Http resilience/Refit/游标分页/异步大文件上传)


文章目录

  • 项目地址
  • 一、Refit
    • 1.1 安装需要的包
    • 1.2 创建接口IGitHubApi
    • 1.3 创建RefitGitHubService
      • 1. 实现接口
      • 2. 注册服务
    • 1.4 修改使用方法
  • 二、Http resilience
    • 2.1 安装所需要的包
    • 2.2 创建resilience pipeline简单版
    • 2.3 创建全局的resilience处理
      • 1. 创建清理全局ResilienceHandler
      • 2. 添加全局resilience
      • 3. 添加自定义的resilience策略
      • 4. 使用自定义策略
  • 三、游标分页
    • 3.1 创建所需要的DTOs
      • 1. 创建游标分页的请求参数
      • 2. 创建CollectionResponse
      • 3. 添加游标编码和解码的DTO
    • 3.2 创建游标查询的Controller
      • 1. 传入带游标的query
      • 2. 对query里的游标解码查询
      • 3. 数据查询逻辑
      • 2.3 测试
  • 四、异步大文件上传
    • 4.1 创建Entities
      • 1. 修改之前的Entry实体
      • 2. EntryImportJob
      • 3. 数据库迁移
    • 4.2 创建DTOs
    • 4.3 创建ProcessEntryImportJob


项目地址

  • 教程作者:
  • 教程地址:
  • 代码仓库地址:
  • 所用到的框架和插件:
dbt 
airflow

一、Refit

可以发起Http请求用于替换我们自己手动写的获取githup外部app服务的方法

1.1 安装需要的包

在这里插入图片描述

1.2 创建接口IGitHubApi

  • 用 Refit 自动帮你生成访问 GitHub API 的客户端

namespace DevHabit.Api.Services;//每次调用这个接口的时候,自动给 HTTP 请求带上这两个头
[Headers("User-Agent: DevHabit/1.0", "Accept: application/vnd.github+json")]
public interface IGitHubApi
{[Get("/user")] //GET 请求,访问的是 GitHub API 的 /user 路径Task<ApiResponse<GitHubUserProfileDto>> GetUserProfile([Authorize(scheme: "Bearer")] string accessToken, //自动添加jwt Token在请求头中CancellationToken cancellationToken = default);[Get("/users/{username}/events")] //GET 请求,访问的是 GitHub API 的 /users/{username}/events 路径Task<ApiResponse<IReadOnlyList<GitHubEventDto>>> GetUserEvents(string username,[Authorize(scheme: "Bearer")] string accessToken, // accessToken自动插到请求头里(带身份认证)int page = 1,[AliasAs("per_page")] int perPage = 100, //告诉RefitGitHub API 要求参数名是 per_page(不是 C# 里的驼峰 PerPageCancellationToken cancellationToken = default);
}

1.3 创建RefitGitHubService

1. 实现接口

  • 实现接口方法的地方
using System.Net.Http.Headers;
using DevHabit.Api.DTOs.GitHub;
using Newtonsoft.Json;
using Refit;namespace DevHabit.Api.Services;public sealed class RefitGitHubService(IGitHubApi gitHubApi, ILogger<GitHubService> logger)
{public async Task<GitHubUserProfileDto?> GetUserProfileAsync(string accessToken,CancellationToken cancellationToken = default){ArgumentException.ThrowIfNullOrEmpty(accessToken);ApiResponse<GitHubUserProfileDto> response = await gitHubApi.GetUserProfile(accessToken, cancellationToken);if (!response.IsSuccessStatusCode){logger.LogWarning("Failed to get user profile from GitHub. Status code: {StatusCode}", response.StatusCode);return null;}return response.Content;}public async Task<IReadOnlyList<GitHubEventDto>?> GetUserEventsAsync(string username,string accessToken,int page = 1,int perPage = 100,CancellationToken cancellationToken = default){ArgumentException.ThrowIfNullOrEmpty(accessToken);ArgumentException.ThrowIfNullOrEmpty(username);ApiResponse<IReadOnlyList<GitHubEventDto>> response =await gitHubApi.GetUserEvents(username,accessToken,page,perPage,cancellationToken);if (!response.IsSuccessStatusCode){logger.LogWarning("Failed to get user events from GitHub. Status code: {StatusCode}", response.StatusCode);return null;}return response.Content;}
}

2. 注册服务

  • 注册该服务在DependencyInjection

在这里插入图片描述

1.4 修改使用方法

  • 替换之前使用githubService方法的Controller
    在这里插入图片描述

二、Http resilience

  • 请求上面接口,有时候会出现问题,我们需要处理请求api接口的问题

2.1 安装所需要的包

在这里插入图片描述

2.2 创建resilience pipeline简单版

  • 直接给需要使用的地方添加,这里我们使用refit获取第三方github的api数据,所以在该服务后面添加
    在这里插入图片描述

2.3 创建全局的resilience处理

1. 创建清理全局ResilienceHandler

  • 如果我们配置了全局resilience,但是部分服务又想执行自己的熔断措施,就需要先清理当前全局的措施,在添加自己的
namespace DevHabit.Api.Extensions;
public static class ResilienceHttpClientBuilderExtensions
{public static IHttpClientBuilder InternalRemoveAllResilienceHandlers(this IHttpClientBuilder builder){builder.ConfigureAdditionalHttpMessageHandlers(static (handlers, _) =>{for (int i = handlers.Count - 1; i >= 0; i--){if (handlers[i] is ResilienceHandler){handlers.RemoveAt(i);}}});return builder;}
}
  • 使用: 在需要清除的服务,先清除
    在这里插入图片描述

2. 添加全局resilience

  • 直接在服务里使用微软的包即可
    在这里插入图片描述

3. 添加自定义的resilience策略

  • 如果上面的包里的方法不够使用,我们可以添加自己的策略;
  • 创建自己的测试策略:在 HttpClient 发送每一个请求前,强制延迟 10 秒再发送。
 namespace DevHabit.Api.Services;public sealed class DelayHandler : DelegatingHandler
{protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken){await Task.Delay(10000, cancellationToken);return await base.SendAsync(request, cancellationToken);}
}

4. 使用自定义策略

在这里插入图片描述

三、游标分页

3.1 创建所需要的DTOs

1. 创建游标分页的请求参数

  • Curor主要是一个index用来记录上一页的位置
namespace DevHabit.Api.DTOs.Entries;
public sealed record EntriesCursorQueryParameters : AcceptHeaderDto
{public string? Cursor { get; init; }public string? Fields { get; init; }public string? HabitId { get; init; }public DateOnly? FromDate { get; init; }public DateOnly? ToDate { get; init; }public EntrySource? Source { get; init; }public bool? IsArchived { get; init; }public int Limit { get; init; } = 10;
}

2. 创建CollectionResponse

  • 该实体用来表示表示含有items和links的实体
namespace DevHabit.Api.DTOs.Common;
public sealed class CollectionResponse<T> : ICollectionResponse<T>, ILinksResponse
{public List<T> Items { get; init; }public List<LinkDto> Links { get; set; }
}
  • 实例化该结构:

在这里插入图片描述

3. 添加游标编码和解码的DTO

  • 将最后一条数据的Id和时间进行base64的编码和解码,防止数据泄密
namespace DevHabit.Api.DTOs.Entries;
public sealed record EntryCursorDto(string Id, DateOnly Date)
{//将一个游标(ID 和时间)编码为字符串,前端分页请求时可用public static string Encode(string id, DateOnly date){var cursor = new EntryCursorDto(id, date); // 创建一个游标对象string json = JsonSerializer.Serialize(cursor); // 序列化为 JSON 字符串return Base64UrlEncoder.Encode(Encoding.UTF8.GetBytes(json)); // 转成 Base64,避免 JSON 暴露或格式错误}public static EntryCursorDto Decode(string? cursor){if (string.IsNullOrWhiteSpace(cursor)){return null;}try{string json = Base64UrlEncoder.Decode(cursor); //解码 Base64 字符串return JsonSerializer.Deserialize<EntryCursorDto>(json); // 反序列化回游标对象}catch{return null;}}
}

3.2 创建游标查询的Controller

流程梳理:

1. 传入带游标的query

在这里插入图片描述

2. 对query里的游标解码查询

  • 如果携带了游标,对游标进行解码,并且根据游标的信息查询数据
    在这里插入图片描述

3. 数据查询逻辑

  1. 获取比Limit多的数据11条
  2. 如果数据大于10条,说明还有下一页
  3. 将最后一条数据的id和Date,编码为下一个游标
  4. 去除掉多余的+1的数据,保证每次10条
  5. 将数据返回给前端
    在这里插入图片描述

2.3 测试

  • 当我们发起一个需要100条数据的请求,如果还有下一页的数据,将会得到有next-page的links
    在这里插入图片描述

四、异步大文件上传

客户端发出请求后,服务器不会马上返回最终结果,而是告诉你正在处理中,让你稍后再来查询最终结果。

  • 实现功能:前端上传页面不需要在上传文件页面等待,可以随时离开该页面;上传的文件会显示状态

4.1 创建Entities

1. 修改之前的Entry实体

  • 给Entry添加一个来源信息 EntrySource

在这里插入图片描述

2. EntryImportJob

  • CSV实体
public sealed class EntryImportJob
{public string Id { get; set; }public string UserId { get; set; } // User who uploaded the filepublic EntryImportStatus Status { get; set; } // Status of the import jobpublic string FileName { get; set; } public byte[] FileContent { get; set; } //上传的文件内容(二进制数组,byte[])public int TotalRecords { get; set; } // Total number of records in the filepublic int ProcessedRecords { get; set; } // Number of records processed so farpublic int SuccessfulRecords { get; set; } // Number of records successfully importedpublic int FailedRecords { get; set; } // Number of records that failed to importpublic List<string> Errors { get; set; } = []; // List of errors encountered during importpublic DateTime CreatedAtUtc { get; set; } //   Creation time of the jobpublic DateTime? CompletedAtUtc { get; set; } // Completion time of the job (if applicable)public static string NewId(){return $"ei_{Guid.CreateVersion7()}";}
}public enum EntryImportStatus
{Pending,Processing,Completed,Failed
}

3. 数据库迁移

  1. 创建表的Configuration
  2. 添加表到数据库上下文
  3. 执行迁移

4.2 创建DTOs

在这里插入图片描述

  • 转换字段
public sealed class CsvEntryRecord
{[Name("habit_id")]public required string HabitId { get; init; } //CSV 文件里叫 "habit_id" 的列,绑定到 HabitId 这个属性。[Name("date")]public required DateOnly Date { get; init; } //CSV 文件里叫 "date" 的列,绑定到 Date 这个属性。[Name("notes")]public string? Notes { get; init; } //CSV 文件里叫 "notes" 的列,绑定到 Notes 这个属性。
}

4.3 创建ProcessEntryImportJob

  • 这是真正处理csv的地方

public sealed class ProcessEntryImportJob(ApplicationDbContext dbContext,ILogger<ProcessEntryImportJob> logger) : IJob //实现Quartz的IJob接口
{//Quartz会调用这个 Executepublic async Task Execute(IJobExecutionContext context){//1.IJobExecutionContext里获取importJobIdstring importJobId = context.MergedJobDataMap.GetString("importJobId")!;//2.从数据库查找对应的导入任务记录EntryImportJob? importJob = await dbContext.EntryImportJobs.FirstOrDefaultAsync(j => j.Id == importJobId);if (importJob is null){logger.LogError("Import job {ImportJobId} not found", importJobId);return;}try{//3. 找到了,就把状态改为 Processing,更改数据库字段importJob.Status = EntryImportStatus.Processing;await dbContext.SaveChangesAsync();using var memoryStream = new MemoryStream(importJob.FileContent);using var reader = new StreamReader(memoryStream);using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);//4.一次性全部加载到内存了(如果文件大,这里有优化空间)var records = csv.GetRecords<CsvEntryRecord>().ToList();//5.获取总条数,并更新到数据库importJob.TotalRecords = records.Count;await dbContext.SaveChangesAsync();foreach (CsvEntryRecord record in records){try{// Validate that the habit exists and belongs to the userHabit? habit = await dbContext.Habits.FirstOrDefaultAsync(h => h.Id == record.HabitId && h.UserId == importJob.UserId);if (habit is null){throw new InvalidOperationException($"Habit with ID '{record.HabitId}' does not exist or does not belong to the user");}var entry = new Entry{Id = Entry.NewId(),UserId = importJob.UserId,HabitId = record.HabitId,Value = habit.Target.Value,Date = record.Date,Notes = record.Notes,Source = EntrySource.FileImport,CreatedAtUtc = DateTime.UtcNow};dbContext.Entries.Add(entry);importJob.SuccessfulRecords++;}catch (Exception ex){importJob.FailedRecords++;importJob.Errors.Add($"Error processing record: {ex.Message}");if (importJob.Errors.Count >= 100){importJob.Errors.Add("Too many errors, stopping error collection...");break;}}finally{importJob.ProcessedRecords++;}// Save progress periodicallyif (importJob.ProcessedRecords % 100 == 0){await dbContext.SaveChangesAsync();}}// Final saveimportJob.Status = EntryImportStatus.Completed;importJob.CompletedAtUtc = DateTime.UtcNow;await dbContext.SaveChangesAsync();}catch (Exception ex){logger.LogError(ex, "Error processing import job {ImportJobId}", importJobId);importJob.Status = EntryImportStatus.Failed;importJob.Errors.Add($"Fatal error: {ex.Message}");importJob.CompletedAtUtc = DateTime.UtcNow;await dbContext.SaveChangesAsync();}}
}
  • 存在问题:
    1. 数据读取在内存中
    2. 数据是一条条写入的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 977. Squares of a Sorted Array

题目描述 双指针法一 用right表示原数组中负数和非负数的分界线。 nums[0,right-1]的是负数&#xff0c;nums[right,nums.size()-1]是非负数。 然后用合并两个有序数组的方法。合并即可。 class Solution { public:vector<int> sortedSquares(vector<int>&…

在 API 模拟阶段:Apipost vs. Faker.js vs. Postman —— 为什么 Apipost 是最优选择

在构建 API 的过程中&#xff0c;模拟数据的能力至关重要。就像你在做饭时等待食材送达一样——没有原料&#xff0c;菜也没法完成。 但是&#xff0c;当你的后端还在开发中&#xff0c;而前端又急需真实的 API 响应进行开发时&#xff0c;该怎么办&#xff1f;这时候&#xf…

一种快速计算OTA PSRR的方法(Ⅰ)

序言:最近碰到了一道有趣的习题&#xff0c;让我重新思考了下如何计算运放的PSRR&#xff0c;再结合相关论文&#xff0c;现将所思所想分享出来&#xff0c;欢迎大家讨论。 1.从Razavi的一道习题引入 题目要求计算电路的PSRR&#xff0c;已知PSRR定义为信号增益除以电源增益&am…

第十二届蓝桥杯 2021 C/C++组 空间

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 思路详解&#xff1a; 代码&#xff1a; 代码详解&#xff1a; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 空间 - 蓝桥云课 思路&#xff1a; 思路详解&#…

TensorFlow深度学习实战——基于循环神经网络的情感分析模型

TensorFlow深度学习实战——基于循环神经网络的情感分析模型 0. 前言1. 数据处理2. 模型构建与训练3. 模型评估相关链接 0. 前言 情感分析 (Sentiment Analysis) 是自然语言处理中的一项技术&#xff0c;旨在识别和提取文本中的情感信息&#xff0c;通常是分析一段文本中是否存…

eslint相关报错收集

[vue/no-multiple-template-root]The template root requires exactly one element.eslint-plugin-vuejsx报错&#xff1a;jsx报错Parsing error: Unexpected token &#xff1c;eslint&#xff1b;ts报错&#xff1a;Parsing error: Unexpected token {eslintmodule报错 ‘mod…

【论文推荐】深度学习赋能地质灾害分析:数据、模型、应用与机遇(用于地质灾害分析的深度学习:数据源)

【论文推荐】深度学习赋能地质灾害分析&#xff1a;数据、模型、应用与机遇&#xff08;用于地质灾害分析的深度学习&#xff1a;数据源&#xff09; 【论文推荐】深度学习赋能地质灾害分析&#xff1a;数据、模型、应用与机遇&#xff08;用于地质灾害分析的深度学习&#xf…

判断用户选择的Excel单元格区域是否跨页?

VBA应用程序开发过程中&#xff0c;经常需要处理用户选中的单元格区域&#xff0c;有的应用场景中&#xff0c;需要限制用户选中区域位于同一页中&#xff08;以打印预览显示的分页划分&#xff09;&#xff0c;但是VBA对象模型中并没有提供相应的接口&#xff0c;用于快速查询…

题解:洛谷 CF2091E Interesting Ratio

思路推导 我们先对 32 32 32 和 96 96 96 进行二进制拆分。 相同部分&#xff08;用 α \alpha α 表示&#xff09;&#xff1a; 5 5 5 个 2 2 2。 不同部分&#xff08;用 β \beta β 表示&#xff09;&#xff1a; 1 1 1 和 3 3 3。 gcd ⁡ ( 32 , 96 ) \gcd(32,9…

linux安装配置PostgreSQL

环境&#xff1a;centos7、SpringBoot、PostgreSQL15 PostgreSQL: Linux downloads (Red Hat family) PostgreSQL安装 1.安装 PostgreSQL Yum 仓库 RPM 包 sudo rpm -ivh https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noar…

docker安装jenkins v2.504.1集群

1 概述 Jenkins是一款开源的、基于Java开发的持续集成&#xff08;CI&#xff09;与持续交付&#xff08;CD&#xff09;工具&#xff0c;旨在通过自动化构建、测试和部署流程&#xff0c;提升软件开发效率与质量。 ‌ 1.1 核心功能与特点 持续集成与交付‌ Jenkins支持自动化…

5月2日日记

今天看了爸爸推荐的书&#xff0c;叫&#xff1a;“高效能人士的七个习惯” 现在刚看完50页&#xff0c;感觉确实有点东西&#xff0c; 七个习惯分别是&#xff1a; 个人层面1积极主动 2要事第一 3以终为始 社交层面 4知彼解己5 统效综合 6双赢思维 7不断更新 目前还没有…

Aws S3上传优化

上传大约 3.4GB 的 JSON 文件&#xff0c;zip算法压缩后约为 395MB&#xff0c;上传至 S3 效率优化&#xff0c;有一些优化方案可以提高上传速率。下面是几种可能的优化方式&#xff0c;包括选择压缩算法、调整上传方式、以及其他可能的方案。 方案 1. 选择更好的压缩算法 压…

CAD(计算机辅助设计)基础知识点整理

以下是CAD&#xff08;计算机辅助设计&#xff09;的基础知识点整理&#xff0c;涵盖核心概念、操作技巧和行业规范&#xff0c;适合新手学习和参考&#xff1a; 一、CAD基本概念 什么是CAD • 利用计算机技术进行设计和绘图的工具&#xff0c;广泛应用于机械、建筑、电子等领…

重构之道:识别并替换不合适使用的箭头函数

1、引言 JavaScript 自 ES6 引入了箭头函数(Arrow Function)后,因其简洁的语法和对 this 的词法绑定机制,迅速成为开发者喜爱的写法之一。然而,并不是所有场景都适合使用箭头函数。 在实际开发中,我们常常会因为追求代码简洁而忽视其潜在问题,例如: this 指向错误不适…

[英语单词] from under

最近在看RCU的资料&#xff0c;读到下面的一句&#xff0c;感觉总是特别怪怪的&#xff0c;就是从单词的组合角度&#xff0c;记录一下。 Use rcu_read_lock() and rcu_read_unlock() to ensure that the structure does not get deleted out from under us。 意思是我们还在使…

Python 中 DAO 层使用泛型的探索

方法一&#xff1a; from types import UnionType from typing import TypeVar, Generic, TypeModelT TypeVar(ModelT)def _new_cls_with_grm_generic_args(cls, __item):new_cls type(f"{cls.__name__}[{__item.__name__}]", (cls,), {})new_cls._grm_generic_ar…

Cesium 环境搭建

一、前提条件 1. **安装 Node.js** - 访问 [Node.js 官方网站](https://nodejs.org/)&#xff0c;下载并安装适合你操作系统的版本。Node.js 用于运行本地开发服务器和安装依赖。 2. **安装 Vue CLI** - Vue CLI 是一个用于快速开发 Vue.js 项目的工具。在终端中运行以下…

DarkGS:论文解读与全流程环境配置及数据集测试【基于Ubuntu20.04 】【2025最新实战无坑版!!】

一、背景及意义 DarkGS是一个创新性的研究项目&#xff0c;旨在解决机器人在黑暗或低光照环境中探索的问题。传统的3D重建和视觉定位系统在光照条件不佳时表现不佳&#xff0c;这严重限制了机器人在黑暗环境中的应用&#xff0c;如夜间救援、深海探索或洞穴勘测等场景。 这项工…

(八)RestAPI 毛子(Unit Testing/Integration)

文章目录 项目地址一、Unit Testing1.1 创建X unit 测试项目1. 创建项目目录2. 管理包1.2 创建CreateEntryDtoValidator测试1.3 创建CreateEntryDtoValidator测试二、Integration test2.1 创建Integration test环境1. 安装所需要的包2.2 配置基础设置1. 数据库链接DevHabitWebA…