提交
This commit is contained in:
parent
a943aa0aa1
commit
400ee7c6a0
@ -1,13 +1,10 @@
|
||||
---
|
||||
title: ASP.Net
|
||||
title: ASP.NET Core
|
||||
date: 2021-03-23 10:30:31
|
||||
author: 文永达
|
||||
top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FBB-E15D-4F4F-8EE8-0F1C9F3C4E7C.jpeg
|
||||
---
|
||||
|
||||
# ASP.NET Core
|
||||
|
||||
## Web API 项目初始化搭建
|
||||
# Web API 项目初始化搭建
|
||||
|
||||
首先打开Visual Studio 2022,然后选择创建新项目
|
||||
|
||||
@ -33,27 +30,33 @@ top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FB
|
||||
|
||||

|
||||
|
||||
## 集成Furion框架
|
||||
# 集成Furion框架
|
||||
|
||||
在NuGet包管理器中搜索`Furion`
|
||||
在NuGet包管理器中搜索 `Furion`
|
||||
|
||||

|
||||
|
||||
选择安装的项目,然后安装即可
|
||||
|
||||
### 可能遇到的问题
|
||||
`Program.cs`配置
|
||||
|
||||
#### 包降级
|
||||
```c#
|
||||
var builder = WebApplication.CreateBuilder(args).Inject();
|
||||
builder.Services.AddControllers().AddInject();
|
||||
app.UseInject();
|
||||
```
|
||||
|
||||
# 可能遇到的问题
|
||||
|
||||
## 包降级
|
||||
|
||||

|
||||
|
||||
将提示的NuGet包升级到 前者的版本即可,比如图内的 Swashbuckle.AspNetCore 原有的版本是 6.2.3 那么升级到 6.5.0即可
|
||||
|
||||
# 部署到Docker
|
||||
|
||||
|
||||
## 部署到Docker
|
||||
|
||||
### 安装.Net SDK 6.0环境
|
||||
## 安装.Net SDK 6.0环境
|
||||
|
||||
```shell
|
||||
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
|
||||
@ -61,26 +64,24 @@ sudo yum install dotnet-sdk-6.0
|
||||
dotnet --info
|
||||
```
|
||||
|
||||
### Visual Studio添加Docker支持
|
||||
## Visual Studio添加Docker支持
|
||||
|
||||

