Frp(fast reverse proxy)是一个开源的内网穿透工具:一台有公网 IP 的服务器跑 frps,内网机器跑 frpc,两者维持一条长连接,公网用户访问服务器端口时,流量经这条连接转发到内网服务。典型场景:在家里的机器上跑了个 Web 服务或 SSH,想从外面访问。 配置文件使用 TOML 格式(frps.toml / frpc.toml,v0.52+ 起 INI 格式已废弃)。 服务端 frps 公网服务器上解压 frp 发行包,编辑 frps.toml: bindPort = 7000 # frpc 连接用的端口 auth.token = "一个足够长的随机密码" # 客户端必须填同一个值 webServer.addr = "0.0.0.0" # 内置监控面板 webServer.port = 7500 webServer.user = "admin" webServer.password = "面板密码" vhostHTTPPort = 8080 # HTTP 代理的入口端口,按需开启 用 systemd 托管,/etc/systemd/system/frps.service: [Unit] Description = frp server After = network.target [Service] Type = simple ExecStart = /opt/frp/frps -c /opt/frp/frps.toml Restart = on-failure RestartSec = 5 [Install] WantedBy = multi-user.target sudo systemctl enable --now frps 记得在云厂商的安全组里放行 7000(frpc 连接)、7500(面板)以及后续代理用到的端口——这是最常见的"连不上"原因。 客户端 frpc 内网机器上编辑 frpc.toml: ...
.NET 10 新增功能概览
类库方面的改进 用字符串比较数字排序 在 .NET 10 中,System.String 类新增了 CompareAsNumbers 方法,用于按数字顺序比较字符串。这对于包含数字的字符串排序非常有用,例如文件名或版本号。 StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering); Console.WriteLine(numericStringComparer.Equals("02", "2")); // Output: True foreach (string os in new[] { "Windows 8", "Windows 10", "Windows 11" }.Order(numericStringComparer)) { Console.WriteLine(os); } // Output: // Windows 8 // Windows 10 // Windows 11 HashSet<string> set = new HashSet<string>(numericStringComparer) { "007" }; Console.WriteLine(set.Contains("7")); // Output: True 对十六进制字符串转换的 UTF-8 支持 .NET 10 在 Convert 类中增加了对十六进制字符串转换操作的 UTF-8 支持。 这些新方法提供了在 UTF-8 字节序列和十六进制表示形式之间转换的有效方法,而无需中间字符串分配: Convert.FromHexString(ReadOnlySpan<Byte>) Convert.FromHexString(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32) Convert.TryToHexString(ReadOnlySpan<Byte>, Span<Byte>, Int32) Convert.TryToHexStringLower(ReadOnlySpan<Byte>, Span<Byte>, Int32) Convert.TryToHexStringUpper(ReadOnlySpan<Byte>, Span<Byte>, Int32) 禁止重复 JSON 属性的选项 JSON 规范不指定在反序列化 JSON 有效负载时如何处理重复属性。 这可能会导致意外的结果和安全漏洞。NET 10 引入了 JsonSerializerOptions.AllowDuplicateProperties 禁止重复 JSON 属性的选项。 ...
Blazor JavaScript 互操作性
关于 JavaScript 位置 在 <head> 标记中加载脚本 <head> <script src="js/first.js"></script> <script> window.jsMethod = (methodParameter) => { ... }; </script> </head> 在 <body> 标记中加载脚本 <body> <script src="js/first.js"></script> <script> window.jsMethod = (methodParameter) => { ... }; </script> </body> 组件并置 组件:CountComponent.razor 组件:CountComponent.razor.cs 样式:CountComponent.razor.css 脚本:CountComponent.razor.js 从.NET 调用 JS 在 wwwroot 下创建 app.js 文件 function helloWorld() { alert('Hello World'); } function add(a, b) { return a + b; } 在 App.razor 中引入 JS 文件 当前 Web 项目脚本引入 <head> <script src="@Assets["app.js"]"></script> </head> 如果使用了 RCL 组件库 <head> <script src="_content/LibraryName/app.js"></script> </head> 在 .NET 组件中调用 JS 方法 @inject IJSRuntime JS <button class="btn btn-primary" @onclick="SayHello">SayHello</button> <button class="btn btn-primary" @onclick="Add">Add</button> <p>Current count: @result</p> @code { private int result = 0; private async Task SayHello() { await JS.InvokeVoidAsync("helloWorld"); } private async Task Add() { result = await JS.InvokeAsync<int>("add", result, 1); StateHasChanged(); } } 基于模块化的 JS 文件 export function helloWorld() { alert('Hello World'); } export function add(a, b) { return a + b; } 在 MyComponent.razor 组件中写如下代码 ...
Blazor 表单验证
使用数据注解 DataAnnotations 验证 public class Employee { [Required(ErrorMessage = "Name is required")] public string Name { get; set; } } @page "/employee" @using System.ComponentModel.DataAnnotations @using Microsoft.AspNetCore.Components.Forms <h3>Employee Form</h3> <EditForm Model="@employee" OnValidSubmit="@HandleValidSubmit"> <DataAnnotationsValidator /> <ValidationSummary /> <div class="form-group"> <label for="Name">Name</label> <InputText id="Name" @bind-Value="employee.Name" class="form-control" /> <ValidationMessage For="@(() => employee.Name)" /> </div> <button type="submit" class="btn btn-primary">Submit</button> </EditForm> @code { private Employee employee = new Employee(); } 使用 FluentValidation 验证 dotnet add package Blazored.FluentValidation public class EmployeeValidator : AbstractValidator<Employee> { public EmployeeValidator() { RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required"); } } @page "/employee" @using FluentValidation @using FluentValidation.Results <h3>Employee Form</h3> <EditForm Model="@employee" OnValidSubmit="@HandleValidSubmit"> <FluentValidationValidator DisableAssemblyScanning="@true" /> <ValidationSummary /> <div class="form-group"> <label for="Name">Name</label> <InputText id="Name" @bind-Value="employee.Name" class="form-control" /> <ValidationMessage For="@(() => employee.Name)" /> </div> <button type="submit" class="btn btn-primary">Submit</button> </EditForm> 自定义验证
Blazor 全球化与本地化
Blazor 应用程序可以通过使用 .NET Core 的全球化和本地化功能来支持多种语言和文化。本文将介绍如何在 Blazor 应用程序中实现全球化和本地化。 Blazor WebAssembly <PropertyGroup> <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> </PropertyGroup> dotnet add package Microsoft.Extensions.Localization Blazor Server builder.Services.AddCustomLocalization(); app.UseCustomLocalization();
C# 12 中的新增功能
主构造函数 C# 12 中的一个新功能是主构造函数。主构造函数是一个类的构造函数,它在类的声明中声明,而不是在类的主体中声明。主构造函数的参数可以用于初始化类的属性。 记录类型 public record Address(string FirstName, string LastName); 类类型 public class Person(string firstName, string lastName) { public override string ToString() { return $"{firstName},{lastName}"; } } public Person(string firstName) : this(firstName, "hello") { } 集合表达式 C# 12 中的另一个新功能是集合表达式。集合表达式是一种新的语法,用于初始化集合。集合表达式使用大括号,其中包含一个或多个元素初始化器。每个元素初始化器都是一个表达式,它可以是一个值,也可以是一个键值对。 // Create an array: int[] a = [1, 2, 3, 4, 5, 6, 7, 8]; // Create a list: List<string> b = ["one", "two", "three"]; // Create a span Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i']; // Create a jagged 2D array: int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; // Create a jagged 2D array from variables: int[] row0 = [1, 2, 3]; int[] row1 = [4, 5, 6]; int[] row2 = [7, 8, 9]; int[][] twoDFromVariables = [row0, row1, row2]; 数组索引范围 数组的 0 索引与 sequence[0] 相同。 ^0 索引与 sequence[sequence.Length] 相同。 表达式 sequence[^0] 会引发异常,就像 sequence[sequence.Length] 一样。 对于任何数字 n,索引 ^n 与 sequence.Length - n 相同。 ...
Model Binding
模型绑定最佳实践 实体对象是 EF 中的概念, 每个实体对象对应数据库中的一张表。模型对象是 MVC 中的概念,是 HTTP 请求和响应的数据结构。HTTP 请求中通过 URL 参数、表单、标头、 JSON 数据等方式传递数据,这些数据最终会被绑定为模型对象中,模型经过转换为实体对象后,被持久化到数据库中。 public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public class ProductModel { public string Name { get; set; } public decimal Price { get; set; } } public class ProductController : Controller { private readonly ShopDbContext _context; public ProductController(ShopDbContext context) { _context = context; } [HttpPost] public async Task<IActionResult> Create(ProductModel model) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var product = new Product { Name = model.Name, Price = model.Price }; _context.Products.Add(product); await _context.SaveChangesAsync(); return CreatedAtAction(nameof(Get), new { id = product.Id }, product); } } 针对不同 HTTP 操作,应该使用不同的模型对象,比如创建模型、更新模型、查询模型、删除模型等,这样的好处是可以更好的区分模型对象的职责,比如创建模型只需要包含创建所需的字段,更新模型只需要包含更新所需的字段,查询模型只需要包含查询所需的字段,删除模型只需要包含删除所需的字段,职责单一,维护性好,还可以针对不同的模型提供不同的验证规则。
PostgreSQL 高可用性和读写分离
基本概念 一个操作系统可以安装多个 PostgreSQL 实例,每个实例都有自己的配置文件、数据目录和端口号。每个实例中可以创建多个数据库,每个数据库中可以创建多个表。使用 Docker 可以方便的创建多个 PostgreSQL 实例。 安装主从服务器 docker run --name postgres1 -e POSTGRES_PASSWORD=postgres -d -p 5431:5432 -v ${pwd}/postgres1/data:/var/lib/postgresql/data postgres docker run --name postgres2 -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 -v ${pwd}/postgres2/data:/var/lib/postgresql/data postgres 逻辑复制 逻辑复制是根据复制标识(通常是主键)复制数据对象及其更改的一种方法。 我们使用术语逻辑与物理复制相比,逻辑复制使用发布和订阅模型, 其中一个或多个订阅者订阅发布者节点上的一个或多个发布。 订阅者从他们订阅的发布中提取数据, 并可能随后重新发布数据以允许级联复制或更复杂的配置。 创建主数据库和从数据库 在 postgres1 中创建数据库 mydb1 数据库。 CREATE DATABASE mydb1; 在 postgres2 中创建数据库 mydb2 数据库。 CREATE DATABASE mydb2; 在 mydb1 和 mydb2 数据库中创建结构相同的表 mytable。 CREATE TABLE mytable (id int PRIMARY KEY, name text NOT NULL); 将服务器日志级别设置为逻辑 在 postgres1 的 mydb1 数据库中设置服务器日志级别为逻辑。 ...
RabbitMQ Management
rabbitmq-plugins enable rabbitmq_management Default User:guest/guest Add User:test/test http://192.168.0.202:15672 http://mqtt.test:15672 rabbitmq-plugins enable rabbitmq_mqtt rabbitmq-plugins disable rabbitmq_mqtt rabbitmq-plugins enable rabbitmq_web_mqtt rabbitmq-plugins disable rabbitmq_web_mqtt rabbitmq-plugins enable_feature_flag all rabbitmq-plugins disable_feature_flag all rabbitmq-plugins enable rabbitmq_auth_backend_http 修改 rabbitmq.conf 配置文件,位置 %AppData%\RabbitMQ auth_backends.1 = http auth_http.http_method = post auth_http.user_path = http://some-server/api/RabbitMqAuth/User auth_http.vhost_path = http://some-server/api/RabbitMqAuth/Vhost auth_http.resource_path = http://some-server/api/RabbitMqAuth/Resource auth_http.topic_path = http://some-server/api/RabbitMqAuth/Topic RabbitMQ server to perform authentication
Readme
朋友们,零度新一代基架今日正式开始搭建。我们非常激动地宣布,新一代基架起名为 HelloShop 项目, 这个基架将演示新一代 .NET 技术栈开发架构,之所以起名为 HelloShop 是以为简单的商店应用能够演示出一个系统的所有技术,当然,这个商城系统也可以是其它系统,只要你能够理解这个基架的设计思想,你就能够快速搭建一个系统,以下文档由 AI 生成,可能会有一些语法错误,我们会在后期进行修正。 业务场景 本商店应用旨在演示一个系统的所有技术,帮助开发者以最小的依赖和代码搭建一个系统,将不同的技术置于微服务架构中。开发者可以根据自己的业务需求快速复用代码并搭建一个系统,而不是花费大量时间在一个通用的框架上。 我们的目标是提供细粒度的微服务架构和最佳实践,让您可以自行组合各种技术。统一的身份认证和授权系统,服务发现,负载均衡,容错,分布式跟踪,分布式缓存,分布式事务,分布式消息,分布式日志,分布式配置,分布式定时任务等等。 此外,我们还提供了界面,包括基于 Blazor 的 Web 应用和基于 MAUI 的混合应用,可以在不同的平台上运行,包括桌面应用,安卓应用和 IOS 应用。 {class=“img-responsive w-100”} 所含技术 Visual Studio 2022、 .NET 8、C# 12.0、Aspire、ASP.NET Core,EF Core、WebApi、gPRC、Blazor、MAUI、PostgreSQL、MonngDB、Redis、SignalR、Identity、Orleans、日志记录、密钥管理、后台定时任务、服务发现、发布订阅、分布式跟踪、健康检查、性能指标探测、配置管理、容器化技术、单元测试和集成测试、基于 Roslyn 的源代码自动生成器和增量生成器。 仓库结构 在仓库的结构上,我们跟随了微软惯用的风格,目录结构如下: assets 静态资源,包括图片,图标,视频,音频等。 build 构建脚本,包括编译脚本,打包脚本,发布脚本等。 docs 相关文档,包括设计文档,架构文档,开发文档,部署文档等。` samples 演示示例,包括代码示例,配置示例,数据示例,文档示例等。 src 源代码,包括源代码,配置文件,资源文件,脚本文件等。 tests 测试代码,包括单元测试,集成测试,端到端测试等。 tools 项目所使用的一些工具。 代码仓库 国外仓库:https://github.com/bit365/hello-shop{target="_blank"} 国内仓库:https://gitee.com/bit365/hello-shop{target="_blank"} 后续计划 我们刚刚还注册了 helloshopnet.com 和 helloshopnet.cn 两个域名用于后期的基架部署,也算是给 HelloShop 安一个家,目前还是正在备案。 技术探讨 本套基架会有一些配套视频在 www.xcode.me 上发布。如果你想要获取最新的视频可以关注我们的微信订阅号 zerostack,我们会在公众号上发布最新的动态,如果你觉得这套基架对你有帮助,你可以给我们一个 Star,这是对我们最大的鼓励。 最后我们希望这套基架能帮助到你,如果你有任何问题可以在 Github 上提出我们会尽快回复,坚持优雅的编码是我们一直所追求的,希望你也能够参与这种优雅。