全栈开发:使用.NET Core WebAPI构建前后端分离的核心技巧(一)

目录

cors解决跨域

依赖注入使用

分层服务注册

缓存方法使用

内存缓存使用

缓存过期清理

缓存存在问题

分布式的缓存


cors解决跨域

前后端分离已经成为一种越来越流行的架构模式,由于跨域资源共享(cors)是浏览器的一种安全机制,它会阻止前端应用向不同域的服务器发起请求,保护用户的隐私和数据安全。为了在前后端分离的应用中确保前端可以安全地访问后端的接口,不会受到浏览器的跨域限制,这里我们可以通过后端进行相应的cors配置。

首先我们先搭建一下.net core webapi的框架,不了解的可以参考我之前的文章:地址 ,然后我们配置了一个登录的接口,返回的结果是记录的类型,然后固定了一下登录成功的用户和密码,如下所示:

/// <summary>
/// 登录验证
/// </summary>
/// <param name="res"></param>
/// <returns></returns>
public record LoginRequest(string UserName, string Password);
public record ProcessInfo(long Id, string Name, long WorkingSet); // 记录类型
public record LoginResponse(bool OK, ProcessInfo[]? ProcessInfos);
[HttpPost]
[Route("login/user")] // 特性路由
public LoginResponse Login(LoginRequest res)
{
    if (res.UserName == "admin" && res.Password == "123456")
    {
        var items = Process.GetProcesses().Select(x => new ProcessInfo(x.Id, x.ProcessName, x.WorkingSet64));
        return new LoginResponse(true, items.ToArray()); // 返回记录类型
    }
    else
    {
        return new LoginResponse(false, null);
    }
}

然后我们就需要在入口文件Program.cs中配置一下我们允许要跨域的源,这里我们直接输入前端运行服务器的域名和端口即可,然后设置允许规则,这里我们正常就都允许,如果想配置部分允许的话,通过With函数进行筛选即可,如下:

// 配置跨域策略
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins("http://localhost:3000") // 允许跨域的源
        .AllowAnyHeader() // 允许任何头
        .AllowAnyMethod() // 允许任何方法
        .AllowCredentials() // 允许携带凭证
        .WithExposedHeaders("X-Custom-Header"); // 暴露自定义头信息
    });
});

app.UseCors(); // 使用跨域策略

然后前端的话,这里我们就使用react框架通过axios发起请求,不了解react的朋友,可参加我之前的文章:地址 ,然后我们通过如下的一个示例代码进行请求的发起:

import axios from "axios"
import { useState } from "react"

const WebApi = () => {
    const [userName, setUserName] = useState<string>('')
    const [password, setPassword] = useState<string>('')
    const [processInfo, setProcessInfo] = useState<any>([])

    const reqPost = () => {
        axios.post('http://localhost:5263/First/login/user', { userName: userName, password: password }).then(res => {
            if (res.data.ok) {
                setProcessInfo(res.data.processInfos)
            } else {
                alert('登录失败, 请重新登录!')
            }
        })
    }
    return (
        <div>
            账户: <input type="text" onChange={(e: any) => setUserName(e.target.value)} /> <br />
            密码: <input type="password" onChange={(e: any) => setPassword(e.target.value)} /> <br />
            <button onClick={() => reqPost()}>发起请求</button>
            {processInfo.map((item: any) => <div key={item.id}>{item.name}</div>)}
        </div>
    )
}

export default WebApi

最终呈现的效果如下所示:

依赖注入使用

依赖注入通过将对象的创建和管理交给框架,而不是在类内部直接创建,可以有效地解耦各个模块,使得每个组件都能够独立地进行测试和维护。这对于实现前后端分离的架构至关重要,因为它允许开发者更灵活地控制和管理后端服务,使得前端与后端的交互更加清晰、可靠。具体可以参考我之前的文章:地址 ,这里不再赘述,然后接下来我们开始演示在WebAPI中如何使用依赖注入:

构造函数注入服务操作:传统且经典的创建依赖注入

创建服务:这里我们直接可以创建一个两数相加的服务函数,如下所示:

namespace netCoreWebApi
{
    public class Calculator
    {
        public int Add(int i1, int i2)
        {
            return i1 + i2;
        }
    }
}

服务注册:然后我们在入口文件中进行服务注册,如下所示:

