一、初始化

WebApplication.CreateBuilder

var builder = WebApplication.CreateBuilder(args);

用于创建一个 Web 应用程序的构建器 (WebApplicationBuilder) 实例。通过这个构建器,你可以配置应用程序的服务和中间件,进而构建和运行你的 ASP.NET Core Web 应用。

builder.WebHost.UseUrls

builder.WebHost.UseUrls("http://*:8080");

用于设置Web服务器所监听的URL和端口。通过这个方法,开发者可以指定应用程序在哪些地址和端口上运行,从而控制外部请求如何到达你的应用程序。

builder.Configuration

builder.Configuration.AddCommandLine(args);

允许你访问应用程序的配置设置,这些设置通常包含在 appsettings.json、环境变量、用户机密、命令行参数等中。builder.Configuration 是 WebApplicationBuilder 的一部分,提供了一种灵活的方式来管理和访问配置。

services.Configure

appsetting.json包含一个secret类型

{  
  "Secret": {  
    "ApiKey": "your-api-key-here",  
    "ConnectionString": "your-connection-string-here"  
  }  
}
public class Secret  
{  
    public string ApiKey { get; set; }  
    public string ConnectionString { get; set; }  
}
builder.Services.Configure<Secret>(builder.Configuration.GetSection("Secret"));  

是一个用于将应用程序配置绑定到强类型配置对象的服务注册方法。通过这种方式,你可以轻松地管理和访问应用程序的配置数据,并能够在整个应用程序中以一致的方式使用这些配置。通常用于将从配置源(如 JSON 文件、环境变量等)获取的设置绑定到特定的类型上。
实际应用:

services.Configure<MyConnectionConFig>(configuration.GetSection("SysConnection"));
_connection = provider.GetRequiredService<IOptions<MyConnectionConFig>>().Value;

services.BuildServiceProvider

例子

using Microsoft.Extensions.DependencyInjection;  
public interface IMyService  
{  
    string GetMessage();  
}  
public class MyService : IMyService  
{  
    public string GetMessage() => "Hello, World!";  
}  
// 创建一个 Service Collection 并注册服务  
var services = new ServiceCollection();  
services.AddSingleton<IMyService, MyService>(); // 注册服务  
// 构建服务提供者  
var serviceProvider = services.BuildServiceProvider();  
// 解析服务  
var myService = serviceProvider.GetService<IMyService>();  
// 使用服务  
Console.WriteLine(myService.GetMessage());

方法用于构建服务提供者(Service Provider),这是完成依赖注入(DI)配置后的一个重要步骤。通过此方法,我们可以生成一个服务提供者实例,该实例可以用于解析已注册的服务。

builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation()

是在 ASP.NET Core 应用程序中注册支持 MVC 控制器和视图的服务,并启用 Razor 视图运行时编译的代码。这对于开发过程中的视图修改非常有用,可以在不重启应用程序的情况下立即查看更改。

builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()

是在 ASP.NET Core 应用程序中用于注册 IHttpContextAccessor 服务的代码。这一行的目的是让你可以在应用程序的各个部分访问当前的 HTTP 上下文。
这是 ASP.NET Core 的接口,允许你从非控制器类中访问当前的 HttpContext。对于需要访问 HTTP 请求信息(如请求头、用户信息等)的服务或类,这个接口非常有用。
IHttpContextAccessor 的实例作为单例服务注册到依赖注入容器中。这意味着在整个应用程序的生命周期中,只有一个实例会被创建和使用。这种注册方式非常适合像 IHttpContextAccessor 这样的服务,因为它不持有状态。

AddScoped

在ASP.NET Core 中,控制器的构造函数会在 “每次 HTTP 请求到达时执行一次”,无论请求方法是 GET、POST 还是其他类型。这与AddScoped注册的服务(如SqlSugarClient)的生命周期完全匹配,确保每个请求都能获得全新的控制器实例和对应的服务实例。
ISqlSugarClient使用AddScoped注册后,在每一次调用控制器里的方法,都会调用一次控制器里的构造函数,并完成注入。即每次请求(GET,POST等)都是新的ISqlSugarClient实例

  • 事务一致性:同一请求内的 SqlSugarClient 实例是共享的,因此 BeginTran() 开启的事务会作用于该请求内的所有操作(包括跨服务、跨方法的调用),确保原子性。
  • 自动释放资源:由于注册时设置了 IsAutoCloseConnection = true,SqlSugarClient 会在请求结束后自动关闭数据库连接,无需手动调用 Close() 或 Dispose()(注入的实例由 DI 容器自动管理生命周期)

初始化

app.UseHttpsRedirection();
添加一个中间件,用于将所有HTTP请求重定向到HTTPS,确保数据传输的安全性
app.UseStaticFiles();
添加一个中间件,用于提供应用程序中的静态文件(如html,css,javascript文件等)的访问
app.UseRouting
添加一个路由中间件,用于解析传入请求的路由信息,并将请求分发到相应的处理程序
app.UseAuthorization();
添加了授权中间件,用于根据请求的路由信息来检查用户是否有权限访问该资源
app.Run();
这行代码用于启动应用程序,并开始监听请求

二、Project

Entity

声明模型,包括DTO(数据传输对象),表格实体等等

Utility

相关的库,包括日志

三、报错

分表标识

现象:“中文提示 : 需要在分表字段加上属性[SplitFieldAttribute]
English Message : [SplitFieldAttribute] need to be added to the table field”
原因:没指定[splitfield]
解决:在CreateTime上面标注[splitfield]属性

在webservice的形参不能设置datacontract

会出现labview无法找到返回的data

"…"doesnothaveanattribute route. ActionMethodsOnController

现象:如图,报错"…"doesnothaveanattribute route. ActionMethodsOnController
在这里插入图片描述
原因:指定了[ApiController]属性的类,存在没指定[Route]属性的方法
解决:所有方法都要指定[Route]属性

btn_task is not define

现象:在点击网页按钮后,控制台出现如题异常
原因:btn_task用了layeropen,url没写对,一定要有参数,不能直接调用
在这里插入图片描述
解决:url要写,并且要带参数

表单提交后提示无信息的异常抖动窗口

现象:如题
原因:提交时候post的地址,有多个
解决:保证只有一个就行

控制器中的形参拿不到视图中Form.Field的值

现象:如题
在这里插入图片描述
原因:AskTargetParam 的成员变量没实现get和set
解决:AskTargetParam 里面的成员变量实现get和set,如下

public class AskTargetParam
{
    //任务号
    public string taskId { get; set; }
    //产品条码
    public string productCode { get; set; }
    //指当前物料的类型
    public string productType { get; set; }
    //起点
    public string start { get; set; }
    //区域点
    public string region { get; set; }
}

