AI 大模型与智能应用
实践与技术

专注分享 AI 大模型、Agent 开发、深度学习等技术实战与架构设计;兼具 C#/.NET 后端开发经验

118
技术文章
8
文章分类
537
技术标签
29971
累计字数
.NET Core Web API 过滤器完全指南:类型、用途与实战
后端开发
2024-06-27 • 0k

.NET Core Web API 过滤器完全指南:类型、用途与实战

在 .NET Core Web API 开发中,过滤器(Filter)是实现横切关注点解耦的核心组件。它能在请求处理的不同阶段拦截逻辑,统一处理权限验证、日志记录、异常捕获等通用需求,避免重复代码。本文将详细拆解 .NET Core Web API 内置的 5 类过滤器,分析其执行机制、核心用途,并提供可直接复用的实战示例。 一、过滤器核心概念:什么是过滤器? 过滤器是 .NET Core 提供的请求拦截机制,本质是一套 “钩子(Hook)” 系统,能嵌入请求处理生命周期的关键节点。其核心价值在于: 解耦横切关注点:将日志、权限、异常处理等通用逻辑与业务逻辑分离; 统一管控:对所有接口或指定接口批量应用通用规则; 可扩展性:支持自定义过滤器,满足复杂业务场景。 所有过滤器遵循固定的执行顺序,从请求进入到响应返回,完整流程如下: 授权过滤器 → 资源过滤器 → 模型绑定 → Action 过滤器 → 执行 Action → Action 过滤器 → 结果过滤器 → 执行结果 → 结果过滤器 → 资源过滤器 → 响应返回 若任意阶段抛出异常,将直接触发异常过滤器处理。 二、5 类内置过滤器详解:用途 + 执行时机 1. 授权过滤器(AuthorizationFilter):请求的 “第一道关卡” 核心作用 验证请求是否有权限访问接口,是所有过滤器中执行最早的组件。若验证失败,直接终止请求并返回 401(未授权)或 403(禁止访问),不执行后续逻辑。 内置实现 [Authorize]:标记需要授权的 Action/Controller,依赖 IAuthorizationService 校验身份(配合 JWT、Cookie 等认证方案)。支持角色、策略等精细化授权,示例: // 仅 Admin 角色可访问 [Authorize(Roles = "Admin")] [HttpGet("admin/data")] public IActionResult GetAdminData() => Ok("管理员数据"); [AllowAnonymous]:允许匿名访问,用于公开接口(如登录、注册),会跳过授权校验: [AllowAnonymous] [HttpPost("login")] public IActionResult Login(string username, string password) => Ok("登录成功"); 关键特点 仅关注 “是否允许访问”,不处理业务逻辑;

dotnet webapi filter
阅读更多
string 类型深度解析:是值类型还是引用类型?
后端开发
2024-06-27 • 0k

string 类型深度解析:是值类型还是引用类型?

在 .NET 开发中,string 类型是我们日常使用最频繁的类型之一。但一个常见的疑问始终困扰着不少开发者:string 到底是值类型还是引用类型?其实答案很明确 ——string 本质是引用类型,但它兼具部分值类型的特性,这种特殊性也让它成为了 .NET 类型系统中极具代表性的存在。本文将从本质定义、特殊特性到实际验证,全面解析 string 类型的本质。 一、核心结论:string 是引用类型 从 .NET 类型系统的底层设计来看,string 完全满足引用类型的所有定义,这是不可动摇的核心事实。 1. 引用类型的核心判定依据 继承关系:string 直接继承自 System.Object(引用类型的基类),而非值类型专属的 System.ValueType; 存储位置:string 对象的实际内容存储在 托管堆 中,变量仅持有指向堆内存的 “引用地址”(类似指针); 可空性:string 变量默认支持赋值为 null(表示不指向任何堆内存对象),而值类型(如 int、DateTime)默认非空,需通过 Nullable<T>(如 int?)才能支持 null; 引用传递特性:当把一个 string 变量赋值给另一个变量时,传递的是 “引用地址”,而非字符串内容的拷贝。 2. 代码验证:引用类型的本质 // 1. 变量 a 指向堆内存中的 "hello" 对象 string a = "hello"; // 2. 变量 b 拷贝的是 a 的引用地址,而非 "hello" 内容 string b = a; // 验证:a 和 b 指向同一个堆内存对象 Console.WriteLine(object.ReferenceEquals(a, b)); // 输出 True(引用地址相同) Console.WriteLine(a == b); // 输出 True(字符串重载了 ==,比较内容而非引用,但此处因引用相同导致内容相同) 从结果可以看出,a 和 b 持有相同的引用地址,指向同一个堆内存对象,这是引用类型的典型特征。