builder.Services.AddScoped<Calculator>(); // 注册Calculator服务

依赖注入:然后我们在控制器文件中通过构造函数进行服务注入:

using Microsoft.AspNetCore.Mvc;
using netCoreWebApi.WebCore;

namespace netCoreWebApi.Controllers
{
    [ApiController]
    [Route("api/[controller]/[action]")]
    [ApiExplorerSettings(GroupName = nameof(ApiVersionInfo.V1))]
    public class FirstController : ControllerBase
    {

        private readonly Calculator calculator;
        public FirstController(Calculator calculator)
        {
            this.calculator = calculator;
        }
        [HttpGet]
        public int Add1()
        {
            return calculator.Add(1, 2);
        }
    }
}

允许项目得到的结果如下所示,果然是3:

低使用频率服务:一些耗时的依赖注入可能会影响其他接口的调用,这里我们需要使用该注入方式进行解决,一般的接口创建不需要使用该服务,只有调用频率不高且资源的创建比较消耗资源的服务才会使用

创建服务:这里我们直接可以创建一个比较耗费资源的扫描文件服务函数,如下所示:

namespace netCoreWebApi
{
    public class SearchService
    {
        private string[] files;
        public SearchService()
        {
            this.files = Directory.GetFiles("d:/","*.exe", SearchOption.AllDirectories);
        }
        public int Count
        {
            get
            {
                return this.files.Length;
            }
        }
    }
}

服务注册:然后我们在入口文件中进行服务注册,如下所示:

builder.Services.AddScoped<SearchService>(); // 注册SearchService服务

依赖注入:然后我们在控制器文件中通过构造函数进行服务注入,把Action用到的服务通过Action的参数注入,在这个参数上标注[FromServices],和Action的其他参数不冲突,只有Action方法才能使用[FromServices],普通的类默认不支持,如下所示:

[HttpGet]
public int Test1([FromServices]SearchService searchService) // 只有请求这个方法时才会注入SearchService
{
    return searchService.Count;
}

如下当请求耗费较多资源的时候,请求时间才会过长,请求其他不耗费资源的接口,正常请求:

分层服务注册

从上面的依赖服务注册使用我们可以了解到,当我们想进行依赖注入的使用,都需要在入口文件进行服务的注册,但是项目一旦庞大起来或者说服务一旦多起来,多人协作开发的时候再要求所有的服务都必须注册在入口文件中就会导致一些问题的冲突,如下所示就是典型的例子:

这里我们需要对服务注册进行解耦操作,即进行分层处理。在分层项目中,让各个项目负责各自的服务注册,这里我们需要先安装一下下面这个依赖包:

然后这里我们创建多个类库,模拟多个服务的使用,然后将这些服务引用到项目上:

然后在每个项目中创建一个或多个实现IModuleInitializer接口的类,然后将服务注册的函数写道该接口类当中,如下所示:

然后我们通过反射原理,将服务注册的函数来映射到入口函数当中,具体代码如下所示:

然后我们再次运行项目,发现我们的服务还是成功被运行起来了,如下所示:

缓存方法使用

缓存:是系统优化中简单又有效的工具,投入小收效大,数据库中的索引等简单有效的优化功能本质上都是缓存,其将经常访问的数据存储在一个快速访问的存储区域(如内存)中,从而减少对数据库或其他慢速存储系统的重复访问。缓存能够显著提高应用程序的性能,尤其是在需要频繁读取大量数据时。

客户端响应缓存:RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头,服务器如果返回cache-control: max-age-60,则表示服务器指示浏览器端可以缓存这个响应内容60秒

这里我们只需要给进行缓存控制的控制器的操作方法添加ResponseCache这个Attribute,.net core会自动添加cache-control报文头,如下所示我们设置了一个获取当前时间的接口,正常情况下每次请求接口都是最新的时间,这里添加了缓存20秒导致了请求在20秒之内的数据都是不变的:

服务端响应缓存:服务端缓存整个HTTP响应,而不是仅仅缓存其中的数据或部分内容。这样,服务器可以直接返回已经缓存的响应,而不需要重新处理请求和生成新的响应。服务端响应缓存可以显著提高性能,特别是在处理重复的请求时。