运行一段时间后报404

现象:

  1. 页面报404
  2. 重启后web.config消失,不确定是重启后没有,还是重启前就没有
  3. 事件日志→系统目录
    在这里插入图片描述
    在这里插入图片描述
  4. 事件日志→应用程序目录
    在这里插入图片描述
    在这里插入图片描述
    原因:可能是被后台关闭了IIS进程
    排查:
  • web.config添加日志
    在这里插入图片描述
  • 线程池关闭自动回收
    在这里插入图片描述

四、多语言切换

①Resources是资源文件的根目录,要手动创建,如果要针对对应的视图和控制器单独切换,则要在对应路径中创建resx文件,例如

  • Resources/Controllers:控制器资源文件根目录
  • Resources/Views:视图资源文件根目录
  • Resources:SharedResource.resxs根目录
  • 以HomeController为例,需要有三个资源文件
    • Resources/Controllers/HomeController.id-ID.resx
    • Resources/Controllers/HomeController.resx
    • Resources/ControllersHomeController.zh-CN.resx

②SharedResource.resx是全局资源文件,可以用在所有视图和控制器当中。需要提前创建SharedResource类,该类文件可以放在任何地方,里面只需要声明一个空的SharedResource。
在这里插入图片描述
③初始化代码,本次切换三种语言,分别是"en-US", “zh-CN”, “id-ID”

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddMvc().AddViewLocalization()
    .AddDataAnnotationsLocalization();
    builder.Services.AddControllersWithViews()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
var app = builder.Build();
var supportedCultures = new[] { "en-US", "zh-CN", "id-ID" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
//以上代码要在这个之前
app.UseRouting();

④控制器代码

  • SetLanguage是Cookies中会缓存目前的语言,这个是全局切换,一旦切换,后面所有页面都会识别到当前语言
 public class HomeController : BaseController
{
		//此处指定了使用Resources/Controllers/HomeController.resx
     private readonly IStringLocalizer<HomeController> _localizer;
     public HomeController(IStringLocalizer<HomeController> localizer)
		{
				//注入
		    _localizer = localizer;
		}
		//切换语言
		[Route("home/setlanguage")]
		[HttpPost]
		public IActionResult SetLanguage(string culture)
		{
		    // 验证语言参数(只允许支持的语言)
		    var supportedCultures = new[] { "zh-CN", "en-US","id-ID" };
		    if (!supportedCultures.Contains(culture))
		    {
		        culture = "zh-CN"; // 默认为中文
		    }
		
		    // 设置语言Cookie(有效期1年,让浏览器记住偏好)
		    Response.Cookies.Append(
		        CookieRequestCultureProvider.DefaultCookieName,
		        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
		        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
		    );
		
		    // 返回成功响应(前端可根据需要刷新页面或更新内容)
		    return Json(new { success = true });
		}
}

⑤视图代码

  • @Localizer[“Copyright”]会切换到当前语言
  • 调用setlanguage切换语言,切换后,会缓存到cookies,所有页面通用
@inject Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer Localizer

<div class="layui-footer" style="background-color:#FFFFFF">
    @* <a id="footer1">@ViewBag.Copyright</a> *@
    <div class="layui-btn-group">
        <button id="btn-zh" class="layui-btn layui-btn-primary layui-btn-small lang-btn">中文</button>
        <button id="btn-en" class="layui-btn layui-btn-primary layui-btn-small lang-btn">English</button>
        <button id="btn-id" class="layui-btn layui-btn-primary layui-btn-small lang-btn">印尼</button>
    </div>
    <a id="footer1">@Localizer["Copyright"]</a>
</div>

$(".lang-btn").click(function () {
    // 确定要切换的语言(中文:zh-CN,英文:en-US)
    var culture = $(this).attr("id");
    if (culture === "btn-zh")
        culture = "zh-CN";
    else if (culture === "btn-en")
        culture = "en-US";
    else if (culture === "btn-id")
        culture = "id-ID";

    $.ajax({
        url: "/home/setlanguage", // 后端接口地址
        type: "POST",
        data: { culture: culture }, // 传递语言参数
        success: function (response) {
            if (response.success) {
                // 切换成功后刷新页面(使新语言生效)
                location.reload();
                // 若需局部更新而非刷新,可重新加载文本(见下方说明)
                // loadLocalizedText();
            }
        },
        error: function () {
            alert("语言切换失败");
        }
    });
});
  • 如果用html(‘登陆’)括号里的字符,要这样替换html(‘@Localizer[“Copyright”]’),也就是用单引号框起来
  • 中文字符解释为了已&#x6E05,除,要改为‘@Html.Raw(@Localizer[“Copyright”])’
    在这里插入图片描述

资源文件

路径
在这里插入图片描述
在这里插入图片描述

视图代码

@*声明*@
@using Microsoft.Extensions.Localization
@using FNMES.WebUI; 
@inject IStringLocalizer<SharedResource> SharedLocalizer

@*使用*@
<div class="layui-btn-group">
    <button class="layui-btn layui-btn-primary layui-btn-small" onclick="btn_import_param()">@SharedLocalizer["ImportParam"]</button>
    <button class="layui-btn layui-btn-primary layui-btn-small" onclick="btn_export_param()">@SharedLocalizer["ExportParam"]</button>
</div>
  • 此处的ImportParam是写在Resources/SharedResource.resx(SharedResource.zh-CN.resx和SharedResource.id-ID.resx)里的,相当于全局的翻译对照表都写这三个文件里。

bug

title不能是对象

现象:在翻译后报错
在这里插入图片描述
原因:
此处翻译后为[账户管理],原本是Account,由于翻译后只有[账户管理],没有用``包括起来,导致代码认为[账户管理]是一个对象
在这里插入图片描述
解决方法:
在资源文件中修改为Account对应的值是账户管理,那后面翻译后就是如下图,功能就正常了。
在这里插入图片描述

筛选器

在 ASP.NET Core 中,ActionFilterAttribute 是一种过滤器,用于在控制器的动作方法执行前后插入自定义逻辑(如日志记录、参数验证、权限检查等)。它的使用方式确实是继承该类并重写方法,然后通过特性标记应用到控制器或动作方法上

方法名 执行时机 常用场景
OnActionExecuting 动作方法执行之前 参数验证、权限检查、日志记录开始
OnActionExecuted 动作方法执行之后(结果返回之前) 日志记录结束、修改返回结果

中断请求

中断请求的核心:context.Result = …(赋值后,框架会终止后续流程并返回该结果)。

public override void OnActionExecuting(ActionExecutingContext context)
{
    if (!context.ActionArguments.ContainsKey("id"))
    {
        // 关键:设置 Result,告诉框架返回这个结果
        context.Result = new BadRequestObjectResult("缺少 id 参数");
        return; // 可选,只是让当前方法提前退出
    }
    base.OnActionExecuting(context);
}

托管服务

编写BackgroundService类继承的类,因为BackgroundService实现了IHostService接口,并可以处理任务取消等逻辑。只需要实现抽象方法ExecuteAsync。
例如:

1  public class DemoBgService : BackgroundService
2  {
3     private ILogger<DemoBgService> logger;
4     public DemoBgService(ILogger<DemoBgService> logger)
5     {
6        this.logger = logger;
7     }
8     protected override async Task ExecuteAsync(CancellationToken stoppingToken)
9     {
10      await Task.Delay(5000);
11      string s =await File.ReadAllTextAsync("d:/1.txt");
12      await Task.Delay(20000);
13      logger.LogInformation(s);
14    }
15 }

注册托管服务

services.AddHostedService<DemoBgService>();

如果托管服务抛出异常,程序就会自动停止并退出

托管服务的特性

托管服务是以单例的生命周期注册到依赖注入容器中的。按照依赖注入容器的要求,长生命周期的服务不能依赖短生命周期的服务,因此可以在托管服务中通过构造方法注入其他生命周期为单例的服务,但是不能注入生命周期为范围或者瞬态的服务。

1  public class ExplortStatisticBgService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<ExplortStatisticBgService> _logger;

    // 仅注入单例的 IServiceScopeFactory 和 ILogger(ILogger 本身是单例安全的)
    public ExplortStatisticBgService(
        IServiceScopeFactory scopeFactory,
        ILogger<ExplortStatisticBgService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await DoExecuteAsync();
                await Task.Delay(5000, stoppingToken); // 传入取消令牌
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "获取用户统计数据失败");
                await Task.Delay(1000, stoppingToken);
            }
        }
    }

    private async Task DoExecuteAsync()
    {
        // 每次执行任务时,创建新的作用域
        using (var scope = _scopeFactory.CreateScope())
        {
            var sp = scope.ServiceProvider;
            // 从新作用域中获取 DbContext(每次都是全新实例)
            var ctx = sp.GetRequiredService<TestDbContext>();

            var items = ctx.Users
                .GroupBy(u => u.CreationTime.Date)
                .Select(e => new { Date = e.Key, Count = e.Count() });

            var sb = new StringBuilder();
            sb.AppendLine($"Date:{DateTime.Now}");
            foreach (var item in items)
            {
                sb.Append(item.Date).AppendLine($":{item.Count}");
            }
            await File.WriteAllTextAsync("d:/1.txt", sb.ToString());
            _logger.LogInformation("导出完成");
        }
        // 作用域释放时,DbContext 自动释放,连接资源回收
    }
}
  • 避免长期持有 DbContext:每次执行 DoExecuteAsync 时,通过新的作用域获取 DbContext,使用后随作用域一起释放,确保每次操作都是 “干净” 的连接。
  • 自动恢复连接:若数据库临时中断,下次任务会创建新的 DbContext 和连接,只要此时数据库已恢复,就能正常执行(无需重启服务)。
  • 符合生命周期规范:单例服务(托管服务)通过 IServiceScopeFactory 动态创建作用域,间接使用 Scoped 服务(DbContext),避免生命周期冲突。