string 值类型 引用类型
阅读更多
RNN隐藏状态全解析:它到底是什么?传给谁?何时输出?
人工智能
2024-05-29 • 0k

RNN隐藏状态全解析:它到底是什么?传给谁?何时输出?

在学习循环神经网络(RNN)时,很多初学者在推导数据流时会产生一系列连环疑问: “当前时间步输出的隐藏状态,和传给下一个时间步的信息,是同一份数据吗?” “既然传给下一步了,那当前步的隐藏状态还留着干啥?” “每一个时间步都需要把隐藏状态接上 Softmax 输出预测结果吗?” 这些问题直击 RNN 的核心架构。今天,我们就把传统 RNN(Vanilla RNN)掰开揉碎,从本质、去向、输出时机三个维度,彻底理清隐藏状态(Hidden State)的前世今生。 一、 破除迷思:传给下一步的信息 和 隐藏状态 是同一个东西吗? 直接给出结论:在传统 RNN 中,它们是完全相等的,就是同一份数据(同一个张量)。 传统 RNN 的核心递推公式为: $$h_t = \tanh(W_{xh} x_t + W_{hh} h_{t-1} + b_h)$$ 在这个公式中,计算出的 $h_t$ 只有一个。它既被定义为“当前时间步的隐藏状态”,又在下一个时间步 $t+1$ 的计算中,原封不动地作为 $h_{t}$(即下一步眼中的 $h_{t-1}$)传入。不存在任何额外的变换或门控机制来区分这两者。 ⚠️ 重要区分:LSTM 不是这样 这种“输出 = 下一步输入”的特性仅适用于传统 RNN。在 LSTM 中,有两条状态线:细胞状态(Cell State, $c_t$)和隐藏状态(Hidden State, $h_t$)。$c_t$ 是真正的“记忆传送带”传给下一步,而 $h_t$ 是经过输出门过滤后的版本用于对外输出。所以在 LSTM 中,两者是不相等的。 二、 隐藏状态的“双重使命”:它到底留着干啥? 既然“传给下一步的信息”和“隐藏状态”是同一个东西,那为什么还要专门叫它“隐藏状态”?它计算出来后到底被拿去干嘛了? 这里需要破除一个思维误区:它们不是两个东西,而是“同一个东西”的两个去向。 当 $h_t$ 被计算出来后,它面临着 “一分为二”的双重使命: 使命 1:向后传(时间维度)—— 作为“记忆” 它会被原封不动地传递给下一个时间步 $t+1$。这是 RNN 能够处理序列数据、拥有“记忆”的核心原因。

RNN 隐藏状态 深度学习
阅读更多
上位机开发入门:概念、技术栈,以及与 MES / ERP / WMS 的关系
嵌入式
2024-05-12 • 0k

上位机开发入门:概念、技术栈,以及与 MES / ERP / WMS 的关系