如果.net core中安装了响应缓存中间件,那么.net core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存,使用方法如下所示,在入口文件处在app.MapControllers()之前添加app.UseResponseCaching(),请确保如果你的项目如果存在app.UseCors()的话,该函数的调用也要写在app.UseResponseCaching()之前,如下所示:

注意,如果你勾选了浏览器当中的禁用缓存的按钮,不仅是客户端,服务器端在请求的时候由于带上了no-cache,服务器端也会禁用掉所有的缓存:

当然服务器缓存还是很鸡肋的,它无法解决恶意请求带给服务器的压力,服务器响应缓存还有很大的限制,包括但不限于:响应状态码为200的GET或者HEAD响应才能被缓存;报文头中不能含有Authorization、Set-Cookie等,为了解决这些问题我们还需要采用内存或者分布式进行缓存。

内存缓存使用

内存缓存:是指将数据存储在计算机的内存中以便快速访问和提高系统性能的一种技术,通常内存缓存用于存储那些频繁访问且计算或获取成本较高的数据,目的是减少从磁盘或其他慢速存储设备中读取数据的次数,从而加速应用程序的响应速度。

内存缓存的数据保存在当前运行的网站程序的内存中,是和进程相关的。因为在Web服务器中多个不同的网站是运行在不同的进程中的,因此不同的网站的内存缓存是不会相互干扰的,而且网站重启之后内存缓存中的所有数据也就都被清空了。内存缓存的使用方法如下所示:

注册内存缓存服务:这里我们需要先在入口文件进行内存缓存服务的注册,如下所示:

builder.Services.AddMemoryCache(); // 添加内存缓存服务

这里我们先创建一个MyDbContext来模拟一下数据库当中的数据,并设置一个函数返回数据:

namespace webapi_study
{
    public class MyDbContext
    {
        public static Task<Book?> GetByIdAsync(long id)
        {
            var result = GetById(id);
            return Task.FromResult(result);
        }
        public static Book? GetById(long id)
        {
            switch (id)
            {
                case 0:
                    return new Book(0, "C#", "张三");
                case 1:
                    return new Book(1, "Java", "李四");
                case 2:
                    return new Book(2, "Python", "王五");
                default:
                    return null;
            }
        }
    }
}

接下来我们在控制器的接口中注册一下缓存服务,通过GetOrCreateAsync函数拿到缓存当中的数据,如果缓存当中没有数据的话我们就正常请求接口拿到数据即可,如下所示:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace webapi_study.Controllers
{
    [ApiController]
    [Route("api/[controller]/[action]")]
    public class TestController : ControllerBase
    {
        private readonly IMemoryCache cache; // 注入缓存服务
        private readonly ILogger<TestController> logger; // 注入日志服务
        public TestController(IMemoryCache cache, ILogger<TestController> logger)
        {
            this.cache = cache;
            this.logger = logger;
        }
        [HttpGet]
        public async Task<ActionResult<Book?>> GetBookById(long id)
        {
            // 1) 从缓存中获取数据 2)从数据库中获取数据 3)返回给调用者并将数据存入缓存
            logger.LogInformation($"开始执行GetBookById: {id}");
            Book? b = await cache.GetOrCreateAsync("book" + id, async (e) =>
            {
                logger.LogInformation($"缓存中没有找到,到数据库中查一查,id={id}");
                return await MyDbContext.GetByIdAsync(id);
            });
            logger.LogInformation($"GetOrCreateAsync结果是:{id}");
            if (b == null)
            {
                return NotFound($"Book with id {id} not found");
            }
            else
            {
                return b;
            }
        }
    }
}

最终呈现的效果如下所示,我们请求两次接口,第一次请求数据库中的数据因为没有缓存数据,所有是请求的接口,第二次是由于缓存中已经存在数据了,我们就直接拿到缓存当中的数据即可:

缓存过期清理

上面我们简单的介绍了一下内存缓存的简单使用,但是上面的例子中缓存是不会过期的,除非重启服务器进行重置操作,但是重置服务器的代价太大了,这里我们需要对在数据改变的时候缓存的处理,如下所示:

手动清理缓存:在数据改变的时候调用Remove或者Set来删除或修改缓存(优点:及时)

设置过期时间:只要过期时间比较短,缓存数据不一致的清空也不会持续太少时间,可以通过两种过期时间策略进行:绝对过期时间;滑动过期时间