IServiceScopeFactory

在 .NET Core/.NET 中,IServiceScopeFactory 是框架默认自动注册到依赖注入(DI)容器中的服务,无需手动注册即可直接注入使用。

  • 无需手动注册:不同于自定义服务需要通过 services.AddXXX() 注册,IServiceScopeFactory 由框架自动注册,直接使用即可。
  • 作用:IServiceScopeFactory 的核心作用是创建临时作用域(IServiceScope),在作用域内可以安全获取 Scoped 或 Transient 生命周期的服务(如 DbContext),避免单例服务直接依赖短生命周期服务导致的资源泄漏或状态异常。

长轮询

长轮询(Long Polling)是一种基于 HTTP 的服务器向客户端推送消息的技术,核心机制是:

  1. 客户端主动发起请求:浏览器通过 AJAX 向服务器发送一个 HTTP 请求。
  2. 服务器 “挂起” 请求:服务器收到请求后,不会立即返回响应,而是将请求保持在 “等待状态”(通常会设置一个超时时间)。
  3. 有消息时立即响应:当服务器端有需要推送给客户端的新消息(如实时数据更新、事件通知等)时,会立即将消息作为响应返回给客户端。
  4. 客户端收到响应后重新发起请求:客户端处理完服务器返回的消息后,会立即再次向服务器发送新的长轮询请求,保持 “持续监听” 状态。

现代 Web 框架(如 ASP.NET Core)都推荐使用异步编程模型,通过非阻塞等待减少线程浪费,即使是长轮询也应避免同步阻塞。
服务器通过异步机制高效等待,而非让方法堵塞 30 秒。这比单纯的线程阻塞更高效,但仍会占用连接资源(每个挂起的请求对应一个未关闭的 TCP 连接)。

存在的问题

  • 服务器资源消耗大:每个挂起的请求会占用服务器的连接资源(如线程、端口等),当客户端数量较多时,容易造成服务器资源耗尽。
  • 客户端发送长轮询请求后,会保持连接处于 “等待状态”,不会立即关闭连接,而是持续监听服务器的响应(这和普通 AJAX 请求的 “发送后等待响应” 逻辑一致,只是等待时间更长)。

中间件

我们也可以开发自定义的中间件来提供额外的功能。虽然对于大部分开发人员来讲,不需要开发自定义的中间件
广义来说,中间件指的是系统软件和应用软件之间连接的软件,以便于软件之间的沟通,比如Web服务器、Redis服务器等都可以称作中间件。
狭义来说,ASP.NET Core中的中间件则指里面的一个组件。每个中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码,next为指向下一个中间件的调用,后逻辑为从下一个中间件返回所执行的逻辑代码。每个HTTP请求都要经历一系列中间件的处理,每个中间件对请求进行特定的处理后,再将其转到下一个中间件,最终的业务逻辑代码执行完成后,响应的内容也会按照请求处理的相反顺序进行处理,然后形成HTTP响应报文返回给客户端。

要进行中间件的开发,我们需要先了解3个重要的概念:Map、Use和Run。