在工业自动化和智能制造项目中,“上位机"并不是孤立存在的。它既直接连接设备,也会和 MES、ERP、WMS 等系统发生数据交互。下面这篇博客会把上位机开发讲清楚,并把它们的关系压缩到核心部分。 什么是上位机开发? 上位机开发,通常指为电脑、工控机或服务器编写软件,用来和 PLC、单片机、传感器、仪表、机器人等下位机设备通信。 它的典型能力包括: 数据采集:读取设备状态、工艺参数、传感器数据。 设备控制:下发启停、参数调整、配方切换等指令。 状态监控:用界面展示产线、设备、工序运行情况。 报警记录:记录故障、异常、停机原因。 报表与追溯:生成产量、工时、质量、能耗等报表。 可以把它理解为:上位机更贴近设备和现场,负责"看得见、控得住、采得准”。 上位机开发的常见技术栈 模块 常用方案 作用 语言/框架 C# + WinForms/WPF/.NET、C++ + Qt、Python + PyQt/PySide C# 在国内工业上位机中较常见;Qt 跨平台性能好;Python 适合原型和数据分析。 通信协议 Modbus、OPC UA、TCP/IP、串口 RS232/485、CAN、MQTT 与 PLC、仪表、传感器、机器人等设备交换数据。 数据处理 实时曲线、报警、日志、报表 处理高频数据、断线重连、异常恢复。 数据存储 SQLite、MySQL、SQL Server、InfluxDB 配置数据用关系型数据库,传感器时序数据可用时序数据库。 软件架构 多线程/异步、MVVM、分层架构 保证界面不卡顿、通信稳定、后期易维护。 一句话选型: Windows 工业项目:优先 C#/.NET。 跨平台或高性能场景:选 C++/Qt。 快速验证、算法分析:选 Python。 MES / ERP / WMS 是什么? 系统 定位 一句话理解 ERP 企业经营管理层 管订单、计划、采购、财务、库存账务。 MES 车间执行层 管工单、工序、报工、质量、生产进度。 WMS 仓储作业层 管入库、出库、库位、批次、库存流转。 上位机 设备控制与监控层 连接 PLC/传感器,做实时采集、界面监控和设备控制。 简单比喻:

上位机 工业自动化 MES
阅读更多
分布式系统并发控制:从单机锁到分布式锁的深度解析
架构与设计
2024-05-06 • 0k

分布式系统并发控制:从单机锁到分布式锁的深度解析

引言 在分布式系统开发中,并发控制是一个永恒的话题。最近在代码评审时,我看到了这样一段代码: // 复杂操作仍需 lock public void ComplexOperation() { lock (_lockObj) { if (_count > 0 && _count < 100) { _count *= 2; // 其他复杂业务逻辑... } } } 这引发了一系列深入的讨论:如果锁里面执行的是用户下单操作,A用户下单过程中执行了1秒,B用户的请求会怎样?是直接失败还是等待?我们需要手动处理这种等待吗? 本文将围绕这个问题展开,深入探讨单机锁、细粒度锁、数据库事务和Redis分布式锁的优缺点及适用场景。 第一部分:单机锁的行为分析 锁的基本行为 当我们在C#中使用lock语句时,CLR会为我们管理一个等待队列: public void PlaceOrder(Order order) { lock (_lockObj) // 所有用户下单都串行化! { // 复杂的下单逻辑,耗时1秒... ProcessOrder(order); UpdateInventory(); // 其他业务... } } 关键结论:B用户会等待A用户执行完,而不是直接失败。整个过程是自动的,不需要人工干预。 具体执行流程 A用户进入lock块,开始执行下单操作(耗时1秒) B用户的线程到达lock语句时,发现_lockObj已被锁定 B线程会阻塞并等待,直到A线程释放锁 A用户执行完毕,释放锁 B线程获得锁,开始执行自己的下单操作 单机锁的严重问题 虽然锁机制自动处理了同步,但这种设计存在严重问题: 性能瓶颈:所有用户请求串行处理 响应时间变长:B用户需要等待A用户的1秒 + 自己的1秒 = 至少2秒 系统吞吐量下降:无法充分利用多核CPU优势 第二部分:解决方案对比 🥉 方案一:细粒度锁 private readonly Dictionary<string, object> _userLocks = new(); private readonly object _dictLock = new object(); public void PlaceOrder(string userId, Order order) { object userLock; lock (_dictLock) { if (!_userLocks.TryGetValue(userId, out userLock)) { userLock = new object(); _userLocks[userId] = userLock; } } // 只锁同一用户的操作 lock (userLock) { ProcessOrder(order); UpdateInventory(); } } 优点:

分布式系统 并发控制 锁机制
阅读更多
一文读懂 RNN:循环神经网络原理与结构解析
人工智能
2024-04-04 • 0k

一文读懂 RNN:循环神经网络原理与结构解析

大家好!今天我们来聊聊深度学习中一个非常有趣且重要的概念——循环神经网络(Recurrent Neural Network,简称RNN)。 如果你之前了解过普通的神经网络(比如用来识别图片的CNN),你可能会发现它们有一个共同的弱点:记性不好。它们处理每一张图片时都是独立的,不会记得上一张图片是什么。但是,在处理语言、语音或者股票走势这种有前后顺序的数据时,“记忆”就变得至关重要了。 这就轮到RNN登场了!让我们结合这张结构图,用最通俗的语言来拆解它。 1. RNN的核心思想:时间就是生命 请看图片左侧那个绿色的方块链条。这就是RNN最直观的形态。 普通神经网络:像是一个个独立的工人,每个人只负责处理手头的任务,做完就忘,不跟下一个人交流。 RNN:像是一条流水线上的工人团队。第二个工人不仅拿到了新的原料(输入数据),还收到了第一个工人传过来的“半成品”或“笔记”(隐藏状态)。 在图中,我们可以看到三个绿色的大方块,分别标记为: 第一个时间步 第二个时间步 第三个时间步 这代表了时间的流逝。RNN不仅仅是在处理空间上的数据,它还在处理时间序列。每一个绿色方块其实代表的是同一个神经网络层(Neural Network Layer) 在不同时间点的状态。这就是为什么叫“循环”神经网络——它在时间轴上自我循环。 2. 深入内部:到底发生了什么? 让我们把目光聚焦在中间那个最详细的绿色方块(第二个时间步)上。这里展示了RNN工作的核心秘密。 两个输入,一个输出 在这个时间步里,神经网络层 A 接收了两股信息流: 本次输入数据 ($X_t$):这是当前时刻的新信息。比如在翻译句子时,这就是当前读到的那个单词。 上次时间步的隐藏状态 ($h_{t-1}$):这是从上一个绿色方块传过来的箭头。这代表了“过去的记忆”。 记忆的传递 注意看那个向上的箭头,指向紫色的圆圈 $h_t$。 $h_{t-1}$ 是过去的记忆。 $X_t$ 是现在的信息。 经过神经网络层 A 的处理(通常包含一个激活函数,如图中黄色的 tanh),生成了 $h_t$。 $h_t$ 既包含了现在的信息,也融合了过去的记忆。然后,它会作为“这次时间步的隐藏状态”,传递给下一个时间步(右边的绿色方块),同时也可能输出给外界(比如预测下一个词是什么)。 简单总结:RNN通过把上一步的计算结果传给下一步,从而实现了对历史信息的“记忆”。 3. 数学公式大揭秘(别怕,很简单!) 图片右上角给出了RNN核心的数学公式: $$h_t = \tanh(W_t[X_t, h_{t-1}] + b_t)$$ 看着复杂,其实拆开看就像做一道菜: $X_t$ (本次输入数据):这是主料。 $h_{t-1}$ (上次时间步的隐藏状态):这是上一顿剩下的老汤,用来提味(提供上下文)。 $[X_t, h_{t-1}]$ (Concatenate):图中的红色箭头汇聚在一起,表示把主料和老汤拼接在一起。 $W_t$ (权重矩阵):这是厨师的调味手法,决定了我们多重视新输入,多重视旧记忆。 $b_t$ (偏置矩阵):这是一个基础底味调整。 $\tanh$ (激活函数):这是最后的烹饪工序(比如烘烤),把数据压缩到 -1 到 1 之间,防止数值爆炸,并引入非线性特征。 $h_t$ (本次时间步的隐藏状态):出锅的美味佳肴,既可以直接吃(输出结果),也可以留一部分做下一顿的老汤(传给下一步)。 4. 图解符号指南 为了看懂这类专业的神经网络图,图片下方给出了一个非常实用的“图例字典”:

RNN 循环神经网络 深度学习
阅读更多
从JSONP到CORS:跨域解决方案的技术演进与架构思考
前端开发
2024-02-12 • 0k