绝对过期时间:顾名思义就是我设置了一个过期时间,超过这个时间缓存自动被清除,如下所示:

滑动过期时间:顾名思义就是只要在缓存没过期的时候请求一次,缓存就会自动续命一段时间:

两种过期时间混用:使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不会过期,可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除,如下所示:

总结:无论使用哪种过期时间策略,程序中都会存在缓存不一致的清空,部分系统(博客系统等)无所谓,部分系统不能忍受(比如金融),可以通过其他机制获取数据源改变的消息,再通过代码调用IMemoryCache的Set方法更新缓存。

缓存存在问题

在内存缓存中,缓存穿透和缓存雪崩是两种常见且需要特别注意的问题,下面简要讨论这两个问题及其解决方法:

缓存穿透:是指查询的数据在缓存中不存在,并且每次查询都直接访问数据库。通常缓存穿透发生在以下几种情况:

1)查询的请求数据根本不在数据库中(例如,恶意请求或数据不存在)。

2)数据被误删除或没有被正确存入缓存。

造成影响

1)每次请求都访问数据库,导致数据库负载加重,降低系统性能。
2)缓存无法有效提高访问速度,因为每次都需要从数据库中读取数据。

解决方案如下:

缓存空结果:对于一些常见的不存在数据(例如查询某个ID的数据返回为空),可以将“空”数据也缓存起来。设置一个较短的过期时间防止数据库不断查询相同的无效数据:

缓存雪崩:是指缓存中的大量数据在同一时刻过期或失效,导致大量请求同时访问数据库,造成数据库压力剧增,甚至崩溃。常见的触发场景是:

1)大量缓存失效:如果缓存的失效时间设置相同或接近,那么这些缓存项会在同一时刻失效,导致大量请求同时查询数据库。

2)数据库访问压力骤增:所有缓存失效后,系统会将大量的请求直接发送到数据库,从而加重数据库负载。

造成影响

1)短时间内大量请求集中访问数据库,容易造成数据库崩溃或性能严重下降。

2)数据库的负载激增,可能导致响应延迟和系统整体性能下降。

解决方案如下:

在基础过期时间之上再加一个随机的过期时间:

分布式的缓存

分布式缓存是一种将缓存数据分布在多个节点上的技术,目的是提高系统的可扩展性、可用性和性能。在大型系统中,单一的缓存节点往往无法满足高并发、高可用的需求,分布式缓存应运而生。

分布式内存缓存:如果集群节点的数量非常多的话,这样的重复查询也同样可能会把数据库压垮

分布式缓存服务器: 分布式缓存是指将缓存数据分布到多个不同的服务器节点上,这些节点共同协作提供缓存服务。用户的请求通过负载均衡的方式访问不同的缓存节点。常见的分布式缓存技术有:

1)Redis:最流行的分布式缓存系统之一,支持内存存储和丰富的数据结构。

2)Memcached:另一个常见的分布式缓存,适合简单的键值对缓存场景。

3)Alibaba Tair:阿里巴巴自研的分布式缓存系统,主要服务于大规模的互联网应用。

.net core中提供了统一的分布式缓存服务器的操作接口IDistributedCache,用法和内存缓存类似,分布式缓存和内存缓存的区别在于:缓存值的类型为byte[],需要我们进行类型转换,也提供了一些安装string类型存取缓存值的扩展方法,如下所示:

方法说明
Task<byte[]>GetAsync(string key)查询缓存键key对应的缓存值,返回值是byte[]类型,如果对应的缓存不存在,则返回null。
Task RefreshAsync(string key)刷新缓存键key对应的缓存项,会对设置了滑动过期时间的缓存项续期。
Task RemoveAsync(string key)删除缓存键key对应的缓存项
Task SetAsync(string key, byte[] value,DistributedCacheEntryOptions options)设置缓存键key对应的缓存项:value属性为byte类型的缓存值,注意value不能是null值
Task<string> GetStringAsync(string key)按照string类型查询缓存键key对应的缓存值,返回值是string类型,如果对应的缓存不存在则返回null。
Task SetStringAsync(string key. string value,DistributedCacheEntryOptions options)设置缓存键key对应的缓存项,value属性为string类型的缓存值,注意value不能是null值。