1  var builder = WebApplication.CreateBuilder(args);
2  var app = builder.Build();
3  app.Map("/test", async appbuilder => {
4      appbuilder.Use(async (context, next) => {
5          context.Response.ContentType = "text/html";
6          await context.Response.WriteAsync("1  Start<br/>");
7          await next.Invoke();
8          await context.Response.WriteAsync("1  End<br/>");
9      });
10     appbuilder.Use(async (context, next) => {
11         await context.Response.WriteAsync("2  Start<br/>");
12         await next.Invoke();
13         await context.Response.WriteAsync("2  End<br/>");
14     });
15     appbuilder.Run(async ctx => {
16         await ctx.Response.WriteAsync("hello middleware <br/>");
17     });
18 });
19 app.Run();

在这里插入图片描述

发布

发布.NET应用意味着编译源代码以创建可执行文件或二进制文件及其依赖项和相关文件以供分发。发布后,可将应用部署到服务器、分发平台、容器或云环境。发布过程为部署和使用在开发环境之外准备应用。
参考网页:https://learn.microsoft.com/zh-cn/dotnet/core/deploying/?pivots=visualstudio
发布应用有两种主要方法。影响此决策的一些因素包含部署环境是否安装了适当的.NET运行时,以及是否需要将运行时与应用捆绑的特定编译功能。这两种发布模式包括:

  • 发布自包含
    此模式生成一个发布文件夹,其中包括用于启动应用的特定于平台的可执行文件、包含应用代码的已编译二进制文件、任何应用依赖项以及运行应用所需的.NET运行时。运行应用的环境不需要预安装.NET运行时。
  • 发布依赖于框架
    此模式生成一个发布文件夹,其中包含用于启动应用的可选特定于平台的可执行文件、

缓存

配置

public void ConfigureServices(IServiceCollection services)
{
    // 其他服务配置...
    services.AddMemoryCache();
    // 其他服务配置...
}

注入

private readonly IMemoryCache memoryCache;
public OutStationController(IMemoryCache cache)
{
    outStationLogic = new RecordOutStationLogic();
    memoryCache = cache;
}

使用

//获取
if (memoryCache.TryGetValue("LastExportTime", out DateTime lastExportTime))
{
    var timeSinceLastExport = DateTime.UtcNow - lastExportTime;
    if (timeSinceLastExport.TotalMinutes < 1)
    {
        return BadRequest("导出请求过于频繁,请稍后再试");
    }
}
//
memoryCache.Set("LastExportTime", DateTime.UtcNow); // 更新导出缓存时间为当前时间 

缓存策略

我们在选择内存缓存的过期时间策略的时候,如果缓存项的条数不多或者大部分缓存数据被访问的频率都差不多的话,我们可以使用绝对过期时间策略;如果只有部分数据访问频率比较高并且数据库中的数据不会被更新的话,我们可以使用滑动过期时间策略;如果缓存项的数据量比较大且只有其中一部分会被频繁访问,而且数据库中的数据会被更新的话,用绝对过期时间和滑动过期时间混合的策略更合适。

1  logger.LogInformation("开始执行Demo3:" + DateTime.Now);
2  var items = await memCache.GetOrCreateAsync("AllBooks3", async (e) => {
3     e.SlidingExpiration = TimeSpan.FromSeconds(10);
4     e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
5     logger.LogInformation("Demo3从数据库中读取数据");
6     return await dbCtx.Books.ToArrayAsync();
7  });
8  logger.LogInformation("Demo3执行结束");

缓存雪崩问题的规避

过期时间用

通用注册服务框架

public interface IMyServices
{
	void SayHello();
}
public class CnService:IMyService
{
	public void SayHello()
	{
		Console.WriteLine("nihao");
	}
}

要求

条码查重

高效的条码查重设计能直接避免数据库性能瓶颈。核心方案是为条码字段建立唯一索引,配合应用层的预处理校验,实现快速且准确的查重。
一、数据库表结构与索引设计(核心)
SQL Server 中推荐使用 VARCHAR(n) 或 NVARCHAR(n),根据条码是否含中文 / 特殊字符选择:

  • 纯数字 / 字母条码:用 VARCHAR(n)(节省空间),长度根据实际条码类型定义(如 EAN-13 码设为 VARCHAR(13),Code 128 码最长可设为 VARCHAR(100))。
  • 含 Unicode 字符的条码:用 NVARCHAR(n)。
    二、应用层查重逻辑(SQL Server 适配)
  1. 预查重(查询判断)
  2. 插入时捕获异常(更高效)
    三、总结
  • 核心:给 Barcode 字段加唯一约束 / 索引(UNIQUE CONSTRAINT 或 UNIQUE INDEX),确保数据库层强制唯一。
  • 应用层:优先用 “插入 + 捕获 2627 错误” 的方式,减少一次查询。
  • 大数据量:索引包含列、定期整理碎片,超千万级用分区表。

SqlSugar实现

  • 表设计
using SqlSugar;
using System;

[SugarTable("BarcodeData")]  // 映射表名
public class BarcodeData
{
    /// <summary>
    /// 自增主键(若条码不适合作为主键)
    /// </summary>
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }

    /// <summary>
    /// 条码字段(核心,需唯一)
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = false)]  // 非空,长度根据实际调整
    [SugarIndex(IsUnique = true, Name = "IX_Barcode")]  // 唯一索引(SQLSugar会自动创建)
    public string Barcode { get; set; }

    /// <summary>
    /// 其他业务字段(示例)
    /// </summary>
    [SugarColumn(Length = 100)]
    public string ProductName { get; set; }

    /// <summary>
    /// 创建时间
    /// </summary>
    [SugarColumn(IsNullable = false, DefaultValue = "GETDATE()")]
    public DateTime CreateTime { get; set; }
}
  • 查重逻辑
/// <summary>
/// 插入条码,若重复则返回错误
/// </summary>
/// <param name="barcode">条码</param>
/// <param name="productName">商品名</param>
/// <returns>是否成功</returns>
public bool InsertBarcode(string barcode, string productName)
{
    var cleanBarcode = barcode?.Trim();
    if (string.IsNullOrEmpty(cleanBarcode))
        return false;

    try
    {
        // 执行插入
        var result = db.Insertable(new BarcodeData
        {
            Barcode = cleanBarcode,
            ProductName = productName
        }).ExecuteCommand();

        return result > 0;  // 插入成功返回true
    }
    catch (SqlException ex)
    {
        // 捕获SQL Server唯一键冲突错误(错误号2627)
        if (ex.Number == 2627)
        {
            Console.WriteLine("条码已存在(唯一键冲突)");
            return false;
        }
        // 其他异常抛出(如连接错误)
        throw;
    }
}

分表查询(超千万级数据)

  1. 工具类:分表名计算 + 动态建表
using SqlSugar;
using System.Security.Cryptography;
using System.Text;

public static class BarcodeShardingHelper
{
    // 分表数量(可根据数据量调整,如20、50)
    private const int TableCount = 10;
    // 基础表名(所有分表以此为前缀)
    private const string BaseTableName = "BarcodeData_";