|
||||
|
||||
### Linux下构建Docker镜像
|
||||
## Linux下构建Docker镜像
|
||||
|
||||
```shell
|
||||
docker image build -f ./XiaodaERP/Dockerfile -t aspnetcore .
|
||||
docker images
|
||||
```
|
||||
|
||||
### 运行Docker镜像
|
||||
## 运行Docker镜像
|
||||
|
||||
```shell
|
||||
docker run --name=aspnetcore -p 9001:80 -d aspnetcore
|
||||
docker ps
|
||||
```
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
cd /usr/local/jenkins_home/workspace/XiaodaERP_NetCore
|
||||
echo $PWD
|
||||
@ -89,9 +90,9 @@ docker images
|
||||
docker run --name xiaodaerp/netcore -p 7274:80 -d xiaodaerp/netcore
|
||||
```
|
||||
|
||||
## 顶级语句配置`Program.cs`
|
||||
# 顶级语句配置 `Program.cs`
|
||||
|
||||
### 取消默认JSON首字母小写命名
|
||||
## 取消默认JSON首字母小写命名
|
||||
|
||||
```c#
|
||||
builder.Services.AddControllers().AddJsonOptions(options => {
|
||||
@ -99,7 +100,7 @@ builder.Services.AddControllers().AddJsonOptions(options => {
|
||||
});
|
||||
```
|
||||
|
||||
### Json序列化时忽略属性为null的值
|
||||
## Json序列化时忽略属性为null的值
|
||||
|
||||
```c#
|
||||
builder.Services.AddControllers().AddJsonOptions(options => {
|
||||
@ -107,7 +108,7 @@ builder.Services.AddControllers().AddJsonOptions(options => {
|
||||
});
|
||||
```
|
||||
|
||||
### Json序列化时日期类型格式化输出
|
||||
## Json序列化时日期类型格式化输出
|
||||
|
||||
```c#
|
||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
@ -116,17 +117,7 @@ builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
});
|
||||
```
|
||||
|
||||
### 集成Furion框架
|
||||
|
||||
```c#
|
||||
var builder = WebApplication.CreateBuilder(args).Inject();
|
||||
builder.Services.AddControllers().AddInject();
|
||||
app.UseInject();
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 使用Autofac自动注入Service
|
||||
## 使用Autofac自动注入Service
|
||||
|
||||
通过NuGet包管理器 安装NuGet包
|
||||
|
||||
@ -140,7 +131,7 @@ Autofac.Extensions.DependencyInjection
|
||||
|
||||
Autofac.Extras.DynamicProxy
|
||||
|
||||
新建`ServiceAutofac.cs`类
|
||||
新建 `ServiceAutofac.cs`类
|
||||
|
||||
```c#
|
||||
using System.Reflection;
|
||||
@ -174,7 +165,7 @@ builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
|
||||
});
|
||||
```
|
||||
|
||||
### 注入Entity Framework Core 6 DbContext上下文
|
||||
## 注入Entity Framework Core 6 DbContext上下文
|
||||
|
||||
```c#
|
||||
builder.Services.AddDbContext<OracleDbContext>(options =>
|
||||
@ -183,7 +174,7 @@ options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext")))
|
||||
builder.Services.AddDbContext<SqlServerDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerDbContext")));
|
||||
```
|
||||
|
||||
### 使用JWT进行授权与认证
|
||||
## 使用JWT进行授权与认证
|
||||
|
||||
安装NuGet包
|
||||
|
||||
@ -246,7 +237,7 @@ app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
```
|
||||
|
||||
新建`TokenHelper.cs`工具类
|
||||
新建 `TokenHelper.cs`工具类
|
||||
|
||||
```c#
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
@ -365,7 +356,7 @@ public ViewUser Login(string UserName, string PassWord)
|
||||
}
|
||||
```
|
||||
|
||||
WebAPI 需要认证的加上`[Authorize]`注解,注意登录不能加
|
||||
WebAPI 需要认证的加上 `[Authorize]`注解,注意登录不能加
|
||||
|
||||
```c#
|
||||
[AuthFilter]
|
||||
@ -400,16 +391,10 @@ public ResultUtil GetUserInfo()
|
||||
|
||||

|
||||
|
||||
## 面向切面编程(AOP)
|
||||
|
||||
三大拦截器
|
||||
|
||||
|
||||
# 三大拦截器
|
||||
|
||||
认证拦截器 `AuthorizeAttribute`
|
||||
|
||||
|
||||
|
||||
方法拦截器 `ActionFilterAttribute`
|
||||
|
||||
```c#
|
||||
@ -540,8 +525,331 @@ public ResultUtil GetUserInfo()
|
||||
builder.Services.AddScoped<AuthFilter>();
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
异常拦截器 `ExceptionFilterAttribute`
|
||||
|
||||
# AspNetCoreRateLimit 速率限制
|
||||
|
||||
## 介绍
|
||||
|
||||
[**AspNetCoreRateLimit**](https://github.com/stefanprodan/AspNetCoreRateLimit/)是一个ASP.NET Core速率限制的解决方案,旨在控制客户端根据IP地址或客户端ID向Web API或MVC应用发出的请求的速率。AspNetCoreRateLimit包含一个**IpRateLimitMiddleware**和**ClientRateLimitMiddleware**,每个中间件可以根据不同的场景配置限制允许IP或客户端,自定义这些限制策略,也可以将限制策略应用在每个API URL或具体的HTTP Method上。
|
||||
|
||||
## 使用
|
||||
|
||||
由上面介绍可知AspNetCoreRateLimit支持了两种方式:基于**客户端IP(\**IpRateLimitMiddleware)\**和客户端ID(\**ClientRateLimitMiddleware\**)速率限制** 接下来就分别说明使用方式
|
||||
|
||||
添加Nuget包引用:
|
||||
|
||||
```shell
|
||||
Install-Package AspNetCoreRateLimit
|
||||
```
|
||||
|
||||
### 基于客户端IP速率限制
|
||||
|
||||
新建 `IPRateExtension.cs`
|
||||
|
||||
```c#
|
||||
public static class IPRateExtension
|
||||
{
|
||||
public static void AddIPRate(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
|
||||
//从appsettings.json中加载常规配置,IpRateLimiting与配置文件中节点对应
|
||||
services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
|
||||
|
||||
//从appsettings.json中加载Ip规则
|
||||
services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));
|
||||
//注入计数器和规则存储
|
||||
//分布式部署时,需要将速率限制计算器和ip规则存储到分布式缓存中如Redis
|
||||
services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
|
||||
services.AddSingleton<IClientPolicyStore, DistributedCacheClientPolicyStore>();
|
||||
// services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
|
||||
services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
|
||||
// services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
|
||||
//配置(解析器、计数器密钥生成器)
|
||||
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||
services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Program.cs`
|
||||
|
||||
```c#
|
||||
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
//初始化限流器
|
||||
builder.Services.AddIPRate(builder.Configuration);
|
||||
//启用客户端IP限制速率
|
||||
app.UseIpRateLimiting();
|
||||
```
|
||||
|
||||
**在appsettings.json中添加通用配置项节点:**
|
||||
|
||||
```json
|
||||
"IpRateLimiting": {
|
||||
//false,则全局将应用限制,并且仅应用具有作为端点的规则*。例如,如果您设置每秒5次调用的限制,则对任何端点的任何HTTP调用都将计入该限制
|
||||
//true, 则限制将应用于每个端点,如{HTTP_Verb}{PATH}。例如,如果您为*:/api/values客户端设置每秒5个呼叫的限制,
|
||||
"EnableEndpointRateLimiting": false,
|
||||
//false,拒绝的API调用不会添加到调用次数计数器上;如 客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等) //,则必须设置StackBlockedRequests为true。
|
||||
"StackBlockedRequests": false,
|
||||
//Kestrel 服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置
|
||||
"RealIpHeader": "X-Real-IP",
|
||||
//取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。
|
||||
"ClientIdHeader": "X-ClientId",
|
||||
//限制状态码
|
||||
"HttpStatusCode": 429,
|
||||
////IP白名单:支持Ip v4和v6
|
||||
//"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ],
|
||||
////端点白名单
|
||||
//"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
|
||||
////客户端白名单
|
||||
//"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
|
||||
//通用规则
|
||||
"GeneralRules": [
|
||||
{
|
||||
//端点路径
|
||||
"Endpoint": "*",
|
||||
//时间段,格式:{数字}{单位};可使用单位:s, m, h, d
|
||||
"Period": "1s",
|
||||
//限制
|
||||
"Limit": 2
|
||||
}, //15分钟只能调用100次
|
||||
{"Endpoint": "*","Period": "15m","Limit": 100}, //12H只能调用1000
|
||||
{"Endpoint": "*","Period": "12h","Limit": 1000}, //7天只能调用10000次
|
||||
{"Endpoint": "*","Period": "7d","Limit": 10000}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
配置节点已添加相应注释信息。
|
||||
|
||||
规则设置格式:
|
||||
|
||||
**端点格式:**`{HTTP_Verb}:{PATH}`,您可以使用asterix符号来定位任何HTTP谓词。
|
||||
|
||||
**期间格式:**`{INT}{PERIOD_TYPE}`,您可以使用以下期间类型之一:`s, m, h, d`。
|
||||
|
||||
**限制格式:**`{LONG}`
|
||||
|
||||
**特点Ip限制规则设置,在\**appsettings.json中添加 IP规则配置节点\****
|
||||
|
||||
```json
|
||||
"IpRateLimitPolicies": {
|
||||
//ip规则
|
||||
"IpRules": [
|
||||
{
|
||||
//IP
|
||||
"Ip": "84.247.85.224",
|
||||
//规则内容
|
||||
"Rules": [
|
||||
//1s请求10次
|
||||
{"Endpoint": "*","Period": "1s","Limit": 10},
|
||||
//15分钟请求200次
|
||||
{"Endpoint": "*","Period": "15m","Limit": 200}
|
||||
]
|
||||
},
|
||||
{
|
||||
//ip支持设置多个
|
||||
"Ip": "192.168.3.22/25",
|
||||
"Rules": [
|
||||
//1秒请求5次
|
||||
{"Endpoint": "*","Period": "1s","Limit": 5},
|
||||
//15分钟请求150次
|
||||
{"Endpoint": "*","Period": "15m","Limit": 150},
|
||||
//12小时请求500次
|
||||
{"Endpoint": "*","Period": "12h","Limit": 500}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
为使特点Ip限制规则生效,需初始化 IP 限制策略
|
||||
|
||||
`Program.cs`
|
||||
|
||||
```c#
|
||||
using (var serviceScope = app.Services.CreateScope())
|
||||
{
|
||||
var services = serviceScope.ServiceProvider;
|
||||
|
||||
// get the IpPolicyStore instance
|
||||
var ipPolicyStore = services.GetRequiredService<IIpPolicyStore>();
|
||||
|
||||
// seed IP data from appsettings
|
||||
ipPolicyStore.SeedAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientPolicyStore = services.GetRequiredService<IClientPolicyStore>();
|
||||
clientPolicyStore.SeedAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
```
|
||||
|
||||
### 运行时更新速率限制
|
||||
|
||||
添加 `IpRateLimitController`控制器
|
||||
|
||||
```c#
|
||||
/// <summary>
|
||||
/// IP限制控制器
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class IpRateLimitController : ControllerBase
|
||||
{
|
||||
private readonly IpRateLimitOptions _options;
|
||||
private readonly IIpPolicyStore _ipPolicyStore;
|
||||
|
||||
public IpRateLimitController(IOptions<IpRateLimitOptions> optionsAccessor, IIpPolicyStore ipPolicyStore)
|
||||
{
|
||||
_options = optionsAccessor.Value;
|
||||
_ipPolicyStore = ipPolicyStore;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IpRateLimitPolicies Get()
|
||||
{
|
||||
return _ipPolicyStore.Get(_options.IpPolicyPrefix);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Post()
|
||||
{
|
||||
var pol = _ipPolicyStore.Get(_options.IpPolicyPrefix);
|
||||
|
||||
pol.IpRules.Add(new IpRateLimitPolicy
|
||||
{
|
||||
Ip = "8.8.4.4",
|
||||
Rules = new List<RateLimitRule>(new RateLimitRule[] {
|
||||
new RateLimitRule {
|
||||
Endpoint = "*:/api/testupdate",
|
||||
Limit = 100,
|
||||
Period = "1d" }
|
||||
})
|
||||
});
|
||||
|
||||
_ipPolicyStore.Set(_options.IpPolicyPrefix, pol);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义 `IpRateLimitMiddleware`中间件
|
||||
|
||||
新建 `CustomIpRateLimitMiddleware`类并继承 `IpRateLimitMiddleware`
|
||||
|
||||
```c#
|
||||
public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly IIpRateLimitLogService _ipRateLimitLogService;
|
||||
|
||||
public CustomIpRateLimitMiddleware(RequestDelegate next, IProcessingStrategy processingStrategy,
|
||||
IOptions<IpRateLimitOptions> options, IIpPolicyStore policyStore, IRateLimitConfiguration config,
|
||||
ILogger<IpRateLimitMiddleware> logger, IIpRateLimitLogService ipRateLimitLogService) : base(next, processingStrategy, options, policyStore, config, logger)
|
||||
{
|
||||
_ipRateLimitLogService = ipRateLimitLogService;
|
||||
}
|
||||
|
||||
// 重写 用于记录被阻止的请求的日志
|
||||
protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity,
|
||||
RateLimitCounter counter, RateLimitRule rule)
|
||||
{
|
||||
// base.LogBlockedRequest(httpContext, identity, counter, rule);
|
||||
var nowDate = DateTime.Now;
|
||||
var ipRateLimitLog = new IpRateLimitLog
|
||||
{
|
||||
HttpVerb = identity.HttpVerb,
|
||||
Path = identity.Path,
|
||||
ClientIp = identity.ClientIp,
|
||||
Limit = rule.Limit,
|
||||
Period = rule.Period,
|
||||
Exceeded = counter.Count - rule.Limit,
|
||||
Endpoint = rule.Endpoint,
|
||||
CreateTime = nowDate
|
||||
};
|
||||
var logStr = $"请求 {ipRateLimitLog.HttpVerb}:{ipRateLimitLog.Path} 来自 IP {ipRateLimitLog.ClientIp} 已被阻止, " +
|
||||
$"配额 {ipRateLimitLog.Limit}/{ipRateLimitLog.Period} 超出次数 {ipRateLimitLog.Exceeded}. " +
|
||||
$"被规则 {ipRateLimitLog.Endpoint} 阻止. 时间: {ipRateLimitLog.CreateTime}";
|
||||
Logger.Info(logStr);
|
||||
_ipRateLimitLogService.InsertIpRateLimitLogAsync(ipRateLimitLog);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Quartz.Net 定时任务
|
||||
|
||||
## 介绍
|
||||
|
||||
在项目的开发过程中,难免会遇见后需要后台处理的任务,例如定时发送邮件通知、后台处理耗时的数据处理等,这个时候你就需要`Quartz.Net`了。
|
||||
|
||||
`Quartz.Net`是纯净的,它是一个.Net程序集,是非常流行的Java作业调度系统Quartz的C#实现。
|
||||
`Quartz.Net`一款功能齐全的任务调度系统,从小型应用到大型企业级系统都能适用。功能齐全体现在触发器的多样性上面,即支持简单的定时器,也支持Cron表达式;即能执行重复的作业任务,也支持指定例外的日历;任务也可以是多样性的,只要继承IJob接口即可。
|
||||
|
||||
对于小型应用,`Quartz.Net`可以集成到你的系统中,对于企业级系统,它提供了Routing支持,提供了Group来组织和管理任务
|
||||
|
||||
## 使用
|
||||
|
||||
添加Quartz.Net的引用
|
||||
|
||||
```shell
|
||||
Install-Package Quartz -Version 3.7.0
|
||||
```
|
||||
|
||||
添加引用以后,来创建一个Job类`HelloQuartzJob`
|
||||
|
||||
```c#
|
||||
public class HelloQuartzJob : IJob
|
||||
{
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
Console.WriteLine("Hello Quartz.Net");
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是个非常简单的Job类,它在执行时输出文本`Hello Quartz.Net`。
|
||||
|
||||
接下来,我们在程序启动时创建调度器(Scheduler),并添加HelloQuartzJob的调度:
|
||||
|
||||
```c#
|
||||
var schedulerFactory = new StdSchedulerFactory();
|
||||
var scheduler = await schedulerFactory.GetScheduler();
|
||||
await scheduler.Start();
|
||||
Console.WriteLine($"任务调度器已启动");
|
||||
|
||||
//创建作业和触发器
|
||||
var jobDetail = JobBuilder.Create<HelloQuartzJob>().Build();
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithSimpleSchedule(m => {
|
||||
m.WithRepeatCount(3).WithIntervalInSeconds(1);
|
||||
})
|
||||
.Build();
|
||||
|
||||
//添加调度
|
||||
await scheduler.ScheduleJob(jobDetail, trigger);
|
||||
```
|
||||
然后运行程序
|
||||
|
||||
```shell
|
||||
任务调度器已启动
|
||||
Hello Quartz.Net
|
||||
Hello Quartz.Net
|
||||
Hello Quartz.Net
|
||||
Hello Quartz.Net
|
||||
```
|
||||
|
||||
通过演示可以看出,要执行一个定时任务,一般需要四步:
|
||||
|
||||
1. 创建任务调度器。调度器通常在应用程序启动时创建,一个应用程序实例通常只需要一个调度器即可。
|
||||
2. 创建Job和JobDetail。Job是作业的类型,描述了作业是如何执行的,这个类是由我们定义的;JobDetail是Quartz对作业的封装,它包含Job类型,以及Job在执行时用到的数据,还包括是否要持久化、是否覆盖已存在的作业等选项。
|
||||
3. 创建触发器。触发器描述了在何时执行作业。
|
||||
4. 添加调度。当完成以上三步以后,就可以对作业进行调度了。
|
||||
|
||||
# NLog 日志记录
|
||||
|
||||
## 介绍
|
||||
|
||||
## 使用
|
||||
|
||||
@ -43,3 +43,11 @@ git rm --cache themes/butterfly
|
||||
git status
|
||||
git add themes/butterfly
|
||||
```
|
||||
|
||||
新建文章
|
||||
|
||||
```shell
|
||||
hexo new [layout] <title>
|
||||
```
|
||||
|
||||
新建一篇文章。如果没有设置 `layout` 的话,默认使用 [_config.yml](https://hexo.io/zh-cn/docs/configuration) 中的 `default_layout` 参数代替。如果标题包含空格的话,请使用引号括起来。
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
---
|
||||
|
||||
title: Java
|
||||
date: 2023-06-09 10:30:31
|
||||
author: 文永达
|
||||
top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/B18FCBB3-67FD-48CC-B4F3-457BA145F17A.jpeg
|
||||
---
|
||||
|
||||
# Spring Boot
|
||||
@ -266,3 +269,384 @@ public class TestThread {
|
||||
要考虑线程安全问题,就需要先考虑Java并发的三大基本特征:**原子性**、**可见性**以及**有序性**
|
||||
|
||||
- 原子性
|
||||
|
||||
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
|
||||
|
||||
那程序中原子性指的是最小操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值、进行加1操作、写入工作内存。所以在多线程中,有可能一个线程还没自增完,可能才执行到第二步,另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。
|
||||
|
||||
- 可见性
|
||||
|
||||
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
|
||||
|
||||
若两个线程在不同的cpu,那么线程1改变了i得值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改,线程没看到这就是可见性的问题。
|
||||
|
||||
- 有序性
|
||||
|
||||
程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题。
|
||||
|
||||
**案例**: **抢票**
|
||||
|
||||
当多个线程同时共享,同一个全局变量或静态变量(即局部变量不会),做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
|
||||
|
||||
Consumer类:
|
||||
|
||||
```java
|
||||
package com.company;
|
||||
|
||||
public class Consumer implements Runnable{
|
||||
|
||||
private int ticket = 100;
|
||||
|
||||
public void run(){
|
||||
while(ticket>0){
|
||||
System.out.println(Thread.currentThread().getName() + "售卖第" + (100-ticket+1) + "张票");
|
||||
ticket--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
主类:
|
||||
```java
|
||||
package com.company;
|
||||
|
||||
public class ThreadSafeProblem {
|
||||
public static void main(String[] args){
|
||||
Consumer abc = new Consumer();
|
||||
|
||||
new Thread(abc, "窗口1").start();
|
||||
new Thread(abc, "窗口2").start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
```shell
|
||||
窗口2售卖第95张票
|
||||
窗口2售卖第96张票
|
||||
窗口2售卖第97张票
|
||||
窗口2售卖第98张票
|
||||
窗口2售卖第99张票
|
||||
窗口2售卖第100张票
|
||||
窗口2售卖第84张票
|
||||
```
|
||||
|
||||
从输出结果来看,售票窗口买票出现了计票的问题,这就是线程安全出现问题了。
|
||||
|
||||
#### 如何确保线程安全
|
||||
|
||||
解决办法:使用多线程之间使用**关键字synchronized**、或者使用**锁(lock)**,或者**volatile关键字**。
|
||||
|
||||
1. **synchronized**(自动锁,锁的创建和释放都是自动的)
|
||||
2. **lock 手动锁**(手动指定锁的创建和释放)
|
||||
3. **volatile关键字**
|
||||
|
||||
为什么能解决?如果可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
|
||||
|
||||
##### synchronized关键字
|
||||
|
||||
###### 同步代码块
|
||||
|
||||
```java
|
||||
synchronized(同一个锁){
|
||||
//可能发生线程冲突问题
|
||||
}
|
||||
```
|
||||
|
||||
将可能会发生线程安全问题的代码,给包括起来,也称为**同步代码块**。synchronized使用的锁可以是对象锁也可以是静态资源,如xxx.class,只有持有锁的线程才能执行同步代码块中的代码。没持有锁的线程即使获取cpu的执行权,也进不去。
|
||||
|
||||
锁的释放是在synchronized同步代码块执行完毕后自动释放。
|
||||
|
||||
同步的前提:
|
||||
|
||||
1. 必须要有两个或两个以上的线程,如果小于2个线程,则没有用,且还会消耗性能(获取锁,释放锁)
|
||||
|
||||
2. 必须是多个线程使用一个锁
|
||||
**弊端**:多个线程需要判断锁,较为消耗资源、抢锁的资源。
|
||||
例子:
|
||||
|
||||
```java
|
||||
public class ThreadSafeProblem {
|
||||
public static void main(String[] args) {
|
||||
Consumer abc = new Consumer();
|
||||
// 注意要使用同一个abc变量作为thread的参数
|
||||
// 如果你使用了两个Consumer对象,那么就不会共享ticket了,就自然不会出现线程安全问题
|
||||
new Thread(abc, "窗口1").start();
|
||||
new Thread(abc, "窗口2").start();
|
||||
}
|
||||
}
|
||||
class Consumer implements Runnable{
|
||||
private int ticket = 100;
|
||||
@Override
|
||||
public void run() {
|
||||
while (ticket > 0) {
|
||||
synchronized (Consumer.class) {
|
||||
if (ticket > 0) {
|
||||
System.out.println(Thread.currentThread().getName() + "售卖第" + (100-ticket+1) + "张票");
|
||||
ticket--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
###### 同步函数
|
||||
|
||||
就是将synchronized加在方法上。
|
||||
|
||||
分为两种:
|
||||
|
||||
第一种是**非静态同步函数**,即方法是非静态的,使用的**this对象锁**,如下代码所示
|
||||
|
||||
第二种是**静态同步函数**,即方法是用static修饰的,使用的锁是**当前类的class文件(xxx.class)**。
|
||||
|
||||
```java
|
||||
public synchronized void sale () {
|
||||
if (ticket > 0) {
|
||||
System.out.println(Thread.currentThread().getName() + "售卖第" + (100-ticket+1) + "张票");
|
||||
ticket--;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
###### 多线程死锁线程
|
||||
|
||||
如下代码所示,
|
||||
|
||||
线程t1,运行后在同步代码块中需要oj对象锁,,运行到sale方法时需要this对象锁
|
||||
|
||||
线程t2,运行后需要调用sale方法,需要先获取this锁,再获取oj对象锁
|
||||
|
||||
那这样就会造成,两个线程相互等待对方释放锁。就造成了死锁情况。简单来说就是:
|
||||
|
||||
同步中嵌套同步,导致锁无法释放。
|
||||
|
||||
```java
|
||||
class ThreadTrain3 implements Runnable {
|
||||
private static int count = 100;
|
||||
public boolean flag = true;
|
||||
private static Object oj = new Object();
|
||||
@Override
|
||||
public void run() {
|
||||
if (flag) {
|
||||
while (true) {
|
||||
synchronized (oj) {
|
||||
sale();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
while (true) {
|
||||
sale();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void sale() {
|
||||
// 前提 多线程进行使用、多个线程只能拿到一把锁。
|
||||
// 保证只能让一个线程 在执行 缺点效率降低
|
||||
synchronized (oj) {
|
||||
if (count > 0) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (Exception e) {
|
||||
// TODO: handle exception
|
||||
}
|
||||
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ThreadDemo3 {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ThreadTrain3 threadTrain1 = new ThreadTrain3();
|
||||
Thread t1 = new Thread(threadTrain1, "①号窗口");
|
||||
Thread t2 = new Thread(threadTrain1, "②号窗口");
|
||||
t1.start();
|
||||
Thread.sleep(40);
|
||||
threadTrain1.flag = false;
|
||||
t2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Lock
|
||||
|
||||
可以视为**synchronized的增强版**,提供了更灵活的功能。该接口提供了限时锁等待、锁中断、锁尝试等功能。**synchronized**实现的同步代码块,它的锁是自动加的,且当执行完同步代码块或者抛出异常后,锁的释放也是自动的。
|
||||
|
||||
```java
|
||||
Lock l = ...;
|
||||
l.lock();
|
||||
try {
|
||||
// access the resource protected by this lock
|
||||
} finally {
|
||||
l.unlock();
|
||||
}
|
||||
```
|
||||
|
||||
但是**Lock锁**是需要手动去加锁和释放锁,所以**Lock**相比于**synchronized**更加的灵活。且还提供了更多的功能比如说
|
||||
|
||||
**tryLock()方法**会尝试获取锁,如果锁不可用则返回false,如果锁是可以使用的,那么就直接获取锁且返回true,官方代码如下:
|
||||
|
||||
```java
|
||||
Lock lock = ...;
|
||||
if (lock.tryLock()) {
|
||||
try {
|
||||
// manipulate protected state
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
// perform alternative actions
|
||||
}
|
||||
```
|
||||
|
||||
例子:
|
||||
|
||||
```java
|
||||
/*
|
||||
* 使用ReentrantLock类实现同步
|
||||
* */
|
||||
class MyReenrantLock implements Runnable{
|
||||
//向上转型
|
||||
private Lock lock = new ReentrantLock();
|
||||
public void run() {
|
||||
//上锁
|
||||
lock.lock();
|
||||
for(int i = 0; i < 5; i++) {
|
||||
System.out.println("当前线程名: "+ Thread.currentThread().getName()+" ,i = "+i);
|
||||
}
|
||||
//释放锁
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
public class MyLock {
|
||||
public static void main(String[] args) {
|
||||
MyReenrantLock myReenrantLock = new MyReenrantLock();
|
||||
Thread thread1 = new Thread(myReenrantLock);
|
||||
Thread thread2 = new Thread(myReenrantLock);
|
||||
Thread thread3 = new Thread(myReenrantLock);
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread3.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
由此我们可以看出,只有当当前线程打印完毕后,其他的线程才可继续打印,线程打印的数据是分组打印,因为当前线程持有锁,但线程之间的打印顺序是随机的。
|
||||
|
||||
即调用**lock.lock()** 代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放再次争抢。
|
||||
|
||||
##### volatile关键字
|
||||
|
||||
先来看一段错误的代码示例:
|
||||
|
||||
```java
|
||||
class ThreadVolatileDemo extends Thread {
|
||||
public boolean flag = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("子线程开始执行");
|
||||
while (flag) {
|
||||
}
|
||||
System.out.println("子线程执行结束...");
|
||||
}
|
||||
public void setFlag(boolean flag){
|
||||
this.flag=flag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ThreadVolatile {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
|
||||
threadVolatileDemo.start();
|
||||
Thread.sleep(3000);
|
||||
threadVolatileDemo.setFlag(false);
|
||||
System.out.println("flag已被修改为false!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```shell
|
||||
子线程开始执行
|
||||
flag已被修改为false
|
||||
```
|
||||
|
||||
虽然flag已被修改,但是子线程依然在执行,这里产生的原因就是**Java内存模型(JMM)** 导致的。
|
||||
|
||||
虽然flag已被修改,但是子线程依然在执行,这里产生的原因就是**Java内存模型(JMM)** 导致的。
|
||||
|
||||
这里再来介绍一下**Java内存模型**吧!!!
|
||||
|
||||
在**Java内存模型**规定了所有的变量(这里的变量是指成员变量,静态字段等但是不包括局部变量和方法参数,因为这是线程私有的)都存储在**主内存**中,每条线程还有自己的**工作内存**,线程的工作内存中拷贝了该线程使用到的主内存中的变量(只是副本,从主内存中拷贝了一份,放到了线程的本地内存中),**线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。** 不同的线程之间也无法直接访问对方工作内存中的变量,**线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行**。
|
||||
|
||||
而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。
|
||||
|
||||
**1. 首先要将共享变量从主内存拷贝到线程自己的工作内存空间,工作内存中存储着主内存中的变量副本拷贝;**
|
||||
|
||||
**2. 线程对副本变量进行操作,(不能直接操作主内存);**
|
||||
|
||||
**3. 操作完成后通过JMM 将线程的共享变量副本与主内存进行数据的同步,将数据写入主内存中;**
|
||||
|
||||
**4. 不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。**
|
||||
|
||||
当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题
|
||||
|
||||
**JMM是在线程调run方法的时候才将共享变量写到自己的线程本地内存中去的,而不是在调用start方法的时候。**
|
||||
|
||||
**解决办法**:
|
||||
|
||||
当出现这种问题时,就可以使用**Volatile关键字**进行解决。
|
||||
|
||||
**Volatile 关键字的作用**是变量在多个线程之间可见。使用**Volatile关键字**将解决线程之间可见性,**强制线程每次读取该值的时候都去“主内存”中取值**。
|
||||
|
||||
只需要在flag属性上加上该关键字即可。
|
||||
|
||||
```java
|
||||
public volatile boolean flag = true;
|
||||
```
|
||||
|
||||
子线程每次都不是读取的线程本地内存中的副本变量了,而是直接读取主内存中的属性值。
|
||||
|
||||
volatile虽然**具备可见性**,但是**不具备原子性**。
|
||||
|
||||
##### synchronized、volatile和Lock之间的区别
|
||||
|
||||
**synochronizd和volatile关键字区别:**
|
||||
|
||||
1. **volatile关键字**解决的是变量在多个线程之间的可见性;而**sychronized关键字**解决的是多个线程之间访问共享资源的同步性。
|
||||
|
||||
> **tip:** final关键字也能实现可见性:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把 **“this”**的引用传递出去(this引用逃逸是一件很危险的事情,其它线程有可能通过这个引用访问到了"初始化一半"的对象),那在其他线程中就能看见final;
|
||||
|
||||
2. **volatile**只能用于修饰变量,而**synchronized**可以修饰方法,以及代码块。(**volatile**是线程同步的轻量级实现,所以**volatile**性能比**synchronized**要好,并且随着JDK新版本的发布,**sychronized关键字**在执行上得到很大的提升,在开发中使用**synchronized关键字**的比率还是比较大);
|
||||
|
||||
3. 多线程访问**volatile**不会发生阻塞,而**sychronized**会出现阻塞;
|
||||
|
||||
4. 多线程访问**volatile**不会发生阻塞,而**sychronized**会出现阻塞;
|
||||
|
||||
5. **线程安全**包含**原子性**和**可见性**两个方面。
|
||||
|
||||
对于用**volatile**修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
|
||||
|
||||
**一句话说明volatile的作用**:实现变量在多个线程之间的可见性。
|
||||
|
||||
**synchronized和lock区别:**
|
||||
|
||||
1. **Lock**是一个接口,而**synchronized**是Java中的关键字,**synchronized**是内置的语言实现;
|
||||
2. **synchronized**在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而**Lock**在发生异常时,如果没有主动通过**unLock()**去释放锁,则很可能造成死锁现象,因此使用**Lock**时需要在**finally**块中释放锁;
|
||||
3. **Lock**可以让等待锁的线程响应中断,而**synchronized**却不行,使用**synchronized**时,等待的线程会一直等待下去,不能够响应中断;
|
||||
4. 通过**Lock**可以知道有没有成功获取锁,而**synchronized**却无法办到。
|
||||
5. **Lock**可以提高多个线程进行读操作的效率(读写锁)。
|
||||
|
||||
**在性能上来说**,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时**Lock**的性能要远远优于**synchronized**。所以说,在具体使用时要根据适当情况选择。
|
||||
|
||||
43
source/_posts/Rust.md
Normal file
43
source/_posts/Rust.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Rust
|
||||
date: 2023-09-15 09:25:54
|
||||
tags:
|
||||
---
|
||||
|
||||
# Rust教程
|
||||
|
||||
## 第一个Rust程序
|
||||
|
||||
Rust语言代码文件后缀名为`.rs`,如helloworld.rs。
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
}
|
||||
```
|
||||
|
||||
使用`rustc`命令编译helloworld.rs文件:
|
||||
|
||||
```shell
|
||||
rustc helloworld.rs # 编译 helloworld.rs 文件
|
||||
```
|
||||
|
||||
编译后会生成helloworld可执行文件:
|
||||
|
||||
```shell
|
||||
./helloworld # 执行 helloworld
|
||||
Hello World!
|
||||
```
|
||||
|
||||
# Rust环境搭建
|
||||
|
||||
## 安装Rust编译工具
|
||||
|
||||
Rust 编译工具从链接 [安装 Rust - Rust 程序设计语言 (rust-lang.org)](https://www.rust-lang.org/zh-CN/tools/install) 中下载的Rustup安装。下载好的Rustup在Windows 上是一个可执行程序 rustup-init.exe。(在其他平台上应该是`rustup-init.sh`)。
|
||||
|
||||
现在执行 rustup-init 文件:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user