using System.Collections.Immutable;
using System.Globalization;
using Dpz.Core.Public.Entity.Auth;
using OpenIddict.Abstractions;
namespace Dpz.Core.Service.OpenIddictStores;
public sealed class DpzScopeStore(IRepository<DpzScope> repository, ILogger<DpzScopeStore> logger)
: IOpenIddictScopeStore<DpzScope>
{
public async ValueTask<long> CountAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Counting scopes");
return await repository.MongodbQueryable.LongCountAsync(
cancellationToken: cancellationToken
);
}
public async ValueTask<long> CountAsync<TResult>(
Func<IQueryable<DpzScope>, IQueryable<TResult>> query,
CancellationToken cancellationToken
)
{
logger.LogInformation("Counting scopes with custom query");
return await query(repository.MongodbQueryable)
.LongCountAsync(cancellationToken: cancellationToken);
}
public async ValueTask CreateAsync(DpzScope scope, CancellationToken cancellationToken)
{
logger.LogInformation("Creating scope {Name} ({Id})", scope.Name, scope.Id);
await repository.InsertAsync(scope, cancellationToken);
}
public async ValueTask DeleteAsync(DpzScope scope, CancellationToken cancellationToken)
{
logger.LogWarning("Deleting scope {Name} ({Id})", scope.Name, scope.Id);
await repository.DeleteAsync(scope.Id, cancellationToken);
}
public async ValueTask<DpzScope?> FindByIdAsync(
string identifier,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find scope by id {Identifier}", identifier);
if (ObjectId.TryParse(identifier, out var id))
{
return await repository.FindAsync(id, cancellationToken);
}
return null;
}
public async ValueTask<DpzScope?> FindByNameAsync(
string name,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find scope by name {Name}", name);
return await repository
.SearchFor(x => x.Name == name)
.FirstOrDefaultAsync(cancellationToken);
}
public IAsyncEnumerable<DpzScope> FindByNamesAsync(
ImmutableArray<string> names,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find scopes by names (count={Count})", names.Length);
if (names.IsDefaultOrEmpty)
{
return AsyncEnumerable.Empty<DpzScope>();
}
var filter = Builders<DpzScope>.Filter.In(x => x.Name, names);
return repository.SearchForAsync(filter, cancellationToken);
}
public IAsyncEnumerable<DpzScope> FindByResourceAsync(
string resource,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find scopes by resource {Resource}", resource);
var filter = Builders<DpzScope>.Filter.AnyEq(x => x.Resources, resource);
return repository.SearchForAsync(filter, cancellationToken);
}
public async ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<DpzScope>, TState, IQueryable<TResult>> query,
TState state,
CancellationToken cancellationToken
)
{
logger.LogInformation("Get with custom query state={State}", state);
return await Task.FromResult(query(repository.MongodbQueryable, state).FirstOrDefault());
}
public async ValueTask<string?> GetDescriptionAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get description of scope {Id}", scope.Id);
return await ValueTask.FromResult(scope.Description);
}
public async ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get descriptions of scope {Id}", scope.Id);
if (scope.Descriptions is null || scope.Descriptions.Count == 0)
{
return await ValueTask.FromResult(ImmutableDictionary<CultureInfo, string>.Empty);
}
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var kv in scope.Descriptions)
{
try
{
builder[CultureInfo.GetCultureInfo(kv.Key)] = kv.Value;
}
catch
{
// 跳过无法解析的文化键
}
}
return await ValueTask.FromResult(builder.ToImmutable());
}
public async ValueTask<string?> GetDisplayNameAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get display name of scope {Id}", scope.Id);
return await ValueTask.FromResult(scope.DisplayName);
}
public async ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get display names of scope {Id}", scope.Id);
if (scope.DisplayNames is null || scope.DisplayNames.Count == 0)
{
return await ValueTask.FromResult(ImmutableDictionary<CultureInfo, string>.Empty);
}
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var kv in scope.DisplayNames)
{
try
{
builder[CultureInfo.GetCultureInfo(kv.Key)] = kv.Value;
}
catch
{
// 跳过无法解析的文化键
}
}
return await ValueTask.FromResult(builder.ToImmutable());
}
public async ValueTask<string?> GetIdAsync(DpzScope scope, CancellationToken cancellationToken)
{
logger.LogTrace("Get id of scope {Id}", scope.Id);
return await ValueTask.FromResult(scope.Id.ToString());
}
public async ValueTask<string?> GetNameAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get name of scope {Id}", scope.Id);
return await ValueTask.FromResult(scope.Name);
}
public ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get properties of scope {Id}", scope.Id);
return ValueTask.FromResult(
OpenIddictBsonPropertySerializer.ToProperties(scope.Properties)
);
}
public async ValueTask<ImmutableArray<string>> GetResourcesAsync(
DpzScope scope,
CancellationToken cancellationToken
)
{
logger.LogTrace("Get resources of scope {Id}", scope.Id);
var list = scope.Resources ?? [];
return await ValueTask.FromResult(ImmutableArray.CreateRange(list));
}
public async ValueTask<DpzScope> InstantiateAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Instantiate new scope");
return await ValueTask.FromResult(new DpzScope { Id = ObjectId.GenerateNewId() });
}
public IAsyncEnumerable<DpzScope> ListAsync(
int? count,
int? offset,
CancellationToken cancellationToken
)
{
logger.LogInformation(
"List scopes with pagination: offset={Offset}, count={Count}",
offset,
count
);
var options = new FindOptions<DpzScope>();
if (offset.HasValue)
{
options.Skip = offset.Value;
}
if (count.HasValue)
{
options.Limit = count.Value;
}
return repository.SearchForAsync(
Builders<DpzScope>.Filter.Empty,
options,
cancellationToken
);
}
public async IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<DpzScope>, TState, IQueryable<TResult>> query,
TState state,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken
)
{
logger.LogInformation("List scopes with custom query state={State}", state);
var projected = query(repository.MongodbQueryable, state);
await foreach (
var element in projected.ToAsyncEnumerable().WithCancellation(cancellationToken)
)
{
yield return element;
}
}
public async ValueTask SetDescriptionAsync(
DpzScope scope,
string? description,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set description of scope {Id}", scope.Id);
scope.Description = description;
await ValueTask.CompletedTask;
}
public async ValueTask SetDescriptionsAsync(
DpzScope scope,
ImmutableDictionary<CultureInfo, string> descriptions,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set descriptions of scope {Id}", scope.Id);
scope.Descriptions = descriptions.ToDictionary(k => k.Key.Name, v => v.Value);
await ValueTask.CompletedTask;
}
public async ValueTask SetDisplayNameAsync(
DpzScope scope,
string? name,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set display name of scope {Id}", scope.Id);
scope.DisplayName = name;
await ValueTask.CompletedTask;
}
public async ValueTask SetDisplayNamesAsync(
DpzScope scope,
ImmutableDictionary<CultureInfo, string> names,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set display names of scope {Id}", scope.Id);
scope.DisplayNames = names.ToDictionary(k => k.Key.Name, v => v.Value);
await ValueTask.CompletedTask;
}
public async ValueTask SetNameAsync(
DpzScope scope,
string? name,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set name of scope {Id}", scope.Id);
scope.Name = name;
await ValueTask.CompletedTask;
}
public ValueTask SetPropertiesAsync(
DpzScope scope,
ImmutableDictionary<string, JsonElement> properties,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set properties of scope {Id}", scope.Id);
scope.Properties = OpenIddictBsonPropertySerializer.ToDocument(properties);
return ValueTask.CompletedTask;
}
public async ValueTask SetResourcesAsync(
DpzScope scope,
ImmutableArray<string> resources,
CancellationToken cancellationToken
)
{
logger.LogInformation("Set resources of scope {Id}", scope.Id);
scope.Resources = resources.IsDefault ? [] : resources.ToArray();
await ValueTask.CompletedTask;
}
public async ValueTask UpdateAsync(DpzScope scope, CancellationToken cancellationToken)
{
logger.LogInformation("Updating scope {Name} ({Id})", scope.Name, scope.Id);
await repository.UpdateAsync(scope, cancellationToken);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释:OpenIddict Scope Store 实现
这是一个实现了 OpenIddict 框架中 IOpenIddictScopeStore<DpzScope> 接口的自定义作用域存储类,用于在 MongoDB 数据库中管理 OAuth 2.0/OpenID Connect 的作用域(Scope)数据。
核心功能
1. 类定义与依赖注入
public sealed class DpzScopeStore(IRepository<DpzScope> repository, ILogger<DpzScopeStore> logger)
- 使用主构造函数(C# 12特性)注入 MongoDB 仓储和日志记录器
sealed表示该类不可被继承
2. CRUD 操作
创建 (Create)
CreateAsync: 插入新的作用域记录InstantiateAsync: 创建新的作用域实例(生成新的 ObjectId)
读取 (Read)
FindByIdAsync: 根据 ID 查找作用域FindByNameAsync: 根据名称查找作用域FindByNamesAsync: 批量根据名称查找(返回异步枚举)FindByResourceAsync: 根据资源标识符查找关联的作用域ListAsync: 支持分页的列表查询
更新 (Update)
UpdateAsync: 更新作用域信息- 多个
Set*Async方法:设置作用域的各个属性
删除 (Delete)
DeleteAsync: 删除指定作用域
3. 统计功能
CountAsync(): 统计所有作用域数量
CountAsync<TResult>(...): 使用自定义查询统计
4. 属性访问方法
基本属性
GetIdAsync: 获取 ID(转换为字符串)GetNameAsync: 获取名称GetDisplayNameAsync: 获取显示名称GetDescriptionAsync: 获取描述
多语言支持
GetDescriptionsAsync / GetDisplayNamesAsync
- 返回
ImmutableDictionary<CultureInfo, string>,支持多文化本地化 - 处理文化键解析异常(跳过无效的文化信息)
关联资源
GetResourcesAsync(): 返回作用域关联的资源列表
扩展属性
GetPropertiesAsync() / SetPropertiesAsync()
- 使用
OpenIddictBsonPropertySerializer在 BSON 和 JSON 之间转换自定义属性
5. 高级查询
GetAsync<TState, TResult>(): 执行自定义 LINQ 查询
ListAsync<TState, TResult>(): 执行自定义查询并返回异步枚举
- 支持传入状态参数和自定义查询逻辑
- 使用
yield return实现流式数据返回
6. 关键技术细节
MongoDB 过滤器构建
Builders<DpzScope>.Filter.In(x => x.Name, names) // IN 查询
Builders<DpzScope>.Filter.AnyEq(x => x.Resources, resource) // 数组元素匹配
异步枚举处理
[EnumeratorCancellation] // 支持取消令牌传播
await foreach (var element in projected.ToAsyncEnumerable()...)
不可变集合使用
- 方法返回值大量使用
ImmutableArray和ImmutableDictionary - 确保数据安全性和线程安全
日志记录
- 所有操作都有对应的日志记录
- 使用不同的日志级别(Trace/Information/Warning)
应用场景
此类用于 OpenIddict 身份认证服务器中,管理 OAuth 2.0 作用域,例如:
openid、profile、email等标准作用域- 自定义 API 作用域
- 支持多语言的作用域描述和显示名称
- 将作用域与资源服务器关联
总结
这是一个完整的、生产级的 MongoDB 存储适配器实现,展示了如何将 OpenIddict 与 MongoDB 集成,包含了完善的错误处理、日志记录和异步操作支持。
AI 正在分析代码…
评论加载中...