diff --git a/.idea/.idea.Bunny.WebApi/.idea/dataSources.xml b/.idea/.idea.Bunny.WebApi/.idea/dataSources.xml index a485d43..9b7261b 100644 --- a/.idea/.idea.Bunny.WebApi/.idea/dataSources.xml +++ b/.idea/.idea.Bunny.WebApi/.idea/dataSources.xml @@ -1,15 +1,23 @@ - + sqlite.xerial true org.sqlite.JDBC - jdbc:sqlite:D:\MyFolder\Bunny\Bunny-cli\CSharp\CSharp-Single-EFCore\Bunny.WebApi\bunny.db + jdbc:sqlite:D:\Project\web\Bunny-Cli\template\CSharp\CSharp-Single-EFCore\Bunny.WebApi\Database\bunny.db $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar + + \ No newline at end of file diff --git a/Bunny.Common/Connect/EFCoreContext.cs b/Bunny.Common/Connect/EFCoreContext.cs index 645970b..57652cb 100644 --- a/Bunny.Common/Connect/EFCoreContext.cs +++ b/Bunny.Common/Connect/EFCoreContext.cs @@ -28,14 +28,9 @@ public class EfCoreContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { - // 添加过滤器 - modelBuilder.Entity() - .HasQueryFilter(e => !e.IsDeleted); - - // 默认值为false,表示未删除 - modelBuilder.Entity() - .Property(e => e.IsDeleted) - .HasDefaultValue(false); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + if (typeof(BaseEntity).IsAssignableFrom(entityType.ClrType)) + entityType.AddSoftDeleteQueryFilter(); base.OnModelCreating(modelBuilder); } @@ -74,7 +69,7 @@ public class EfCoreContext : DbContext if (item.Entity is BaseEntity entity) switch (item.State) { - //添加操作 + // 添加操作 case EntityState.Added: { if (entity.Id == string.Empty) entity.Id = Guid.NewGuid().ToString(); @@ -84,10 +79,10 @@ public class EfCoreContext : DbContext entity.UpdateUserId = BaseContext.GetUserId()!.Value; break; } - //修改操作 + // 修改操作 case EntityState.Modified: entity.UpdateTime = DateTime.Now; - entity.UpdateUserId = BaseContext.GetUserId()!.Value; + if (BaseContext.GetUserId() != null) entity.UpdateUserId = BaseContext.GetUserId()!.Value; break; case EntityState.Detached: break; @@ -97,8 +92,6 @@ public class EfCoreContext : DbContext entity.UpdateTime = DateTime.Now; entity.UpdateUserId = BaseContext.GetUserId()!.Value; break; - default: - throw new ArgumentOutOfRangeException(); } } } \ No newline at end of file diff --git a/Bunny.Common/Connect/RedisContext.cs b/Bunny.Common/Connect/RedisContext.cs index c8bd9d3..8949c64 100644 --- a/Bunny.Common/Connect/RedisContext.cs +++ b/Bunny.Common/Connect/RedisContext.cs @@ -5,7 +5,7 @@ namespace Bunny.Common.Connect; public static class RedisContext { - public static IDatabase RedisDatabase; + public static IDatabase? RedisDatabase; private static readonly EndPointCollection EndPointCollection = new(); public static void AddRedisContext(this WebApplicationBuilder builder) diff --git a/Bunny.Common/Connect/SoftDeleteQueryExtension.cs b/Bunny.Common/Connect/SoftDeleteQueryExtension.cs new file mode 100644 index 0000000..b583b5c --- /dev/null +++ b/Bunny.Common/Connect/SoftDeleteQueryExtension.cs @@ -0,0 +1,36 @@ +using System.Linq.Expressions; +using System.Reflection; +using Bunny.Dao.Entity.Base; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Bunny.Common.Connect; + +/// +/// 软删除过滤器 +/// +public static class SoftDeleteQueryExtension +{ + /// + /// 软删除过滤器 + /// + /// + public static void AddSoftDeleteQueryFilter(this IMutableEntityType entityData) + { + var methodToCall = typeof(SoftDeleteQueryExtension) + .GetMethod(nameof(GetSoftDeleteFilter), BindingFlags.NonPublic | BindingFlags.Static) + ?.MakeGenericMethod(entityData.ClrType); + var filter = methodToCall?.Invoke(null, []); + entityData.SetQueryFilter((LambdaExpression)filter!); + } + + /// + /// 软删除 + /// + /// + /// + private static LambdaExpression GetSoftDeleteFilter() where TEntity : BaseEntity + { + Expression> filter = x => !x.IsDeleted; + return filter; + } +} \ No newline at end of file diff --git a/Bunny.Common/Context/BaseContext.cs b/Bunny.Common/Context/BaseContext.cs index 297d59e..2eab517 100644 --- a/Bunny.Common/Context/BaseContext.cs +++ b/Bunny.Common/Context/BaseContext.cs @@ -3,33 +3,33 @@ public class BaseContext { // 使用Lazy初始化确保线程安全 - public static readonly ThreadLocal UserId = new(); - public static readonly ThreadLocal Token = new(); + public static readonly ThreadLocal? UserId = null; + public static readonly ThreadLocal? Token = null; // 用户id相关 public static long? GetUserId() { - return UserId.Value; + return UserId?.Value; } public static void SetUserId(long? userId) { - UserId.Value = userId; + UserId!.Value = userId; } public static string? GetToken() { - return Token.Value; + return Token?.Value; } public static void SetToken(string token) { - Token.Value = token; + Token!.Value = token; } public static void RemoveUser() { - Token.Value = null; - UserId.Value = null; + if (Token != null) Token.Value = null; + if (UserId != null) UserId.Value = null; } } \ No newline at end of file diff --git a/Bunny.Dao/Entity/Base/BaseEntity.cs b/Bunny.Dao/Entity/Base/BaseEntity.cs index 5875efc..d3ece92 100644 --- a/Bunny.Dao/Entity/Base/BaseEntity.cs +++ b/Bunny.Dao/Entity/Base/BaseEntity.cs @@ -12,7 +12,5 @@ public class BaseEntity public long UpdateUserId { get; set; } - public string? OperationMessage { get; set; } - public bool IsDeleted { get; set; } } \ No newline at end of file diff --git a/Bunny.Dao/Entity/System/Blog.cs b/Bunny.Dao/Entity/System/Blog.cs index 516e119..986ec52 100644 --- a/Bunny.Dao/Entity/System/Blog.cs +++ b/Bunny.Dao/Entity/System/Blog.cs @@ -5,5 +5,5 @@ namespace Bunny.Dao.Entity.System; public class Blog : BaseEntity { - [Required(ErrorMessage = "邮箱是必填项")] public string Url { get; set; } + [Required(ErrorMessage = "Url是必填项")] public string Url { get; set; } } \ No newline at end of file diff --git a/Bunny.Service/IService/Service/JobService.cs b/Bunny.Service/IService/Service/JobService.cs index 0924f09..3a60d3b 100644 --- a/Bunny.Service/IService/Service/JobService.cs +++ b/Bunny.Service/IService/Service/JobService.cs @@ -13,7 +13,6 @@ public class JobService : IJobService public void StartSimpleJob() { var schedulerFactory = new StdSchedulerFactory(); - // TODO 如果使用异步,必须使用await 和 async // var scheduler = schedulerFactory.GetScheduler(); var scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult(); @@ -32,10 +31,6 @@ public class JobService : IJobService // 使用同步方法 scheduler.ScheduleJob(jobDetail, trigger).GetAwaiter().GetResult(); scheduler.Start().GetAwaiter().GetResult(); - - // TODO 使用异步 - // await _scheduler.ScheduleJob(jobDetail, trigger); - // await _scheduler.Start(); } /// @@ -49,8 +44,8 @@ public class JobService : IJobService var jobDetail = JobBuilder.Create() .UsingJobData("username", "用户名") .UsingJobData("password", "密码") - // TODO 如果不设置持久的会报错; - // TODO Jobs added with no trigger must be durable.Quartz.SchedulerException: Jobs added with no trigger must be durable. + // 如果不设置持久的会报错; + // Jobs added with no trigger must be durable.Quartz.SchedulerException: Jobs added with no trigger must be durable. .StoreDurably() // 设置作业为持久的 .WithIdentity("simpleJob", "简单的JOB") .Build(); @@ -63,9 +58,9 @@ public class JobService : IJobService .UsingJobData("trigger", "trigger值") .WithIdentity("testTrigger", "测试发出器") .StartNow() - // TODO 设置调度时间单位 + // 设置调度时间单位 // .WithSimpleSchedule(x => x.WithIntervalInSeconds(1).WithRepeatCount(10)) - // TODO 自定义调度时间单位 + // 自定义调度时间单位 .WithSimpleSchedule(x => x.WithInterval(TimeSpan.FromMinutes(1)).WithRepeatCount(10)) .Build(); @@ -95,9 +90,9 @@ public class JobService : IJobService .UsingJobData("trigger", "trigger值") .WithIdentity("testTrigger", "测试发出器") .StartNow() - // TODO StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(20, 0)):触发器从每天的20:00(晚上8点)开始。 - // TODO EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(22, 0)):触发器在每天的22:00(晚上10点)结束。 - // TODO WithIntervalInSeconds(2):在开始和结束时间之间,触发器将每2秒触发一次作业。 + // StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(20, 0)):触发器从每天的20:00(晚上8点)开始。 + // EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(22, 0)):触发器在每天的22:00(晚上10点)结束。 + // WithIntervalInSeconds(2):在开始和结束时间之间,触发器将每2秒触发一次作业。 .WithDailyTimeIntervalSchedule(x => x.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(16, 0)) .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(22, 0)) // 设置工作时间在每周的星期几 diff --git a/Bunny.WebApi/Config/BaseConfig.cs b/Bunny.WebApi/Config/BaseConfig.cs index f7de169..c05ccbb 100644 --- a/Bunny.WebApi/Config/BaseConfig.cs +++ b/Bunny.WebApi/Config/BaseConfig.cs @@ -11,7 +11,7 @@ public class BaseConfig(WebApplicationBuilder builder) /// public void Initialize() { - // 配置跨域 + // 配置跨域 UseCors(); // 配置日志相关 // builder.Logging.AddLog4Net("Config/log4net.config"); @@ -24,8 +24,11 @@ public class BaseConfig(WebApplicationBuilder builder) // 设置文件最大上传大小 options.Limits.MaxRequestBodySize = 1024 * 1024 * 100; }); - // 添加Service服务 + // 注入后台服务 builder.AddApplicationServices(); + // 设置控制器返回时间,统一格式 + builder.Services.AddControllers().AddJsonOptions(options => + options.JsonSerializerOptions.Converters.Add(new JsonDateTimeConverter())); // 添加后台服务 builder.AddApplicationBackendServices(); // 添加验证码 diff --git a/Bunny.WebApi/Database/bunny.db b/Bunny.WebApi/Database/bunny.db index ba69319..8381f69 100644 Binary files a/Bunny.WebApi/Database/bunny.db and b/Bunny.WebApi/Database/bunny.db differ diff --git a/Bunny.WebApi/Database/bunny.db-shm b/Bunny.WebApi/Database/bunny.db-shm index 7600918..83f77db 100644 Binary files a/Bunny.WebApi/Database/bunny.db-shm and b/Bunny.WebApi/Database/bunny.db-shm differ diff --git a/Bunny.WebApi/Database/bunny.db-wal b/Bunny.WebApi/Database/bunny.db-wal index 07ff647..45880b8 100644 Binary files a/Bunny.WebApi/Database/bunny.db-wal and b/Bunny.WebApi/Database/bunny.db-wal differ diff --git a/Bunny.WebApi/ReadMe.md b/Bunny.WebApi/ReadMe.md index 297b093..4e6e28a 100644 --- a/Bunny.WebApi/ReadMe.md +++ b/Bunny.WebApi/ReadMe.md @@ -62,3 +62,77 @@ service.AddCaptcha(options => }); } ``` + +## AutoFac配置 + +### 自动注入按照名称注入 + +**AutoFac配置详情** + +```csharp +using Autofac; +using Bunny.Common.Attribute; +using Microsoft.AspNetCore.Mvc; + +namespace Bunny.WebApi.Config; + +public static class AddAutofacConfig +{ + /// + /// AutoFac自动注入约定名称 + /// 接口以Service结尾注入到IOC中 + /// + /// + 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().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()); + +// 如果将Service放在 WebApi同级目录下,可以完成Controller自动注入,不需要写构造函数 +builder.Services.Replace(ServiceDescriptor.Transient()); +builder.Services.AddControllers().AddJsonOptions(options => + options.JsonSerializerOptions.Converters.Add(new JsonDateTimeConverter())); +``` \ No newline at end of file