从JSONP到CORS:跨域解决方案的技术演进与架构思考

在Web开发领域,CORS(跨源资源共享)是每个前端开发者都会遇到的关键概念。但为什么我们如今使用CORS,而曾经流行的JSONP却逐渐退出历史舞台?本文将带您深入理解跨域解决方案从JSONP到CORS的技术演进历程。 第一部分:理解CORS - 跨域资源共享 什么是CORS? CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器强制执行的一种安全机制,用于解决"同源策略"带来的限制。 同源策略要求协议、域名、端口完全相同的请求才被允许。例如: https://www.example.com → https://api.example.com ❌ 跨域 http://localhost:3000 → http://localhost:8080 ❌ 跨域 CORS的工作原理 当浏览器检测到跨域请求时,会执行以下流程: 发送预检请求:对于非简单请求,浏览器先发送OPTIONS请求 服务器响应:后端返回包含CORS头部的响应 浏览器验证:浏览器检查头部,决定是否放行 实际请求:验证通过后发送真正的请求 # 预检请求 OPTIONS /api/data HTTP/1.1 Origin: https://www.example.com Access-Control-Request-Method: POST # 服务器响应 HTTP/1.1 200 OK Access-Control-Allow-Origin: https://www.example.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: Content-Type 为什么配置服务端CORS就能解决问题? 关键在于:CORS的决定权在服务器,浏览器只是执行者。 当您在.NET服务端配置CORS时,有多种方式: ASP.NET Core 全局配置: // Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("https://www.example.com") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); var app = builder.Build(); app.UseCors("AllowSpecificOrigin"); 控制器级别配置: [ApiController] [Route("api/[controller]")] [EnableCors("AllowSpecificOrigin")] public class UsersController : ControllerBase { [HttpGet] public IActionResult GetUsers() { var users = new[] { "John", "Jane", "Bob" }; return Ok(users); } } Action级别配置:

CORS JSONP 跨域
阅读更多
PyTorch GPU 配置指南:CUDA 安装与环境验证
人工智能
2024-02-09 • 0k

PyTorch GPU 配置指南:CUDA 安装与环境验证

PyTorch 作为主流的深度学习框架,在安装时严格区分了 CPU 版本与 GPU 版本。这两个版本对应不同的底层二进制分发包,并非仅通过代码参数进行区分。在实际开发与部署中,准确判断当前环境并进行相应的版本切换是保障模型训练与推理效率的基础。 一、 验证当前 PyTorch 版本类型 确认已安装版本的最直接方式是通过 Python 代码进行检测。通过导入 torch 库并调用相关函数,可以获取当前的版本信息及硬件加速状态: import torch print("PyTorch版本:", torch.__version__) print("CUDA是否可用:", torch.cuda.is_available()) print("CUDA版本:", torch.version.cuda) 在输出结果中,若 torch.cuda.is_available() 返回 True,且版本号包含 +cuXXX 后缀(如 2.1.0+cu118),则表明当前为 GPU 版本;若返回 False,或版本号包含 +cpu 后缀,则表明当前为纯 CPU 版本。此外,也可在终端使用 pip show torch 命令,通过查看包版本号的后缀来快速判断。 二、 从 CPU 版本切换至 GPU 版本 若检测到当前为 CPU 版本且需要启用硬件加速,需执行卸载与重装操作。首先,通过 pip uninstall torch torchvision torchaudio -y 命令彻底卸载现有的 CPU 版本。 随后,需确认系统显卡驱动所支持的最高 CUDA 版本(可通过终端执行 nvidia-smi 命令查看)。根据查到的 CUDA 版本,从 PyTorch 官方源拉取对应的 GPU 版本进行安装。例如,对于支持 CUDA 12.1 及以上版本的显卡,可执行以下命令:

PyTorch CUDA GPU加速
阅读更多
DDD领域驱动设计全面解析:.NET核心概念与落地实践指南
架构与设计
2024-01-24 • 0k

DDD领域驱动设计全面解析:.NET核心概念与落地实践指南