    /// <summary>
    /// 根据条码计算目标分表名
    /// </summary>
    public static string GetTargetTableName(string barcode)
    {
        if (string.IsNullOrEmpty(barcode))
            throw new ArgumentNullException(nameof(barcode), "条码不能为空");

        // 1. 取条码前4位(不足4位补0,确保长度一致)
        string barcodePrefix = barcode.PadRight(4, '0').Substring(0, 4);
        // 2. 计算哈希值并对分表数量取余,得到分表后缀
        using (var md5 = MD5.Create())
        {
            byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(barcodePrefix));
            int hashValue = BitConverter.ToInt32(hashBytes, 0);
            int tableSuffix = Math.Abs(hashValue) % TableCount; // 取绝对值避免负后缀
            return $"{BaseTableName}{tableSuffix}";
        }
    }

    /// <summary>
    /// 动态创建分表(若表不存在)
    /// </summary>
    public static void CreateTableIfNotExists(SqlSugarClient db, string tableName)
    {
        // 检查表是否存在
        bool tableExists = db.DbMaintenance.IsAnyTable(tableName);
        if (tableExists) return;

        // 动态创建分表(结构与基础表一致,含唯一索引)
        db.CodeFirst.InitTablesByEntity<BarcodeData>(tableName);
        // 为分表添加条码唯一索引(确保查重)
        db.DbMaintenance.CreateIndex<BarcodeData>(
            indexName: $"IX_{tableName}_Barcode",
            isUnique: true,
            columnNames: new[] { nameof(BarcodeData.Barcode) }
        );
        Console.WriteLine($"分表 {tableName} 创建成功");
    }
}
  1. 数据读写核心逻辑(适配分表)
public class BarcodeService
{
    private readonly SqlSugarClient _db;

    public BarcodeService(SqlSugarClient db)
    {
        _db = db;
    }

    /// <summary>
    /// 分表插入条码(自动定位分表,处理重复)
    /// </summary>
    public bool InsertBarcodeToShard(BarcodeData data)
    {
        if (data == null || string.IsNullOrEmpty(data.Barcode))
            return false;

        // 1. 预处理条码(去空格)
        data.Barcode = data.Barcode.Trim();
        // 2. 计算目标分表名
        string targetTable = BarcodeShardingHelper.GetTargetTableName(data.Barcode);
        // 3. 确保分表存在
        BarcodeShardingHelper.CreateTableIfNotExists(_db, targetTable);

        try
        {
            // 4. 插入目标分表(SQLSugar通过AS指定分表名)
            int result = _db.Insertable(data)
                          .AS(targetTable) // 关键:切换到目标分表
                          .ExecuteCommand();
            return result > 0;
        }
        catch (SqlException ex) when (ex.Number == 2627)
        {
            // 捕获唯一键冲突(条码重复)
            Console.WriteLine($"条码 {data.Barcode} 已存在于分表 {targetTable}");
            return false;
        }
    }

    /// <summary>
    /// 分表查重(快速定位目标表,避免全表扫描)
    /// </summary>
    public bool IsBarcodeExistsInShard(string barcode)
    {
        if (string.IsNullOrEmpty(barcode))
            return false;

        string cleanBarcode = barcode.Trim();
        string targetTable = BarcodeShardingHelper.GetTargetTableName(cleanBarcode);

        // 若分表不存在,直接返回不存在
        if (!_db.DbMaintenance.IsAnyTable(targetTable))
            return false;

        // 仅查询目标分表(效率极高)
        return _db.Queryable<BarcodeData>()
                 .AS(targetTable)
                 .Where(b => b.Barcode == cleanBarcode)
                 .Any();
    }

    /// <summary>
    /// 分表查询条码信息
    /// </summary>
    public BarcodeData GetBarcodeFromShard(string barcode)
    {
        if (string.IsNullOrEmpty(barcode))
            return null;

        string cleanBarcode = barcode.Trim();
        string targetTable = BarcodeShardingHelper.GetTargetTableName(cleanBarcode);

        if (!_db.DbMaintenance.IsAnyTable(targetTable))
            return null;

        // 查询目标分表
        return _db.Queryable<BarcodeData>()
                 .AS(targetTable)
                 .Where(b => b.Barcode == cleanBarcode)
                 .First();
    }
}

笔记

  1. WebAPI 控制器类一般继承自ControllerBase即可,就能够使用ControllerBase中的Response、Request、HttpContext等成员
  2. WebAPI中操作方法可以试 同步也可以是异步。因为异步方法能提升系统的并发吞吐量,所以如果一个操作方法调用的代码有异步调用,那么建议把操作方法声明成异步。另外操作方法一般不会被自身代码直接调用,所以不需要以Async结尾。
  3. Program.cs文件中,可以看到WebApplication.CreateBuilder(args)方法返回的是WebApplicationBuilder类型的对象,而WebApplicationBuilder中的Services属性,就是类型为IServiceCollection的属性,一般把服务注册到这个Services属性即可。
1  var builder = WebApplication.CreateBuilder(args);
2  builder.Services.AddControllers();
3  builder.Services.AddEndpointsApiExplorer();
4  builder.Services.AddSwaggerGen();
5  var app = builder.Build();
  1. 当需要注册服务时候,只是要把注册代码写到builder.Build之前即可。一般来说,服务的注册顺序不会影响程序的运行效果,因此不用关注不同服务注册代码的顺序。
  2. WebApplication的CreateBuilder方法会按照下面顺序提供默认的配置:
  • 加载现有的IConfiguration
  • 加载项目根目录下的appsetting.json
  1. 使用滑动过期时间策略,我们可以保证经常访问的数据长期保存在缓存中,但是如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不过期。一旦发生缓存数据不一致的情况,我们设定了滑动过期时间策略的缓存项就得不到更新了。这种情况下,我们可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定得比滑动过期时间长,这样缓存项会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除。
1  logger.LogInformation("开始执行Demo3:" + DateTime.Now);
2  var items = await memCache.GetOrCreateAsync("AllBooks3", async (e) => {
3     e.SlidingExpiration = TimeSpan.FromSeconds(10);
4     e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
5     logger.LogInformation("Demo3从数据库中读取数据");
6     return await dbCtx.Books.ToArrayAsync();
7  });
8  logger.LogInformation("Demo3执行结束");