对于用什么做缓存服务器,用SQL Server做缓存其性能并不好;Memcached是缓存专用,性能非常高但是集群、高可用等方面比较弱,而且有”缓存键的最大长度为250字节“等限制,可以安装EnyimMemcachedCore这个第三方NuGet包;Redis不局限于缓存,Redis做缓存服务器比Memcached性能稍差,但是Redis的高可用、集群等方面非常强大,适合在数据量大、高可用性等场合使用,可以按照如下插件进行使用:

然后我们在入口文件进行服务注册:

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379"; // 配置连接字符串
    options.InstanceName = "SampleInstance"; // 配置实例名称,避免缓存冲突
});

然后我们在控制器当中构造分布式缓存的服务:

然后通过GetStringAsync函数构造当前的id,来判断当前是否存在缓存

[HttpGet]
public async Task<ActionResult<Book?>> GetBookById1(long id)
{
    Book? book;
    string? s = await disCache.GetStringAsync("book" + id);
    if (s == null)
    {
        book = await MyDbContext.GetByIdAsync(id);
        await disCache.SetStringAsync("book" + id, JsonSerializer.Serialize(book));
    }
    else
    {
        book = JsonSerializer.Deserialize<Book?>(s);
    }
    if (book == null)
    {
        return NotFound($"Book with id {id} not found");
    }
    else
    {
        return book;
    }
}

通过redis服务器可以看到我们的缓存信息:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM执行流程与架构(对应不同版本JDK)

直接上图&#xff08;对应JDK8以及以后的HotSpot&#xff09; 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭&#xff1a; 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间&#xff0c;堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…

【玩转 Postman 接口测试与开发2_014】第11章:测试现成的 API 接口(下)——自动化接口测试脚本实战演练 + 测试集合共享

《API Testing and Development with Postman》最新第二版封面 文章目录 3 接口自动化测试实战3.1 测试环境的改造3.2 对列表查询接口的测试3.3 对查询单个实例的测试3.4 对新增接口的测试3.5 对修改接口的测试3.6 对删除接口的测试 4 测试集合的共享操作4.1 分享 Postman 集合…

登录认证(5):过滤器:Filter

统一拦截 上文我们提到&#xff08;登录认证&#xff08;4&#xff09;&#xff1a;令牌技术&#xff09;&#xff0c;现在大部分项目都使用JWT令牌来进行会话跟踪&#xff0c;来完成登录功能。有了JWT令牌可以标识用户的登录状态&#xff0c;但是完整的登录逻辑如图所示&…

基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…

git基础使用--1--版本控制的基本概念

文章目录 git基础使用--1--版本控制的基本概念1.版本控制的需求背景&#xff0c;即为啥需要版本控制2. 集中式版本控制SVN3. 分布式版本控制 Git4. SVN和Git的比较 git基础使用–1–版本控制的基本概念 1.版本控制的需求背景&#xff0c;即为啥需要版本控制 先说啥叫版本&…

< OS 有关 > Android 手机 SSH 客户端 app: connectBot

connectBot 开源且功能齐全的SSH客户端,界面简洁,支持证书密钥。 下载量超 500万 方便在 Android 手机上&#xff0c;连接 SSH 服务器&#xff0c;去运行命令。 Fail2ban 12小时内抓获的 IP ~ ~ ~ ~ rootjpn:~# sudo fail2ban-client status sshd Status for the jail: sshd …

课题推荐——基于自适应滤波技术的多传感器融合在无人机组合导航中的应用研究

无人机在现代航空、农业和监测等领域的应用日益广泛。为了提高导航精度&#xff0c;通常采用多传感器融合技术&#xff0c;将来自GPS、惯性测量单元&#xff08;IMU&#xff09;、磁力计等不同传感器的数据整合。然而&#xff0c;传感器的量测偏差、环境干扰以及非线性特性使得…

【MySQL】常用语句

目录 1. 数据库操作2. 表操作3. 数据操作&#xff08;CRUD&#xff09;4. 高级查询5. 索引管理6. 用户与权限7. 数据导入导出8. 事务控制9. 其他实用语句注意事项 如果这篇文章对你有所帮助&#xff0c;渴望获得你的一个点赞&#xff01; 1. 数据库操作 创建数据库 CREATE DATA…

基于多智能体强化学习的医疗AI中RAG系统程序架构优化研究