DDD领域驱动设计全面解析:.NET核心概念与落地实践指南 领域驱动设计(Domain-Driven Design,简称 DDD)是一种以业务领域为核心的现代化软件架构设计方法论,专注于解决复杂企业级业务系统的设计与开发难题。在当今微服务架构和分布式系统盛行的环境下,DDD已成为构建高内聚、低耦合系统的最佳实践。本文将深入剖析DDD核心概念,结合.NET技术栈全面讲解DDD落地实践,帮助开发者掌握企业级应用架构设计精髓。 .NET 作为成熟稳定的企业级开发平台,天然支持 DDD 领域驱动设计的核心诉求 —— 无论是分层架构、面向对象设计,还是依赖注入、领域模型映射等,都能通过 ASP.NET Core、EF Core、MediatR 等 .NET 生态工具无缝落地。本文将从 DDD 领域驱动设计的核心思想出发,结合 .NET 实战场景,系统讲解 DDD 关键概念、架构分层及落地技巧,提供完整的企业应用架构解决方案。 一、DDD领域驱动设计核心思想:业务驱动的架构哲学 DDD领域驱动设计的本质是业务驱动而非技术驱动,所有设计决策都围绕业务需求展开,在 .NET 企业应用开发中,核心遵循三大原则: 领域优先:业务逻辑(领域模型)是系统的核心资产,技术细节(数据库、API、缓存等)仅作为辅助实现; 边界清晰:通过「限界上下文」划分业务边界,解决复杂系统中的 “知识混乱”,避免不同业务模块的概念冲突; 迭代演进:通过「领域语言(Ubiquitous Language)」统一团队(开发、产品、测试)认知,领域模型随业务迭代持续优化。 二、DDD核心概念详解与.NET落地实践 DDD 的概念体系可分为「领域层核心概念」「架构分层」「辅助概念」三类,以下结合 .NET 代码示例和生态工具逐一说明。 (一)领域层核心概念:DDD业务逻辑的核心载体 领域层是 DDD 的 “心脏”,包含业务模型、规则和行为,不依赖任何外部技术框架,纯业务逻辑封装。 1. 领域模型(Domain Model)- DDD核心设计元素 领域模型是业务概念的代码映射,分为两种设计风格: 贫血模型:仅包含属性和 getter/setter,无业务逻辑(传统 MVC 常用,非 DDD 推荐); 充血模型:包含属性 + 业务逻辑方法(DDD 核心),模型自身封装业务规则,确保数据一致性。 .NET 实战示例(充血模型): // 订单领域模型(充血模型) public class Order { &#x20; // 私有构造函数:强制通过工厂方法创建,保证创建时的业务规则 &#x20; private Order(Guid orderId, Guid userId, List\<OrderItem> items) &#x20; { &#x20; Id = orderId; &#x20; UserId = userId; &#x20; Items = items; &#x20; Status = OrderStatus.Draft; &#x20; TotalAmount = CalculateTotalAmount(); // 创建时自动计算总金额 &#x20; } &#x20; // 领域属性:仅暴露 getter,通过方法修改状态,避免外部随意篡改 &#x20; public Guid Id { get; } &#x20; public Guid UserId { get; } &#x20; public List\<OrderItem> Items { get; } &#x20; public OrderStatus Status { get; private set; } &#x20; public decimal TotalAmount { get; private set; } &#x20; // 业务逻辑方法:订单提交(封装状态变更规则) &#x20; public void Submit() &#x20; { &#x20; if (Status != OrderStatus.Draft) &#x20; throw new InvalidOperationException("只有草稿状态的订单可提交"); &#x20; if (Items == null || !Items.Any()) &#x20; throw new InvalidOperationException("订单不能为空"); &#x20; &#x20; &#x20; Status = OrderStatus.Submitted; &#x20; // 发布领域事件(后续讲解) &#x20; DomainEvents.Raise(new OrderSubmittedEvent(this)); &#x20; } &#x20; // 业务逻辑方法:计算总金额(封装金额计算规则) &#x20; private decimal CalculateTotalAmount() &#x20; { &#x20; return Items.Sum(item => item.Quantity \* item.UnitPrice); &#x20; } &#x20; // 工厂方法:创建订单(封装创建规则,避免外部直接 new 导致规则遗漏) &#x20; public static Order Create(Guid userId, List\<OrderItem> items) &#x20; { &#x20; if (userId == Guid.Empty) &#x20; throw new ArgumentException("用户ID不能为空"); &#x20; if (items == null || !items.Any()) &#x20; throw new ArgumentException("订单商品不能为空"); &#x20; &#x20; &#x20; return new Order(Guid.NewGuid(), userId, items); &#x20; } } // 订单状态(枚举值对象) public enum OrderStatus { &#x20; Draft, // 草稿 &#x20; Submitted, // 已提交 &#x20; Paid, // 已支付 &#x20; Shipped, // 已发货 &#x20; Completed // 已完成 } // 订单项(聚合子对象) public class OrderItem { &#x20; public Guid ProductId { get; } &#x20; public int Quantity { get; } &#x20; public decimal UnitPrice { get; } &#x20; public OrderItem(Guid productId, int quantity, decimal unitPrice) &#x20; { &#x20; if (quantity <= 0) &#x20; throw new ArgumentException("数量必须大于0"); &#x20; if (unitPrice < 0) &#x20; throw new ArgumentException("单价不能为负数"); &#x20; &#x20; &#x20; ProductId = productId; &#x20; Quantity = quantity; &#x20; UnitPrice = unitPrice; &#x20; } } 2. 聚合(Aggregate)与聚合根(Aggregate Root)- DDD一致性边界 问题背景:复杂领域中,多个领域对象可能存在强关联(如订单 + 订单项),直接操作单个对象易破坏数据一致性;