我们在选择内存缓存的过期时间策略的时候,如果缓存项的条数不多或者大部分缓存数据被访问的频率都差不多的话,我们可以使用绝对过期时间策略;如果只有部分数据访问频率比较高并且数据库中的数据不会被更新的话,我们可以使用滑动过期时间策略;如果缓存项的数据量比较大且只有其中一部分会被频繁访问,而且数据库中的数据会被更新的话,用绝对过期时间和滑动过期时间混合的策略更合适。
7. 每个挂起的请求对应一个未关闭的 TCP 连接
8. 开发服务的时候,推荐优先实现异步方法(返回Task并使用async/await),尤其是涉及I/O操作的场景。这种方式能充分利用ASP.NET Core的异步非堵塞特性,避免线程堵塞,提升服务的并发能力和稳定性。

using System.Threading.Tasks;
using SqlSugar;
public class MySoapService : IMySoapService
{
    private readonly ISqlSugarClient _db; // 注入 SqlSugar 客户端
    public MySoapService(ISqlSugarClient db)
    {
        _db = db;
    }
    // 同步方法(仅作对比,不推荐用于 I/O 操作)
    public User GetUserSync(int userId)
    {
        // 调用 SqlSugar 同步方法(会阻塞线程)
        return _db.Queryable<User>()
                  .Where(u => u.Id == userId)
                  .First(); // 同步查询
    }
    // 异步方法(推荐:全程异步,不阻塞线程)
    public async Task<User> GetUserAsync(int userId)
    {
        // 调用 SqlSugar 异步方法(await 等待时释放线程)
        return await _db.Queryable<User>()
                       .Where(u => u.Id == userId)
                       .FirstAsync(); // 异步查询(关键)
    }
}
  1. 使用了app.UseHttpsRedirection();,所有的HTTP请求都会被重定向为HTTPS请求。
  2. 正向代理和反向代理