一、引言 1.1 研究背景与意义 在数智化医疗飞速发展的当下,医疗人工智能(AI)已成为提升医疗服务质量、优化医疗流程以及推动医学研究进步的关键力量。医疗 AI 借助机器学习、深度学习等先进技术,能够处理和分析海量的医疗数据,从而辅助医生进行疾病诊断、制定治疗方案以…

使用 Elastic Cloud Hosted 优化长期数据保留:确保政府合规性和效率

作者&#xff1a;来自 Elastic Jennie Davidowitz 在数字时代&#xff0c;州和地方政府越来越多地承担着管理大量数据的任务&#xff0c;同时确保遵守严格的监管要求。这些法规可能因司法管辖区而异&#xff0c;通常要求将数据保留较长时间 —— 有时从一年到七年不等。遵守刑事…

【NLP 20、Encoding编码 和 Embedding嵌入】

目录 一、核心定义与区别 二、常见Encoding编码 (1) 独热编码&#xff08;One-Hot Encoding&#xff09; (2) 位置编码&#xff08;Positional Encoding&#xff09; (3) 标签编码&#xff08;Label Encoding&#xff09; (4) 注意事项 三、常见Embedding词嵌入 (1) 基础词嵌入…

【Envi遥感图像处理】010:归一化植被指数NDVI计算方法

文章目录 一、NDVI简介二、NDVI计算方法1. NDVI工具2. 波段运算三、注意事项1. 计算结果为一片黑2. 计算结果超出范围一、NDVI简介 归一化植被指数,是反映农作物长势和营养信息的重要参数之一,应用于遥感影像。NDVI是通过植被在近红外波段(NIR)和红光波段(R)的反射率差异…

7、怎么定义一个简单的自动化测试框架?

定义一个简单的自动化测试框架可以从需求理解、框架设计、核心模块实现、测试用例编写和集成执行等方面入手&#xff0c;以下为你详细介绍&#xff1a; 1. 明确框架需求和范围 确定测试类型&#xff1a;明确框架要支持的测试类型&#xff0c;如单元测试、接口测试、UI 测试等…

web-XSS-CTFHub

前言 在众多的CTF平台当中&#xff0c;作者认为CTFHub对于初学者来说&#xff0c;是入门平台的不二之选。CTFHub通过自己独特的技能树模块&#xff0c;可以帮助初学者来快速入门。具体请看官方介绍&#xff1a;CTFHub。 作者更新了CTFHub系列&#xff0c;希望小伙伴们多多支持…

Linux中的基本指令(二)

一、移动和重命名指令mv 1.1基本作用及使用规范 基本作用是进行文件的移动和重命名&#xff0c;使用规范如&#xff1a; mv src[目录/文件]dst[路径/文件] 回车 1.2三种不同的作用 通过在src部分和dst部分写入不同的内容&#xff0c;来实现文件的移动和重命名的等不同功能…

【RL Latest Tech】安全强化学习(Safe RL):理论、方法与应用

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…

qt-Quick笔记之Dark Mode And Light Mode In Application

qt-Quick笔记之Dark Mode And Light Mode In Application code review! 文章目录 qt-Quick笔记之Dark Mode And Light Mode In Application1.运行2.目录结构3.main.qml4.main.cpp5.main.pro6.main.qrc 本例修改自视频教程&#xff1a;Qt QML | &#x1f319; Dark Mode And ☀…

visual studio安装

一、下载Visual Studio 访问Visual Studio官方网站。下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux 在主页上找到并点击“下载 Visual Studio”按钮。 选择适合需求的版本&#xff0c;例如“Visual Studio Community”&#xff08;免费版本&#xff09;&#x…

基于Springboot框架的学术期刊遴选服务-项目演示

项目介绍 本课程演示的是一款 基于Javaweb的水果超市管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附…

【C++篇】位图与布隆过滤器

目录 一&#xff0c;位图 1.1&#xff0c;位图的概念 1.2&#xff0c;位图的设计与实现 1.5&#xff0c;位图的应用举例 1.4&#xff0c;位图常用应用场景 二&#xff0c;布隆过滤器 2.1&#xff0c;定义&#xff1a; 2.2&#xff0c;布隆过滤器的实现 2.3&#xff0c; 应…