DDD 领域驱动设计 dotnet
阅读更多
天气查询API实现 | 后端开发最佳实践
后端开发
2023-12-25 • 0k

天气查询API实现 | 后端开发最佳实践

1. 天气查询功能介绍 天气查询API在现代Web和移动应用中扮演着重要角色,它可以为用户提供实时天气数据、未来天气预报以及生活指数等关键信息。本文将详细介绍如何实现一个高效、稳定的天气查询服务,涵盖从API选择、异步编程实现到缓存优化的完整开发流程。 2. 天气API服务选择指南 在开发天气查询应用时,选择合适的API服务提供商至关重要。以下是几个主流的天气API服务对比: 和风天气API:国内数据覆盖全面,API文档完善,适合后端开发使用 OpenWeatherMap:全球天气数据服务,国际化支持好 高德地图天气API:国内覆盖较全面,适合本地化应用 百度地图天气API:与百度地图集成良好,适合地图应用场景 本文以和风天气API为例,详细讲解调用天气API的完整实现方案,包括API密钥获取、请求构建、响应解析和错误处理等关键环节。 3. 天气查询API代码实现 3.1 获取和风天气API密钥 在开始天气查询开发前,首先需要在和风天气开发者平台注册账号并获取API密钥。注册流程简单,完成后即可获得免费额度的API访问权限。 3.2 天气查询类实现 using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; public class WeatherAPI { private readonly string _apiKey; private readonly string _baseUrl; private readonly HttpClient _httpClient; public WeatherAPI(string apiKey) { _apiKey = apiKey; _baseUrl = "https://devapi.qweather.com/v7"; _httpClient = new HttpClient(); _httpClient.Timeout = TimeSpan.FromSeconds(10); } /// <summary> /// 实现获取指定位置的实时天气数据 /// 支持城市ID或经纬度查询 /// </summary> public async Task<Dictionary<string, object>> GetCurrentWeatherAsync(string location) { string url = $"{_baseUrl}/weather/now"; var parameters = new Dictionary<string, string> { { "key", _apiKey }, { "location", location } // 可以是城市ID或经纬度 }; try { using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}")) { response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); dynamic data = JsonConvert.DeserializeObject(responseBody); if (data.code == "200") { return data.now.ToObject<Dictionary<string, object>>(); } else { Console.WriteLine($"查询失败: {data.code} - {data.msg}"); return null; } } } catch (Exception ex) { Console.WriteLine($"请求异常: {ex.Message}"); return null; } } /// <summary> ///实现获取指定位置的天气预报数据 /// 支持3天或7天预报查询 /// </summary> public async Task<List<Dictionary<string, object>>> GetWeatherForecastAsync(string location, int days = 3) { string url = days <= 3 ? $"{_baseUrl}/weather/3d" : $"{_baseUrl}/weather/7d"; var parameters = new Dictionary<string, string> { { "key", _apiKey }, { "location", location } }; try { using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}")) { response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); dynamic data = JsonConvert.DeserializeObject(responseBody); if (data.code == "200") { var dailyList = data.daily.ToObject<List<Dictionary<string, object>>>(); return dailyList.GetRange(0, Math.Min(days, dailyList.Count)); } else { Console.WriteLine($"查询失败: {data.code} - {data.msg}"); return new List<Dictionary<string, object>>(); } } } catch (Exception ex) { Console.WriteLine($"请求异常: {ex.Message}"); return new List<Dictionary<string, object>>(); } } /// <summary> ///实现获取指定位置的生活指数数据 /// 包括穿衣、洗车、紫外线、运动、旅游指数 /// </summary> public async Task<List<Dictionary<string, object>>> GetLifeIndexAsync(string location) { string url = $"{_baseUrl}/indices/1d"; var parameters = new Dictionary<string, string> { { "key", _apiKey }, { "location", location }, { "type", "1,2,3,5,9" } // 包括穿衣、洗车、紫外线、运动、旅游指数 }; try { using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}")) { response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); dynamic data = JsonConvert.DeserializeObject(responseBody); if (data.code == "200") { return data.daily.ToObject<List<Dictionary<string, object>>>(); } else { Console.WriteLine($"查询失败: {data.code} - {data.msg}"); return new List<Dictionary<string, object>>(); } } } catch (Exception ex) { Console.WriteLine($"请求异常: {ex.Message}"); return new List<Dictionary<string, object>>(); } } /// <summary> /// 构建查询字符串 /// </summary> private string BuildQueryString(Dictionary<string, string> parameters) { var queryParts = new List<string>(); foreach (var param in parameters) { queryParts.Add($"{param.Key}={System.Net.WebUtility.UrlEncode(param.Value)}"); } return string.Join("&", queryParts); } } 3.3天气查询实例代码示例 // 使用示例 class Program { static async Task Main(string[] args) { // 替换为您的API密钥 string apiKey = "your_api_key_here"; var weatherAPI = new WeatherAPI(apiKey); // 查询北京天气(使用城市ID) string location = "101010100"; // 北京的城市ID // 获取实时天气 var currentWeather = await weatherAPI.GetCurrentWeatherAsync(location); if (currentWeather != null) { Console.WriteLine("实时天气:"); Console.WriteLine($"温度: {currentWeather["temp"]}°C"); Console.WriteLine($"天气: {currentWeather["text"]}"); Console.WriteLine($"风向: {currentWeather["windDir"]} {currentWeather["windScale"]}级"); Console.WriteLine($"湿度: {currentWeather["humidity"]}%"); } // 获取3天预报 var forecast = await weatherAPI.GetWeatherForecastAsync(location); Console.WriteLine("\n未来3天预报:"); foreach (var day in forecast) { Console.WriteLine($"日期: {day["fxDate"]}"); Console.WriteLine($"天气: {day["textDay"]} -> {day["textNight"]}"); Console.WriteLine($"温度: {day["tempMin"]}°C - {day["tempMax"]}°C"); Console.WriteLine(); } // 获取生活指数 var lifeIndices = await weatherAPI.GetLifeIndexAsync(location); Console.WriteLine("\n生活指数:"); foreach (var index in lifeIndices) { Console.WriteLine($"{index["name"]}: {index["level"]} - {index["text"]}"); } } } 4. ASP.NET Core天气查询Web API服务实现 以下是使用ASP.NET Core框架构建高性能天气查询Web API服务的完整实现,支持跨平台部署和高并发访问:

C#天气查询 ASP.NET Core开发 和风天气API调用
阅读更多