You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为OpenIddict编写生产就绪脚本替换AddHostedService初始化数据?

OpenIddict生产环境数据库初始化方案

核心结论

OpenIddict的数据库表结构可以用EF Core迁移初始化,但客户端、权限范围这类业务数据不适合放在迁移中,推荐用独立的幂等性脚本/工具来完成,更符合生产环境的配置隔离需求。


一、表结构初始化:用EF Core迁移

这是OpenIddict官方推荐的标准方式,用来创建其依赖的数据库表(如OpenIddictApplications、OpenIddictScopes等):

  1. 确保项目已配置EF Core和OpenIddict的EF存储:
    services.AddDbContext<YourDbContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        options.UseOpenIddict();
    });
    
    services.AddOpenIddict()
        .AddCore(options => options.UseEntityFrameworkCore().UseDbContext<YourDbContext>())
        // 其他配置(如服务器、验证)...
    
  2. 生成并应用迁移:
    # 生成初始化迁移
    dotnet ef migrations add InitialOpenIddictSetup -c YourDbContext
    # 应用到数据库
    dotnet ef database update -c YourDbContext
    
  3. Azure DevOps集成:用DotNetCoreCLI任务执行dotnet ef database update,或生成SQL脚本(dotnet ef migrations script)后用SQL任务执行。

二、客户端/权限范围初始化:用独立幂等脚本

EF迁移是为结构变更设计的,客户端、权限范围属于环境特定的业务配置(开发/测试/生产环境可能不同),放在迁移中会导致配置混淆。推荐两种方案:

方案1:编写.NET控制台工具(类型安全)

创建独立控制台项目,引用你的DbContext和OpenIddict包,编写幂等初始化逻辑:

using Microsoft.EntityFrameworkCore;
using YourProject.Data;
using OpenIddict.Abstractions;

var connString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING") 
    ?? throw new InvalidOperationException("连接字符串未设置");

var options = new DbContextOptionsBuilder<YourDbContext>()
    .UseSqlServer(connString)
    .Options;

using var context = new YourDbContext(options);
await context.Database.EnsureCreatedAsync();

// 初始化权限范围
var apiScope = await context.OpenIddictScopes.FirstOrDefaultAsync(s => s.Name == "api");
if (apiScope == null)
{
    await context.OpenIddictScopes.AddAsync(new OpenIddictScopeDescriptor
    {
        Name = "api",
        DisplayName = "API访问权限",
        Description = "授予API端点访问权限",
        Resources = { "https://your-prod-api.com" }
    }.ToEntity());
}

// 初始化客户端
var webClient = await context.OpenIddictApplications.FirstOrDefaultAsync(a => a.ClientId == "prod-web-client");
if (webClient == null)
{
    await context.OpenIddictApplications.AddAsync(new OpenIddictApplicationDescriptor
    {
        ClientId = "prod-web-client",
        ClientSecret = Environment.GetEnvironmentVariable("CLIENT_SECRET_HASH"), // 生产环境用哈希值
        DisplayName = "生产环境Web客户端",
        RedirectUris = { new Uri("https://your-prod-web.com/callback") },
        Permissions =
        {
            OpenIddictConstants.Permissions.Endpoints.Authorization,
            OpenIddictConstants.Permissions.Endpoints.Token,
            OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
            OpenIddictConstants.Permissions.Scopes.Email,
            "api"
        },
        Type = OpenIddictConstants.ClientTypes.Confidential
    }.ToEntity());
}

await context.SaveChangesAsync();
  • 把工具打包成可执行文件,在Azure DevOps流水线中添加任务运行,用流水线机密管理存储连接字符串、客户端密钥哈希等敏感信息。
  • 密钥哈希可通过OpenIddictHelpers.HashPassword("your-secret")生成,生产环境严禁存明文。

方案2:编写SQL脚本(轻量直接)

若不想写.NET代码,可直接编写幂等SQL脚本插入数据:

-- 插入权限范围
IF NOT EXISTS (SELECT 1 FROM OpenIddictScopes WHERE Name = 'api')
BEGIN
    INSERT INTO OpenIddictScopes (Name, DisplayName, Description, Resources, ConcurrencyToken)
    VALUES (
        'api',
        'API访问权限',
        '授予API端点访问权限',
        '["https://your-prod-api.com"]',
        NEWID()
    );
END

-- 插入客户端(注意:ClientSecret必须是哈希值)
IF NOT EXISTS (SELECT 1 FROM OpenIddictApplications WHERE ClientId = 'prod-web-client')
BEGIN
    INSERT INTO OpenIddictApplications (
        ClientId, ClientSecret, DisplayName, RedirectUris, Permissions,
        ConcurrencyToken, Type, ConsentType
    )
    VALUES (
        'prod-web-client',
        'AQAAAAEAACcQAAAAEL...', -- 替换为实际哈希值
        '生产环境Web客户端',
        '["https://your-prod-web.com/callback"]',
        '["authorization_code", "token", "authorization", "email", "api"]',
        NEWID(),
        'confidential',
        'explicit'
    );
END
  • 在Azure DevOps中用SQL Server任务执行脚本,同样用流水线变量管理敏感数据。

三、Azure DevOps流水线关键要点

  1. 环境隔离:用变量组存储不同环境的配置(如客户端ID、回调URI),避免硬编码。
  2. 执行顺序:先运行EF迁移确保表结构存在,再执行数据初始化脚本。
  3. 幂等性:所有初始化逻辑必须保证多次执行不会重复创建数据(用IF NOT EXISTS或代码查询判断),这是生产就绪脚本的核心要求。

内容的提问来源于stack exchange,提问作者Audam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:20:11