diff --git a/README.md b/README.md index 78d38682..ba862756 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ EasilyNET Packages - 对 MongoDB 驱动的一些封装,方便使用以及一些常用的默认配置 - 雪花 ID,一些常用的数据类型,枚举,扩展方法等 - 自动模块化注入服务,新增支持 WPF,WinForm 等项目,需使用 IHost 通用主机模式 -- MongoDB 添加 DateOnly 和 TimeOnly 的支持 +- MongoDB 添加 DateOnly 和 TimeOnly 的支持, dynamic 类型支持 - MongoDB GridFS 用法的简单支持(常用用法)和使用案例. - 在 WebAPI 中集成一些常见的过滤器和中间件 - 对 Swagger 文档添加分组,隐藏 API 和添加部分数据类型默认值显示的支持,方便前端工程师查阅 @@ -54,7 +54,7 @@ EasilyNET Packages - Some encapsulation of MongoDB driver for easy use and some common default configurations - Snowflake ID, some common data types, enumerations, extension methods, etc. - Automatic modular injection services, adding support for WPF, WinForm and other projects, using IHost common host mode -- Added support for DateOnly and TimeOnly in MongoDB +- Added support for DateOnly and TimeOnly in MongoDB, dynamic type support - Simple support for MongoDB GridFS usage (common usage) and usage examples. - Integration of some common filters and middleware in WebAPI - Added support for grouping, hiding APIs, and displaying default values for some data types in Swagger documents, making it easier for front-end engineers to refer to diff --git a/sample/WebApi.Test.Unit/Controllers/MongoLockController.cs b/sample/WebApi.Test.Unit/Controllers/MongoLockController.cs index 7240d5e0..a9628ae0 100644 --- a/sample/WebApi.Test.Unit/Controllers/MongoLockController.cs +++ b/sample/WebApi.Test.Unit/Controllers/MongoLockController.cs @@ -9,8 +9,39 @@ namespace WebApi.Test.Unit.Controllers; /// MongoDB分布式锁测试 /// [Route("api/[controller]/[action]"), ApiController, ApiGroup("MongoLock", "v1", "基于MongoDB实现的分布式锁测试")] -public class MongoLockController(IMongoLockFactory lockFactory) : ControllerBase +public class MongoLockController(IMongoLockFactory lockFactory, DbContext db) : ControllerBase { + /// + /// 测试锁 + /// + /// + [HttpPost] + public async Task BusinessTest() + { + const string lockId = "64d44afda4473b85a177084d"; // 这里使用一个随机的ID作为锁ID,相当于其他锁中的Key.用来区分不同的业务的锁 + var mongoLock = lockFactory.GenerateNewLock(ObjectId.Parse(lockId)); + var acq = await mongoLock.AcquireAsync(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(0)); + try + { + if (acq.Acquired) + { + await db.GetCollection("locks_test").InsertOneAsync(new + { + AcquiredId = acq.AcquireId + }); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + finally + { + await mongoLock.ReleaseAsync(acq); + } + } + /// /// AcquireLock /// diff --git a/sample/WebApi.Test.Unit/Script.js b/sample/WebApi.Test.Unit/Script.js index 07d2abfa..ae2d1a85 100644 --- a/sample/WebApi.Test.Unit/Script.js +++ b/sample/WebApi.Test.Unit/Script.js @@ -1,15 +1,32 @@ -import { check } from "k6"; +import { check, sleep } from "k6"; import http from "k6/http"; export const options = { - stages: [ - { duration: "30s", target: 20 }, - { duration: "1m30s", target: 10 }, - { duration: "20s", target: 0 }, - ], + stages: [ + { duration: "60s", target: 100 }, // Ramp-up to 100 users over 30s + { duration: "60s", target: 200 }, // Ramp-up to 200 users over 1 minute + { duration: "60s", target: 300 }, // Ramp-up to 300 users over 1 minute + { duration: "60s", target: 500 }, // Ramp-down to 0 users over 1 minute + ], }; export default function () { - const res = http.post("http://localhost:5046/api/MongoTest/MongoPost"); - check(res, { "status was 200": (r) => r.status == 200 }); + const url = 'http://localhost:8848/api/MongoLock/BusinessTest'; + + const payload = JSON.stringify({ + email: 'aaa', + password: 'bbb' + }); + + const params = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const res = http.post(url, params); + check(res, { "status was 200": (r) => r.status == 200 }); + + // Optional: Add a small sleep to simulate real user behavior + sleep(1); } diff --git a/sample/WebApi.Test.Unit/ServiceModules/MongoModule.cs b/sample/WebApi.Test.Unit/ServiceModules/MongoModule.cs index 42d0e28f..b8d067ad 100644 --- a/sample/WebApi.Test.Unit/ServiceModules/MongoModule.cs +++ b/sample/WebApi.Test.Unit/ServiceModules/MongoModule.cs @@ -92,9 +92,12 @@ public override void ConfigureServices(ConfigureServicesContext context) // }).Build()); // b.AddSimpleConsole(); //})); + cs.MaxConnecting = int.MaxValue; + cs.MaxConnectionPoolSize = int.MaxValue; }; }); context.Services.RegisterSerializer(new DateOnlySerializerAsString()); context.Services.RegisterSerializer(new TimeOnlySerializerAsString()); + context.Services.RegisterDynamicSerializer(); } } \ No newline at end of file diff --git a/src/EasilyNET.MongoDistributedLock/Acquire.cs b/src/EasilyNET.MongoDistributedLock/Acquire.cs index 65f85cc7..e3064d0b 100644 --- a/src/EasilyNET.MongoDistributedLock/Acquire.cs +++ b/src/EasilyNET.MongoDistributedLock/Acquire.cs @@ -6,14 +6,18 @@ namespace EasilyNET.MongoDistributedLock; /// internal sealed class Acquire : IAcquire { + private readonly IDistributedLock? _distributedLock; + /// /// 构造函数 /// /// - public Acquire(ObjectId acquireId) + /// + public Acquire(ObjectId acquireId, IDistributedLock distributedLock) { Acquired = true; AcquireId = acquireId; + _distributedLock = distributedLock; } /// @@ -29,4 +33,12 @@ public Acquire() /// public ObjectId AcquireId { get; } + + public async ValueTask DisposeAsync() + { + if (Acquired && _distributedLock is not null) + { + await _distributedLock.ReleaseAsync(this); + } + } } \ No newline at end of file diff --git a/src/EasilyNET.MongoDistributedLock/Attributes/IAcquire.cs b/src/EasilyNET.MongoDistributedLock/Attributes/IAcquire.cs index 8b4bc391..afac22b2 100644 --- a/src/EasilyNET.MongoDistributedLock/Attributes/IAcquire.cs +++ b/src/EasilyNET.MongoDistributedLock/Attributes/IAcquire.cs @@ -5,7 +5,7 @@ namespace EasilyNET.MongoDistributedLock.Attributes; /// /// IAcquire /// -public interface IAcquire +public interface IAcquire : IAsyncDisposable { /// /// true if lock successfully acquired; otherwise, false diff --git a/src/EasilyNET.MongoDistributedLock/DistributedLock.cs b/src/EasilyNET.MongoDistributedLock/DistributedLock.cs index 570f5797..3789c4b7 100644 --- a/src/EasilyNET.MongoDistributedLock/DistributedLock.cs +++ b/src/EasilyNET.MongoDistributedLock/DistributedLock.cs @@ -34,10 +34,10 @@ public async Task AcquireAsync(TimeSpan lifetime, TimeSpan timeout) var acquire = await _locks.Find(bf.Eq(c => c.Id, _lockId)).FirstOrDefaultAsync(); if (acquire is not null && await WaitSignalAsync(acquire.AcquireId, timeout) == false) { - return await TryUpdateAsync(lifetime, acquireId) ? new Acquire(acquireId) : new(); + return await TryUpdateAsync(lifetime, acquireId) ? new Acquire(acquireId, this) : new(); } } - return new Acquire(acquireId); + return new Acquire(acquireId, this); } /// diff --git a/src/EasilyNET.MongoSerializer.AspNetCore/ServiceCollectionExtensions.cs b/src/EasilyNET.MongoSerializer.AspNetCore/ServiceCollectionExtensions.cs index ab4f6837..f2604503 100644 --- a/src/EasilyNET.MongoSerializer.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/EasilyNET.MongoSerializer.AspNetCore/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; // ReSharper disable UnusedMethodReturnValue.Global // ReSharper disable MemberCanBePrivate.Global @@ -25,16 +26,16 @@ public static IServiceCollection RegisterSerializer(this IServiceCollection s return services; } - ///// - ///// 注册动态类型( | )序列化支持 - ///// - ///// - ///// + /// + /// 注册动态类型 [ | ] 序列化支持 + /// + /// + /// //[Obsolete("MongoDB.Drive 2.18之前和2.21之后,又默认支持动态类型的序列化了(2.19-2.20之间的版本,仅允许反序列化被视为安全的类型).暂时标记为过时.以后应该会移除")] - //public static IServiceCollection RegisterDynamicSerializer(this IServiceCollection services) - //{ - // var objectSerializer = new ObjectSerializer(type => ObjectSerializer.DefaultAllowedTypes(type) || (type.FullName is not null && type.FullName.StartsWith("<>f__AnonymousType"))); - // BsonSerializer.RegisterSerializer(objectSerializer); - // return services; - //} + public static IServiceCollection RegisterDynamicSerializer(this IServiceCollection services) + { + var objectSerializer = new ObjectSerializer(type => ObjectSerializer.DefaultAllowedTypes(type) || (type.FullName is not null && type.FullName.StartsWith("<>f__AnonymousType"))); + BsonSerializer.RegisterSerializer(objectSerializer); + return services; + } } \ No newline at end of file