维度 正向代理 反向代理
代理对象 客户端 服务器
客户端是否感知代理 是(需手动配置) 否(以为直接访问服务器)
目标服务器感知对象 只知道代理,不知道真实客户端 只知道代理,不知道真实客户端
核心用途 突破限制、隐藏客户端 负载均衡、隐藏服务器、安全加固
典型工具 VPN Nginx、Apache、F5负载均衡器
举例:
  • 正向代理:在国内想访问Google,通过一个位于境外的代理服务器(正向代理),先连代理,代理再去请求Google,Google只看到代理IP。
  • 方向代理:访问www.taobao.com时,请求会先到淘宝的反向代理服务器如(Nginx),代理再把请求转发给后端的某台应用服务器,用户实际不知道具体是哪台服务器处理了请求。
    简言之,正向代理"帮客户端做事",反向代理"帮服务器做事",两者都是中间层,服务的对象和场景不一样
  1. 点击IIS express,浏览器就会弹出https的地址,要修改为http
  • 在launchSettings.json中去掉sslPort行
{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:32439",
      //"sslPort": 44348
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:8081",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "FNMES.WebUI": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:8080",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
  • 在.vs/config/applicationhost.config中去掉
<bindings>
          <binding protocol="http" bindingInformation="*:32439:localhost" />
          <!-- <binding protocol="https" bindingInformation="*:44348:localhost" /> -->
</bindings>
  • 去掉program.cs中的app.UseHttpsRedirection();
  1. 即便返回的是对象,实际到客户端也是字符串
public class RetMessage<T>
{
    public string messageType { get; set; }
    public string message { get; set; } 
    public T data { get; set; }
}
[Route("web/CheckLink")]
[HttpPost]
public RetMessage<string> CheckLink()
{
    return new();
}
  1. webapi里的返回不能用[DataContract]来标记,不然调用后,会返回空结果{},但是webservice是要的
  2. 插入分表如果不加splittable的话,就会导致报错,错误内容是没有sugartable指明的那个表名
  3. 权限中的排序码规则是五位数,第一位是属于第几个主菜单,第三位是属于子菜单下的第几个,比如说子菜单的排序码为20300,属于第二个主菜单的,在子菜单中排行第三

生词

AOP:Aspect Oriented Programming,面向切面编程
MIME:Multipurpose Internet Mail Extensions,多用途互联网邮件扩展

问题

为什么注册order这么简单,用builder.Services.AddScoped<IOrderService, OrderService>();,注册sqlsugar要这么麻烦builder.Services.AddScoped(…);

答案:注册 IOrderService 简单、注册 ISqlSugarClient 麻烦,核心原因是 两者的 “实例创建逻辑复杂度” 完全不同:IOrderService 是你自定义的 “纯业务服务”,创建逻辑极简;而 ISqlSugarClient 是第三方框架的 “数据库客户端”,创建需要依赖配置、连接参数等额外逻辑,无法直接通过 “接口 - 实现类” 自动映射。

  1. 为什么 IOrderService 注册这么简单?
  • builder.Services.AddScoped<IOrderService, OrderService>() 之所以简洁,是因为它满足 “无参 / 依赖可自动注入” 两个关键条件:
    • 条件 1:实现类构造函数无特殊参数
      你的 OrderService 要么是无参构造(public OrderService() {}),要么依赖的是其他已注册的服务(如 public OrderService(ILogService logService))—— 这些依赖都能被 DI 容器自动找到并注入。
    • 条件 2:接口与实现类是 “直接映射”
      DI 容器不需要额外逻辑,只要看到 IOrderService,就能直接 new OrderService(依赖项) 创建实例,完全是 “按规则自动生成”。
      ▷ 构造函数可以无外部参数,如果要外部参数,可以通过注入
  1. 为什么 ISqlSugarClient 注册这么麻烦?
    ISqlSugarClient 是 SQLSugar 框架提供的 “数据库客户端接口”,它的创建需要 3 个必须的 “非标准配置”,DI 容器无法自动处理,必须手动写逻辑:
    • 需要 “连接字符串”(外部配置,非服务依赖)
    • 需要 “数据库类型、连接策略” 等配置
    • 接口与实现类不是 “直接映射”

AddScoped在添加SqlSugar的时候,里面的方法有什么要求,必须返回的是SqlSugar句柄对象吗?

在 AddScoped 中注册 SqlSugar 时,对注册方法的核心要求是:必须返回 ISqlSugarClient 接口的实例(可以是其实现类 SqlSugarClient 的对象),这是由依赖注入容器的类型匹配规则决定的。

  1. 核心要求:返回类型必须匹配注册的服务类型
    AddScoped(…) 中,泛型参数 ISqlSugarClient 是 “服务类型”,因此注册方法的返回值必须是 ISqlSugarClient 接口的实例(或其实现类的对象,因为实现类兼容接口类型)。
// 正确:返回 SqlSugarClient(ISqlSugarClient 的实现类)
builder.Services.AddScoped<ISqlSugarClient>(sp =>
{
    var config = new ConnectionConfig
    {
        ConnectionString = "你的连接字符串",
        DbType = DbType.SqlServer,
        IsAutoCloseConnection = true
    };
    return new SqlSugarClient(config); // SqlSugarClient 实现了 ISqlSugarClient
});
  1. 对 “创建逻辑” 的要求:必须正确初始化 SqlSugarClient
    除了返回类型,注册方法内部的 “实例创建逻辑” 还需满足 SqlSugar 的初始化要求,否则即使返回了 ISqlSugarClient 实例,也可能无法正常工作。
    ▷ 返回的实例类型要 “对”,实例内部配置要 “全”,这就是注册 SqlSugar 时对方法的核心要求。

sp没有用到,为什么要有

在 AddScoped(sp => { … }) 中,sp(即 IServiceProvider)看似没用到却必须存在,核心原因是 这是依赖注入容器规定的 “工厂方法签名”

addscoped

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SqlSugar;

var builder = WebApplication.CreateBuilder(args);

// 1. 注册 SQLSugar 为 Scoped 服务,必须返回SqlSugarClient
builder.Services.AddScoped<ISqlSugarClient>(sp =>
{
    // 从配置中读取连接字符串
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    // 配置 SQLSugar 连接参数
    var config = new ConnectionConfig
    {
        ConnectionString = connectionString,
        DbType = DbType.SqlServer, // 数据库类型(根据实际修改)
        IsAutoCloseConnection = true, // 自动关闭连接
        InitKeyType = InitKeyType.Attribute // 通过特性初始化主键
    };
    // 创建 SqlSugarClient 实例
    var db = new SqlSugarClient(config);
    // 可选:配置 SQL 执行日志(开发环境调试用)
    db.Aop.OnLogExecuting = (sql, pars) =>
    {
        Console.WriteLine($"SQL: {sql}");
        Console.WriteLine($"参数: {string.Join(", ", pars.Select(p => $"{p.ParameterName}={p.Value}"))}");
    };
    return db;
});

// 2. 注册控制器
builder.Services.AddControllers();
var app = builder.Build();
// 3. 配置中间件
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

AppScoped后,是什么时候可以用ISqlSugarClient?

在 ASP.NET Core 中,当你用 AddScoped(…) 注册服务后,ISqlSugarClient 实例的可用时机与应用的生命周期阶段密切相关,具体可以用的时间点是:
①在 app.Run() 执行之后(应用启动完成,进入请求处理阶段)

[ApiController]
[Route("api/users")]
public class UserController : ControllerBase
{
    private readonly ISqlSugarClient _db;
    // 请求到达时,框架自动创建作用域并注入 ISqlSugarClient 实例
    public UserController(ISqlSugarClient db)
    {
        _db = db; // 此时 _db 已可用(在 app.Run() 之后)
    }
    [HttpGet]
    public IActionResult Get()
    {
        var users = _db.Queryable<User>().ToList(); // 正常使用
        return Ok(users);
    }
}

②在 app.Run() 之前的启动阶段通过手动创建作用域获取。
如果需要在 app.Run() 之前(如初始化数据、启动后台任务)使用 ISqlSugarClient,不能直接获取实例,必须手动创建作用域(Scope),因为 Scoped 服务的生命周期与 “作用域” 绑定,而默认情况下启动阶段没有自动创建的作用域。

var builder = WebApplication.CreateBuilder(args);

// 注册 Scoped 服务
builder.Services.AddScoped<ISqlSugarClient>(sp =>
{
    var config = new ConnectionConfig
    {
        ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection"),
        DbType = DbType.SqlServer,
        IsAutoCloseConnection = true
    };
    return new SqlSugarClient(config);
});

var app = builder.Build();

// 启动阶段(app.Run() 之前)手动使用 ISqlSugarClient
// 必须创建作用域,否则无法获取 Scoped 服务
using (var scope = app.Services.CreateScope())
{
    var serviceProvider = scope.ServiceProvider;
    // 从作用域中获取 ISqlSugarClient 实例(此时已可用)
    var db = serviceProvider.GetRequiredService<ISqlSugarClient>();
    
    // 执行初始化操作(如创建表、插入初始数据)
    db.CodeFirst.InitTables<User>(); // 正常使用
    Console.WriteLine("数据库表初始化完成");
}

app.Run(); // 启动应用,进入请求处理阶段
  • 为什么 Scoped 服务需要 “作用域”?
    Scoped 服务的设计初衷是 “在一个作用域内复用,作用域结束后释放”(如一个 HTTP 请求对应一个作用域)。
    ▷ app.Run() 之后:HTTP 请求触发自动创建作用域,ISqlSugarClient 可直接通过依赖注入在控制器、服务中使用(无需手动处理)。
    ▷ app.Run() 之前:需通过 CreateScope() 手动创建作用域,才能获取 ISqlSugarClient 实例(用于启动阶段的初始化逻辑)。

webservice的接口参数加了字段,并发布后,对应的labview程序需要重新导入吗?在不使用这个字段的前提下。

不用,不会报错

只有一台服务器,用的.NET core,部署在IIS上,有必要用Nginx进行反向代理吗?有什么性能上的优势吗?

即使只有一台服务器,且.NET Core部署在IIS上,使用Nginx作为反向代理有一定价值,尤其在性能优化、功能扩展和安全性上有特定的优势。但

Nginx方向代理的潜在优势(单服务器场景)

  1. 静态资源处理更高效
  • Ngix擅长处理静态资源(如HTML、CSS、JS、图片等),其内核对静态文件和读取、缓存、压缩,优化更成熟,性能优于IIS处理静态资源的效率。
  • 实现方式:将静态资源路径(如/static/*)直接由Nginx处理,动态请求(如API接口)转发给IIS上的.NET Core应用。
    例:Nginx配置静态资源缓存和压缩,减少IIS和.NET Core进程的负担,间接提升动态请求的处理能力。
  1. 更灵活的负载均衡与扩展预留
  • 即使当前只有一台服务器,Nginx可轻松配置"单节点负载均衡"(看似多余,但为未来拓展多服务器预留了配置基础)
  • 此外,Nginx支持按URL路径、权重等方式分发请求,若服务器上部署了多个.NET Core应用(如不同微服务),可通过Nginx统一入口管理,比IIS多站点配置更灵活
  1. SSL终结和安全加固
  • SSL终结:Nginx可集中处理HTTPS证书解密(SSL终结),将解密后的HTTP请求转发给IIS,避免.NET Core应用和IIS重复处理加密解密,节省CPU资源(尤其高并发HTTPS场景)
  • 安全防护:Nginx可配置WAF规则(如过滤恶意请求、限制IP访问频率),作为第一层防护,比直接暴露IIS更安全。
  1. 连接管理与并发优化
  • Nginx基于"事件驱动"模型,处理高并发TCP连接的能力强于IIS(尤其短连接场景)。通过Nginx作为前端代理,可优化连接复用(如keepalive配置),减少服务器的连接建立/关闭开销。
  • 例如Nginx与客户端保持长连接,而后端IIS采用短连接,平衡客户端和服务器的连接数,避免IIS连接数耗尽。
  1. 日志与监控集中化
  • 所有请求先经过Nginx,可在Nginx层统一记录访问日志(无需配置IIS日志),便于集中分析(如请求来源、响应时间、错误码),且日志格式更灵活。

如果我可以加一台主机,windows系统的,也部署一个一样的程序到IIS里,可以做负载均衡吗?

完全可以!在两台 Windows 服务器(均部署相同的 .NET Core 程序到 IIS)之间实现负载均衡,是提升并发能力和可用性的常见方案。

SSL/TLS 证书是什么

证书(通常指 SSL/TLS 证书)是网络通信中用于验证身份和加密数据的电子文件,核心作用是解决 “如何信任对方” 和 “如何安全传输数据” 的问题,是 HTTPS 协议的基础。
一、证书的核心作用

  • 身份验证:证明 “服务器确实是它声称的那个主体”(如 www.baidu.com 的证书能证明该服务器归百度所有),防止 “中间人攻击”(有人冒充目标服务器骗取数据)。
  • 加密通信:证书中包含公钥,客户端通过公钥加密数据,只有服务器的私钥能解密,确保数据在传输过程中即使被截获,也无法被破解。
    二、证书的核心组成部分
    一份标准的 SSL/TLS 证书包含以下关键信息(可通过浏览器查看,如点击网址栏的小锁图标):
  • 主体(Subject):证书所有者的信息,如域名(www.example.com)、公司名称等(核心是 “域名”,证书与域名绑定)。
  • 颁发机构(Issuer):签发证书的 CA(Certificate Authority,证书颁发机构,如 Let’s Encrypt、DigiCert、GlobalSign 等)。
  • 公钥(Public Key):用于加密数据的密钥(公开可见,任何人都能获取)。
  • 私钥(Private Key):与公钥配对的密钥(必须严格保密,仅服务器持有,用于解密公钥加密的数据)。
  • 有效期:证书的生效和过期时间(通常 1-2 年,过期后需重新申请)。
  • 数字签名:CA 用自己的私钥对证书内容加密生成的 “指纹”,客户端可通过 CA 的公钥验证签名,确认证书未被篡改。
    如果我们配置了负载均衡服务器到网站应用程序之间采用HTTP通信,那么一定要删掉Program.cs中的UseHttpsRedirection,以避免程序把HTTP请求重定向到HTTPS请求。

FINALLY里面如果存在错误,会直接崩溃

要保证FINALLY里面不能有错误
尝试了在FINALLY中释放了没在等待的_semaphore,会导致错误产生,并退出函数

泛型不协变

错误代码,泛型的返回是ISqlSugarClient 等,不能直接给到ISqlSugarClient

ISqlSugarClient queryable;
switch (format)
{
    case "Cell":
        queryable = db.MasterQueryable<ProcessBindCell>();
        break;
    case "Block":
        queryable = db.MasterQueryable<ProcessBindBlock>();
        break;
    case "Pack":
        queryable = db.MasterQueryable<ProcessBind>();
        break;
    default:
        break;
}

最后修改为

// 1. 定义公共接口
public interface IProcessBindInfo
{
    int Id { get; set; }
    string PalletNo { get; set; }
    string ProductCode { get; set; }
    // ... 其他公共字段
}
// 2. 主方法:只负责根据 format 调用泛型方法
public List<IProcessBindInfo> GetList(int pageIndex, int pageSize, string keyWord, string configId, ref int totalCount, string index, string format)
{
    try
    {
        var db = GetInstance(configId);
        // 3. 核心改造:根据 format 直接调用泛型辅助方法
        // 泛型方法内部会自动处理强类型查询,并返回转换后的接口集合
        return format switch
        {
            "Cell" => GetPagedList<ProcessBindCell>(db, pageIndex, pageSize, keyWord, ref totalCount, index),
            "Block" => GetPagedList<ProcessBindBlock>(db, pageIndex, pageSize, keyWord, ref totalCount, index),
            "Pack" => GetPagedList<ProcessBind>(db, pageIndex, pageSize, keyWord, ref totalCount, index),
            _ => throw new Exception($"未知的 format 类型: {format}")
        };
    }
    catch (Exception E)
    {
        Logger.ErrorInfo(E.Message);
        return null;
    }
}
// 4. 核心泛型辅助方法:在泛型内部完成强类型查询和转换
private List<IProcessBindInfo> GetPagedList<T>(
    ISqlSugarClient db, 
    int pageIndex, 
    int pageSize, 
    string keyWord, 
    ref int totalCount, 
    string index) 
    where T : class, IProcessBindInfo, new()
{
    // 在泛型方法内部,T 是确定的类型,可以直接写强类型的 Lambda
    var queryable = db.MasterQueryable<T>();
    if (!string.IsNullOrEmpty(keyWord))
    {
        // 编译器知道 T 实现了 IProcessBindInfo,所以有 PalletNo 和 ProductCode
        queryable = queryable.Where(it => it.PalletNo.Contains(keyWord) || it.ProductCode.Contains(keyWord));
    }
    if (index == "1") // 正序   
    {
        queryable = queryable.OrderBy(it => it.Id);
    }
    else // 倒序   
    {
        queryable = queryable.OrderByDescending(it => it.Id);
    }
    // 执行分页查询,得到强类型的实体集合 List<T>
    List<T> list = queryable.ToPageList(pageIndex, pageSize, ref totalCount);
    // 5. 核心:将 List<T> 转换为 List<IProcessBindInfo> 返回
    // 因为 T 实现了 IProcessBindInfo,所以可以直接用 Cast 转换
    return list.Cast<IProcessBindInfo>().ToList();
}

bug

偶尔出现调用WEBAPI耗时过长,并且其他API像被这个API堵塞一样

现象:
api的日志显示,横向的红框是接口调用偶尔需要15s(接口超时就是15s),后面的api的调用时间都在这个之后,程序上没有对api加锁,不会彼此堵塞
在这里插入图片描述
原因:在调调用gratingSecurityRequestFeedback接口时,接口提供方会调用我放的另外一个接口TaskReport,而在TaskReport里面有个查表,表数据500w+且没索引,因此导致整个数据库都卡顿,连带其余接口写webapi日志都无法写入,要等这个查询过后,才能写入。
解决方法:
1.表加索引
2.表加数据迁移

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