using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Dpz.Core.Public.Entity.Auth;
using Dpz.Core.Service.OpenIddictStores;
using OpenIddict.Abstractions;

namespace Dpz.Core.Service;

public class TokenStoreService<T>(IRepository<T> repository, ILogger<TokenStoreService<T>> logger)
    : IOpenIddictTokenStore<T>
    where T : AuthToken, new()
{
    public async ValueTask<long> CountAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Counting tokens");
        return await repository.MongodbQueryable.LongCountAsync(
            cancellationToken: cancellationToken
        );
    }

    public async ValueTask<long> CountAsync<TResult>(
        Func<IQueryable<T>, IQueryable<TResult>> query,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Counting tokens with custom query");
        return await query(repository.MongodbQueryable)
            .LongCountAsync(cancellationToken: cancellationToken);
    }

    public async ValueTask CreateAsync(T token, CancellationToken cancellationToken)
    {
        logger.LogInformation("Creating token {Id}", token.Id);
        await repository.InsertAsync(token, cancellationToken);
    }

    public async ValueTask DeleteAsync(T token, CancellationToken cancellationToken)
    {
        logger.LogInformation("Deleting token {Id}", token.Id);
        await repository.DeleteAsync(token.Id, cancellationToken);
    }

    public IAsyncEnumerable<T> FindAsync(
        string? subject,
        string? client,
        string? status,
        string? type,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation(
            "Find tokens subject={Subject}, client={Client}, status={Status}, type={Type}",
            subject,
            client,
            status,
            type
        );
        var filter = Builders<T>.Filter.Empty;

        if (!string.IsNullOrWhiteSpace(subject))
        {
            filter &= Builders<T>.Filter.Eq(x => x.Subject, subject);
        }
        if (!string.IsNullOrWhiteSpace(client) && ObjectId.TryParse(client, out var appId))
        {
            filter &= Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
        }
        if (!string.IsNullOrWhiteSpace(status))
        {
            filter &= Builders<T>.Filter.Eq(x => x.Status, status);
        }
        if (!string.IsNullOrWhiteSpace(type))
        {
            filter &= Builders<T>.Filter.Eq(x => x.Type, type);
        }

        return repository.SearchForAsync(filter, cancellationToken);
    }

    public IAsyncEnumerable<T> FindByApplicationIdAsync(
        string identifier,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Find tokens by application id {Identifier}", identifier);
        if (!ObjectId.TryParse(identifier, out var appId))
        {
            return AsyncEnumerable.Empty<T>();
        }
        var filter = Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
        return repository.SearchForAsync(filter, cancellationToken);
    }

    public IAsyncEnumerable<T> FindByAuthorizationIdAsync(
        string identifier,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Find tokens by authorization id {Identifier}", identifier);
        if (!ObjectId.TryParse(identifier, out var authId))
        {
            return AsyncEnumerable.Empty<T>();
        }
        var filter = Builders<T>.Filter.Eq(x => x.AuthorizationId, authId);
        return repository.SearchForAsync(filter, cancellationToken);
    }

    public async ValueTask<T?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
    {
        logger.LogInformation("Find token by id {Identifier}", identifier);
        if (ObjectId.TryParse(identifier, out var id))
        {
            return await repository.FindAsync(id, cancellationToken);
        }
        return null;
    }

    public async ValueTask<T?> FindByReferenceIdAsync(
        string identifier,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Find token by reference id {Identifier}", identifier);
        return await repository
            .SearchFor(x => x.ReferenceId == identifier)
            .FirstOrDefaultAsync(cancellationToken);
    }

    public IAsyncEnumerable<T> FindBySubjectAsync(
        string subject,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Find tokens by subject {Subject}", subject);
        var filter = Builders<T>.Filter.Eq(x => x.Subject, subject);
        return repository.SearchForAsync(filter, cancellationToken);
    }

    public async ValueTask<string?> GetApplicationIdAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        return await ValueTask.FromResult(token.ApplicationId.ToString());
    }

    public async ValueTask<TResult?> GetAsync<TState, TResult>(
        Func<IQueryable<T>, TState, IQueryable<TResult>> query,
        TState state,
        CancellationToken cancellationToken
    )
    {
        return await Task.FromResult(query(repository.MongodbQueryable, state).FirstOrDefault());
    }

    public async ValueTask<string?> GetAuthorizationIdAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        return await ValueTask.FromResult(token.AuthorizationId.ToString());
    }

    public async ValueTask<DateTimeOffset?> GetCreationDateAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        var result = token.CreationDate is { } dt
            ? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
            : (DateTimeOffset?)null;
        return await ValueTask.FromResult(result);
    }

    public async ValueTask<DateTimeOffset?> GetExpirationDateAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        var result = token.ExpirationDate is { } dt
            ? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
            : (DateTimeOffset?)null;
        return await ValueTask.FromResult(result);
    }

    public async ValueTask<string?> GetIdAsync(T token, CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(token.Id.ToString());
    }

    public async ValueTask<string?> GetPayloadAsync(T token, CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(token.Payload);
    }

    public ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        return ValueTask.FromResult(
            OpenIddictBsonPropertySerializer.ToProperties(token.Properties)
        );
    }

    public async ValueTask<DateTimeOffset?> GetRedemptionDateAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        var result = token.RedemptionDate is { } dt
            ? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
            : (DateTimeOffset?)null;
        return await ValueTask.FromResult(result);
    }

    public async ValueTask<string?> GetReferenceIdAsync(
        T token,
        CancellationToken cancellationToken
    )
    {
        return await ValueTask.FromResult(token.ReferenceId);
    }

    public async ValueTask<string?> GetStatusAsync(T token, CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(token.Status);
    }

    public async ValueTask<string?> GetSubjectAsync(T token, CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(token.Subject);
    }

    public async ValueTask<string?> GetTypeAsync(T token, CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(token.Type);
    }

    public async ValueTask<T> InstantiateAsync(CancellationToken cancellationToken)
    {
        return await ValueTask.FromResult(new T { Id = ObjectId.GenerateNewId() });
    }

    public IAsyncEnumerable<T> ListAsync(
        int? count,
        int? offset,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation(
            "List tokens with pagination: offset={Offset}, count={Count}",
            offset,
            count
        );
        var options = new FindOptions<T>();
        if (offset.HasValue)
        {
            options.Skip = offset.Value;
        }
        if (count.HasValue)
        {
            options.Limit = count.Value;
        }

        return repository.SearchForAsync(Builders<T>.Filter.Empty, options, cancellationToken);
    }

    public async IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
        Func<IQueryable<T>, TState, IQueryable<TResult>> query,
        TState state,
        [EnumeratorCancellation] CancellationToken cancellationToken
    )
    {
        logger.LogInformation("List tokens 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<long> PruneAsync(
        DateTimeOffset threshold,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Prune tokens threshold={Threshold}", threshold);
        var builder = Builders<T>.Filter;
        var filter = builder.Or(
            builder.Lt(x => x.ExpirationDate, threshold.UtcDateTime),
            builder.Lt(x => x.RedemptionDate, threshold.UtcDateTime)
        );
        var result = await repository.Collection.DeleteManyAsync(filter, cancellationToken);
        return result.DeletedCount;
    }

    public async ValueTask<long> RevokeAsync(
        string? subject,
        string? client,
        string? status,
        string? type,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation(
            "Revoke tokens subject={Subject}, client={Client}, status={Status}, type={Type}",
            subject,
            client,
            status,
            type
        );
        var builder = Builders<T>.Filter;
        var filter = builder.Empty;

        if (!string.IsNullOrWhiteSpace(subject))
        {
            filter &= builder.Eq(x => x.Subject, subject);
        }
        if (!string.IsNullOrWhiteSpace(client) && ObjectId.TryParse(client, out var appId))
        {
            filter &= builder.Eq(x => x.ApplicationId, appId);
        }
        if (!string.IsNullOrWhiteSpace(status))
        {
            filter &= builder.Eq(x => x.Status, status);
        }
        if (!string.IsNullOrWhiteSpace(type))
        {
            filter &= builder.Eq(x => x.Type, type);
        }

        var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
        var result = await repository.Collection.UpdateManyAsync(
            filter,
            update,
            cancellationToken: cancellationToken
        );
        return result.ModifiedCount;
    }

    public async ValueTask<long> RevokeByApplicationIdAsync(
        string identifier,
        CancellationToken cancellationToken = new CancellationToken()
    )
    {
        logger.LogInformation("Revoke tokens by application id {Identifier}", identifier);
        if (!ObjectId.TryParse(identifier, out var appId))
        {
            return 0L;
        }
        var filter = Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
        var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
        var result = await repository.Collection.UpdateManyAsync(
            filter,
            update,
            cancellationToken: cancellationToken
        );
        return result.ModifiedCount;
    }

    public async ValueTask<long> RevokeByAuthorizationIdAsync(
        string identifier,
        CancellationToken cancellationToken
    )
    {
        logger.LogInformation("Revoke tokens by authorization id {Identifier}", identifier);
        if (!ObjectId.TryParse(identifier, out var authId))
        {
            return 0L;
        }
        var filter = Builders<T>.Filter.Eq(x => x.AuthorizationId, authId);
        var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
        var result = await repository.Collection.UpdateManyAsync(
            filter,
            update,
            cancellationToken: cancellationToken
        );
        return result.ModifiedCount;
    }

    public async ValueTask<long> RevokeBySubjectAsync(
        string subject,
        CancellationToken cancellationToken = new CancellationToken()
    )
    {
        logger.LogInformation("Revoke tokens by subject {Subject}", subject);
        var filter = Builders<T>.Filter.Eq(x => x.Subject, subject);
        var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
        var result = await repository.Collection.UpdateManyAsync(
            filter,
            update,
            cancellationToken: cancellationToken
        );
        return result.ModifiedCount;
    }

    public async ValueTask SetApplicationIdAsync(
        T token,
        string? identifier,
        CancellationToken cancellationToken
    )
    {
        if (ObjectId.TryParse(identifier, out var appId))
        {
            token.ApplicationId = appId;
        }
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetAuthorizationIdAsync(
        T token,
        string? identifier,
        CancellationToken cancellationToken
    )
    {
        if (ObjectId.TryParse(identifier, out var authId))
        {
            token.AuthorizationId = authId;
        }
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetCreationDateAsync(
        T token,
        DateTimeOffset? date,
        CancellationToken cancellationToken
    )
    {
        token.CreationDate = date?.UtcDateTime;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetExpirationDateAsync(
        T token,
        DateTimeOffset? date,
        CancellationToken cancellationToken
    )
    {
        token.ExpirationDate = date?.UtcDateTime;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetPayloadAsync(
        T token,
        string? payload,
        CancellationToken cancellationToken
    )
    {
        token.Payload = payload;
        await ValueTask.CompletedTask;
    }

    public ValueTask SetPropertiesAsync(
        T token,
        ImmutableDictionary<string, JsonElement> properties,
        CancellationToken cancellationToken
    )
    {
        token.Properties = OpenIddictBsonPropertySerializer.ToDocument(properties);
        return ValueTask.CompletedTask;
    }

    public async ValueTask SetRedemptionDateAsync(
        T token,
        DateTimeOffset? date,
        CancellationToken cancellationToken
    )
    {
        token.RedemptionDate = date?.UtcDateTime;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetReferenceIdAsync(
        T token,
        string? identifier,
        CancellationToken cancellationToken
    )
    {
        token.ReferenceId = identifier;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetStatusAsync(
        T token,
        string? status,
        CancellationToken cancellationToken
    )
    {
        token.Status = status;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetSubjectAsync(
        T token,
        string? subject,
        CancellationToken cancellationToken
    )
    {
        token.Subject = subject;
        await ValueTask.CompletedTask;
    }

    public async ValueTask SetTypeAsync(T token, string? type, CancellationToken cancellationToken)
    {
        token.Type = type;
        await ValueTask.CompletedTask;
    }

    public async ValueTask UpdateAsync(T token, CancellationToken cancellationToken)
    {
        logger.LogInformation("Update token {Id}", token.Id);
        await repository.UpdateAsync(token, cancellationToken);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个用于 OpenIddict 框架的 Token(令牌)存储服务实现,使用 MongoDB 作为底层数据存储。

核心功能

类定义

public class TokenStoreService<T> : IOpenIddictTokenStore<T>
    where T : AuthToken, new()
  • 泛型类,T 必须继承自 AuthToken 并有无参构造函数
  • 实现了 OpenIddict 的 IOpenIddictTokenStore<T> 接口
  • 使用主构造函数注入 IRepository<T> 和 ILogger

主要方法分类

1. 查询方法

  • CountAsync: 统计令牌总数
  • FindAsync: 多条件查询(subject/client/status/type)
  • FindByApplicationIdAsync: 根据应用ID查找
  • FindByAuthorizationIdAsync: 根据授权ID查找
  • FindByIdAsync: 根据令牌ID查找
  • FindByReferenceIdAsync: 根据引用ID查找
  • FindBySubjectAsync: 根据主题查找
  • ListAsync: 分页列表查询

2. CRUD 操作

  • CreateAsync: 创建令牌
  • UpdateAsync: 更新令牌
  • DeleteAsync: 删除令牌
  • InstantiateAsync: 实例化新令牌对象(自动生成 ObjectId)

3. 属性读取方法(Getter)

提供了一系列 Get*Async 方法获取令牌属性:

  • ApplicationId、AuthorizationId
  • CreationDate、ExpirationDate、RedemptionDate(日期转换为 UTC)
  • Status、Type、Subject
  • Payload、Properties(使用 BSON 序列化)

4. 属性设置方法(Setter)

对应的 Set*Async 方法用于设置令牌属性:

  • 日期字段转换为 UTC DateTime
  • ObjectId 字段进行解析验证
  • Properties 使用自定义序列化器转换

5. 批量操作

  • PruneAsync: 清理过期/已赎回的令牌

    // 删除 ExpirationDate 或 RedemptionDate 早于阈值的记录
    
  • Revoke 系列方法: 撤销令牌(将 Status 设为 Revoked)

    • RevokeAsync(多条件)
    • RevokeByApplicationIdAsync
    • RevokeByAuthorizationIdAsync
    • RevokeBySubjectAsync

技术特点

  1. MongoDB 集成

    • 使用 MongoDB Filter/Update Builders 构建查询
    • ObjectId 类型转换和验证
    • 异步枚举支持
  2. 日志记录

    • 所有关键操作都记录日志
    • 包含操作参数便于追踪
  3. 异步模式

    • 全部使用 ValueTask / IAsyncEnumerable
    • 支持 CancellationToken 取消操作
  4. 数据转换

    • DateTime ↔ DateTimeOffset(UTC 时区处理)
    • ObjectId ↔ string
    • Properties ↔ BSON Document(通过 OpenIddictBsonPropertySerializer)
  5. 空值处理

    • ObjectId 解析失败返回空集合或 0
    • 可空日期字段的安全转换

使用场景

这个服务是 OpenIddict 在 MongoDB 环境下管理 OAuth 2.0 / OpenID Connect 令牌的核心组件,负责令牌的完整生命周期管理。

评论加载中...