2024-09-02 23:20:28 +08:00
|
|
|
|
## 基础配置
|
|
|
|
|
|
|
|
|
|
基础配置可以看下这个,里面整合几乎这个模板所有的配置。
|
|
|
|
|
|
|
|
|
|
![image-20240902231627836](./images/image-20240902231627836.png)
|
|
|
|
|
|
|
|
|
|
### 数据库配置
|
|
|
|
|
|
|
|
|
|
![image-20240902231720819](./images/image-20240902231720819.png)
|
|
|
|
|
|
|
|
|
|
### 中间件配置
|
|
|
|
|
|
|
|
|
|
![image-20240902231705133](./images/image-20240902231705133.png)
|
|
|
|
|
|
|
|
|
|
## 后台服务设置
|
|
|
|
|
|
|
|
|
|
### Quartz写法
|
|
|
|
|
|
|
|
|
|
- 持久化存储生成数据SQL语句参考
|
|
|
|
|
- https://github.com/quartznet/quartznet/tree/main/database/tables
|
|
|
|
|
|
|
|
|
|
### 原生写法
|
|
|
|
|
|
|
|
|
|
- 设置每秒执行多少,设置后台服务,原生写法
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
|
|
|
|
namespace Bunny.Service.BackgroundModule;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 定时任务
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class TemplateBackgroundModule : BackgroundService
|
|
|
|
|
{
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
{
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("TemplateService started");
|
|
|
|
|
await Task.Delay(1000, stoppingToken);
|
|
|
|
|
await Task.Run(() => { Console.WriteLine("执行了。。。"); }, stoppingToken);
|
|
|
|
|
await Task.Delay(1000, stoppingToken);
|
|
|
|
|
Console.WriteLine("--------------------------------");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 验证码生成
|
|
|
|
|
|
|
|
|
|
- 验证码生成相关配置
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
var service = builder.Services;
|
|
|
|
|
// 使用图形验证码
|
|
|
|
|
service.AddDistributedMemoryCache();
|
|
|
|
|
// 验证码相关配置内容
|
|
|
|
|
service.AddCaptcha(options =>
|
|
|
|
|
{
|
|
|
|
|
options.CaptchaType = CaptchaType.DEFAULT; // 验证码类型
|
|
|
|
|
options.CodeLength = 4; // 验证码长度
|
|
|
|
|
options.ExpirySeconds = 60; // 过期时间(单位/秒)
|
|
|
|
|
options.IgnoreCase = true; // 比较忽略大小写
|
|
|
|
|
options.ImageOption.Animation = true; // 是否启用动画
|
|
|
|
|
options.ImageOption.Width = 130; // 验证码宽度
|
|
|
|
|
options.ImageOption.Height = 48; // 验证码高度
|
|
|
|
|
options.ImageOption.BackgroundColor = SKColors.White;
|
|
|
|
|
options.ImageOption.BubbleCount = 6; // 气泡数量
|
|
|
|
|
options.ImageOption.BubbleMinRadius = 2; // 气泡最小半径
|
|
|
|
|
options.ImageOption.BubbleMaxRadius = 6; // 气泡最大半径
|
|
|
|
|
options.ImageOption.BubbleThickness = 2; // 气泡边沿厚度
|
|
|
|
|
options.ImageOption.InterferenceLineCount = 2; // 干扰线数量
|
|
|
|
|
options.ImageOption.FontSize = 36; // 字体大小
|
|
|
|
|
options.ImageOption.FontFamily = DefaultFontFamilys.Instance.Kaiti; // 字体,中文使用kaiti,其他字符可根据喜好设置
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## AutoFac配置
|
|
|
|
|
|
|
|
|
|
### 自动注入按照名称注入
|
|
|
|
|
|
|
|
|
|
**AutoFac配置详情**
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
using Autofac;
|
|
|
|
|
using Bunny.Common.Attribute;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
|
|
|
|
namespace Bunny.WebApi.Config;
|
|
|
|
|
|
|
|
|
|
public static class AddAutofacConfig
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// AutoFac自动注入约定名称
|
|
|
|
|
/// 接口以Service结尾注入到IOC中
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="builder"></param>
|
|
|
|
|
public static void BuildContainer(ContainerBuilder builder)
|
|
|
|
|
{
|
|
|
|
|
// 扫描所以前缀Bunny的命名空间
|
|
|
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(t => t.FullName!.StartsWith("Bunny"));
|
|
|
|
|
|
|
|
|
|
// 遍历注入服务
|
|
|
|
|
foreach (var assembly in assemblies)
|
|
|
|
|
{
|
|
|
|
|
var types = assembly.GetTypes();
|
|
|
|
|
|
|
|
|
|
// 注入Service
|
|
|
|
|
var serviceTypes = types.Where(t => t.GetCustomAttributes(typeof(ServiceAttribute), false).Length != 0);
|
|
|
|
|
foreach (var serviceType in serviceTypes)
|
|
|
|
|
{
|
|
|
|
|
var interfaces = serviceType.GetInterfaces();
|
|
|
|
|
builder.RegisterType(serviceType).As(interfaces).InstancePerDependency();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注入数据库相关
|
|
|
|
|
var mapperTypes = types.Where(t => t.GetCustomAttributes(typeof(MapperAttribute), false).Length != 0);
|
|
|
|
|
foreach (var mapperType in mapperTypes)
|
|
|
|
|
{
|
|
|
|
|
var interfaces = mapperType.GetInterfaces();
|
|
|
|
|
builder.RegisterType(mapperType).As(interfaces).InstancePerDependency();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注入后台服务
|
|
|
|
|
var componentTypes =
|
|
|
|
|
types.Where(t => t.GetCustomAttributes(typeof(ComponentAttribute), false).Length != 0);
|
|
|
|
|
foreach (var componentType in componentTypes)
|
|
|
|
|
builder.RegisterType(componentType).AsSelf().As<IHostedService>().SingleInstance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果不在在 Controller 层写构造函数可以打开这个,自动完成注入
|
|
|
|
|
var controllerBaseType = typeof(ControllerBase);
|
|
|
|
|
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
|
|
|
|
|
.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
|
|
|
|
|
.PropertiesAutowired(); //支持属性注入
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**主程序入口配置**
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// 让控制器实例由容器创建
|
|
|
|
|
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
|
|
|
|
|
|
|
|
|
|
// 如果将Service放在 WebApi同级目录下,可以完成Controller自动注入,不需要写构造函数
|
|
|
|
|
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
|
|
|
|
|
builder.Services.AddControllers().AddJsonOptions(options =>
|
|
|
|
|
options.JsonSerializerOptions.Converters.Add(new JsonDateTimeConverter()));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 日志配置
|
|
|
|
|
|
|
|
|
|
### 数据库生成
|
|
|
|
|
|
|
|
|
|
在这里面有数据库生成内容
|
|
|
|
|
|
|
|
|
|
![image-20240902230702816](./images/image-20240902230702816.png)
|
|
|
|
|
|
|
|
|
|
如果需要写入数据库放开这个注释
|
|
|
|
|
|
|
|
|
|
![image-20240902230759404](./images/image-20240902230759404.png)
|
|
|
|
|
|
|
|
|
|
> **配置参考**
|
|
|
|
|
>
|
|
|
|
|
> ```xml
|
|
|
|
|
> <?xml version="1.0" encoding="utf-8"?>
|
|
|
|
|
> <log4net>
|
|
|
|
|
> <!-- Define some output appenders -->
|
|
|
|
|
> <appender name="RollingAppender" type="log4net.Appender.RollingFileAppender">
|
|
|
|
|
> <file value="log4net\log4net.log"/>
|
|
|
|
|
> <!--追加日志内容-->
|
|
|
|
|
> <appendToFile value="true"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--防止多线程时不能写Log,官方说线程非安全-->
|
|
|
|
|
> <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--可以为:Once|Size|Date|Composite-->
|
|
|
|
|
> <!--Composite为Size和Date的组合-->
|
|
|
|
|
> <rollingStyle value="Composite"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--当备份文件时,为文件名加的后缀-->
|
|
|
|
|
> <datePattern value="yyyyMMdd.TXT"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--日志最大个数,都是最新的-->
|
|
|
|
|
> <!--rollingStyle节点为Size时,只能有value个日志-->
|
|
|
|
|
> <!--rollingStyle节点为Composite时,每天有value个日志-->
|
|
|
|
|
> <maxSizeRollBackups value="20"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--可用的单位:KB|MB|GB-->
|
|
|
|
|
> <maximumFileSize value="3MB"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--置为true,当前最新日志文件名永远为file节中的名字-->
|
|
|
|
|
> <staticLogFileName value="true"/>
|
|
|
|
|
>
|
|
|
|
|
> <!--输出级别在INFO和ERROR之间的日志-->
|
|
|
|
|
> <filter type="log4net.Filter.LevelRangeFilter">
|
|
|
|
|
> <param name="LevelMin" value="ALL"/>
|
|
|
|
|
> <param name="LevelMax" value="FATAL"/>
|
|
|
|
|
> </filter>
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <conversionPattern value="%date [%thread] %-5level %logger - %Message%newline"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </appender>
|
|
|
|
|
>
|
|
|
|
|
> <!--SqlServer形式-->
|
|
|
|
|
> <!--log4net日志配置:http://logging.apache.org/log4net/release/config-examples.html -->
|
|
|
|
|
> <appender name="AdoNetAppenderSqlServer" type="log4net.Appender.AdoNetAppender">
|
|
|
|
|
> <!--日志缓存写入条数 设置为0时只要有一条就立刻写到数据库-->
|
|
|
|
|
> <bufferSize value="0"/>
|
|
|
|
|
> <!--日志数据库连接串-->
|
|
|
|
|
> <connectionType
|
|
|
|
|
> value="System.Data.SqlClient.SqlConnection,System.Data.SqlClient, Version=4.6.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
|
|
|
|
|
> <connectionString
|
|
|
|
|
> value="Data Source=192.168.3.98;Initial Catalog=LogManager;Persist Security Info=True;User ID=sa;Password=abc1234."/>
|
|
|
|
|
> <commandText
|
|
|
|
|
> value="INSERT INTO Log4Net ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @Message, @exception)"/>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@log_date"/>
|
|
|
|
|
> <dbType value="DateTime"/>
|
|
|
|
|
> <layout type="log4net.Layout.RawTimeStampLayout"/>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@thread"/>
|
|
|
|
|
> <dbType value="String"/>
|
|
|
|
|
> <size value="255"/>
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <conversionPattern value="%thread"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@log_level"/>
|
|
|
|
|
> <dbType value="String"/>
|
|
|
|
|
> <size value="50"/>
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <conversionPattern value="%level"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@logger"/>
|
|
|
|
|
> <dbType value="String"/>
|
|
|
|
|
> <size value="255"/>
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <conversionPattern value="%logger"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@Message"/>
|
|
|
|
|
> <dbType value="String"/>
|
|
|
|
|
> <size value="4000"/>
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <conversionPattern value="%Message"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> <parameter>
|
|
|
|
|
> <parameterName value="@exception"/>
|
|
|
|
|
> <dbType value="String"/>
|
|
|
|
|
> <size value="2000"/>
|
|
|
|
|
> <layout type="log4net.Layout.ExceptionLayout"/>
|
|
|
|
|
> </parameter>
|
|
|
|
|
> </appender>
|
|
|
|
|
>
|
|
|
|
|
> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
|
|
|
|
|
> <layout type="log4net.Layout.PatternLayout">
|
|
|
|
|
> <!-- 自定义日志格式 -->
|
|
|
|
|
> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
|
|
|
|
|
> </layout>
|
|
|
|
|
> </appender>
|
|
|
|
|
>
|
|
|
|
|
> <root>
|
|
|
|
|
>
|
|
|
|
|
> <!--控制级别,由低到高: ALL|DEBUG|INFO|WARN|ERROR|FATAL|OFF-->
|
|
|
|
|
> <!--OFF:0-->
|
|
|
|
|
> <!--FATAL:FATAL-->
|
|
|
|
|
> <!--ERROR: ERROR,FATAL-->
|
|
|
|
|
> <!--WARN: WARN,ERROR,FATAL-->
|
|
|
|
|
> <!--INFO: INFO,WARN,ERROR,FATAL-->
|
|
|
|
|
> <!--DEBUG: INFO,WARN,ERROR,FATAL-->
|
|
|
|
|
> <!--ALL: DEBUG,INFO,WARN,ERROR,FATAL-->
|
|
|
|
|
> <priority value="ALL"/>
|
|
|
|
|
> <!-- TODO 修改成你自己的类型 -->
|
|
|
|
|
> <level value="INFO"/>
|
|
|
|
|
>
|
|
|
|
|
> <!-- 写入文本文件 -->
|
|
|
|
|
> <appender-ref ref="RollingAppender"/>
|
|
|
|
|
> <!-- 写入数据库 -->
|
|
|
|
|
> <appender-ref ref="AdoNetAppenderSqlServer"/>
|
|
|
|
|
> <!-- 控制台输出 -->
|
|
|
|
|
> <appender-ref ref="ConsoleAppender"/>
|
|
|
|
|
> </root>
|
|
|
|
|
> </log4net>
|
|
|
|
|
> ```
|
|
|
|
|
|
|
|
|
|
## 统一日期返回格式
|
|
|
|
|
|
|
|
|
|
对于控制器来说,需要指定返回日期格式可以看下这个,
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 自定义Json转换器,用于将DateTime类型转换为JSON格式
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class JsonDateTimeConverter : JsonConverter<DateTime>
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 重写读取方法,将JSON数据转换为DateTime类型
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="reader">JSON读取器</param>
|
|
|
|
|
/// <param name="typeToConvert">要转换的目标类型</param>
|
|
|
|
|
/// <param name="options">JSON序列化选项</param>
|
|
|
|
|
/// <returns>转换后的DateTime对象</returns>
|
|
|
|
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
|
|
|
{
|
|
|
|
|
return DateTime.TryParse(reader.GetString(), out var date) ? date : default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 重写写入方法,将DateTime对象转换为JSON格式并写入JSON写入器
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="writer">JSON写入器</param>
|
|
|
|
|
/// <param name="value">要写入的DateTime对象</param>
|
|
|
|
|
/// <param name="options">JSON序列化选项</param>
|
|
|
|
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
|
|
|
|
{
|
|
|
|
|
// 将DateTime对象转换为特定格式的字符串并写入JSON写入器
|
|
|
|
|
writer.WriteStringValue(value.ToString(LocalDateTimeConstant.DefaultDateTimeSecondFormat));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-09-04 15:44:07 +08:00
|
|
|
|
## 常见过滤器
|
|
|
|
|
|
|
|
|
|
Authorization Filter(授权过滤器):用于控制用户是否有权限访问特定的资源。
|
|
|
|
|
|
|
|
|
|
Action Filter(动作过滤器):在执行动作方法前后执行特定的逻辑。
|
|
|
|
|
|
|
|
|
|
Result Filter(结果过滤器):在执行结果前后执行特定的逻辑。
|
|
|
|
|
|
|
|
|
|
Exception Filter(异常过滤器):用于处理应用程序中发生的异常。
|
|
|
|
|
|
|
|
|
|
1. AuthorizationFilterAttribute
|
|
|
|
|
是所有过滤器中最先执行的。
|
|
|
|
|
用于执行授权检查。
|
|
|
|
|
只有一个方法:OnAuthorization,它在ActionFilterAttribute的OnActionExecuting之前执行。
|
|
|
|
|
如果授权失败,可以抛出AuthorizationException或返回Challenge或Forbid结果来阻止请求继续执行。
|
|
|
|
|
|
|
|
|
|
2. ResourceFilterAttribute
|
|
|
|
|
用于在动作方法执行前后立即执行逻辑,但比ActionFilterAttribute更早或更晚。
|
|
|
|
|
有两个方法:OnResourceExecuting和OnResourceExecuted。
|
|
|
|
|
可以用来执行需要在动作方法执行前或后立即进行的操作,例如缓存。
|
|
|
|
|
|
|
|
|
|
3. ExceptionFilterAttribute
|
|
|
|
|
专门用于处理异常。
|
|
|
|
|
有两个方法:OnException和OnExceptionAsync。
|
|
|
|
|
通常用于捕获和处理动作方法执行过程中抛出的异常。
|
|
|
|
|
由于它在异常发生后才执行,因此不适合用于清理资源,因为那时资源可能已经被破坏。
|
|
|
|
|
|
|
|
|
|
4. ResultFilterAttribute
|
|
|
|
|
用于在动作结果执行前后执行逻辑。
|
|
|
|
|
有两个方法:OnResultExecuting和OnResultExecuted。
|
|
|
|
|
与ActionFilterAttribute类似,但专注于动作结果的处理,例如渲染视图或返回JSON数据。
|
|
|
|
|
|
|
|
|
|
5. FormatFilterAttribute
|
|
|
|
|
是一个内置的过滤器,用于处理内容协商。
|
|
|
|
|
自动添加到控制器或动作方法上,当请求指定媒体类型时触发。
|
|
|
|
|
|
|
|
|
|
6. ServiceFilterAttribute
|
|
|
|
|
用于从服务容器中解析并执行一个过滤器实例。
|
|
|
|
|
不是直接实现过滤器逻辑,而是引用一个已经注册到依赖注入容器中的过滤器实例。
|
|
|
|
|
|
|
|
|
|
7. TypeFilterAttribute
|
|
|
|
|
类似于ServiceFilterAttribute,但它是通过反射创建过滤器实例,不需要过滤器在DI容器中注册。
|
|
|
|
|
可以接受参数,用于构造过滤器实例。
|