diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorHostedService.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorHostedService.cs new file mode 100644 index 000000000..2590ba079 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorHostedService.cs @@ -0,0 +1,53 @@ +using LINGYUN.Abp.MicroService.AIService.DbMigrator; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Data; + +namespace LINGYUN.Abp.MicroService.AIService; +public class AIServiceDbMigratorHostedService : IHostedService +{ + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly IConfiguration _configuration; + + public AIServiceDbMigratorHostedService( + IHostApplicationLifetime hostApplicationLifetime, + IConfiguration configuration) + { + _hostApplicationLifetime = hostApplicationLifetime; + _configuration = configuration; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using var application = await AbpApplicationFactory + .CreateAsync(options => + { + options.Services.ReplaceConfiguration(_configuration); + options.UseAutofac(); + options.Services.AddLogging(c => c.AddSerilog()); + options.AddDataMigrationEnvironment(); + }); + + await application.InitializeAsync(); + + await application + .ServiceProvider + .GetRequiredService() + .CheckAndApplyDatabaseMigrationsAsync(); + + await application.ShutdownAsync(); + + _hostApplicationLifetime.StopApplication(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorModule.cs new file mode 100644 index 000000000..ff9dd1c85 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/AIServiceDbMigratorModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.MicroService.AIService.DbMigrator; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AIServiceMigrationsEntityFrameworkCoreModule) + )] +public class AIServiceDbMigratorModule : AbpModule +{ + +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/FodyWeavers.xml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/LINGYUN.Abp.MicroService.AIService.DbMigrator.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/LINGYUN.Abp.MicroService.AIService.DbMigrator.csproj new file mode 100644 index 000000000..662765cf3 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/LINGYUN.Abp.MicroService.AIService.DbMigrator.csproj @@ -0,0 +1,40 @@ + + + + + + Exe + net10.0 + enable + false + LINGYUN.Abp.MicroService.AIService + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + Always + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/Program.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/Program.cs new file mode 100644 index 000000000..135bb215b --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/Program.cs @@ -0,0 +1,43 @@ +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; +using System; + +var defaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}"; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning) +#if DEBUG + .MinimumLevel.Override("LINGYUN.Abp.MicroService.AIService", LogEventLevel.Debug) +#else + .MinimumLevel.Override("LINGYUN.Abp.MicroService.AIService", LogEventLevel.Information) +#endif + .Enrich.FromLogContext() + .WriteTo.Async(x => x.Console(outputTemplate: defaultOutputTemplate)) + .WriteTo.Async(x => x.File("Logs/migrations.txt", outputTemplate: defaultOutputTemplate)) + .CreateLogger(); + +try +{ + var builder = Host.CreateDefaultBuilder(args) + .AddAppSettingsSecretsJson() + .ConfigureLogging((context, logging) => logging.ClearProviders()) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }); + await builder.RunConsoleAsync(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly!"); +} +finally +{ + await Log.CloseAndFlushAsync(); +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/appsettings.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/appsettings.json new file mode 100644 index 000000000..38212c579 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.DbMigrator/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "Default": "Host=127.0.0.1;Database=abp;Username=postgres;Password=123456" + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDataSeeder.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDataSeeder.cs new file mode 100644 index 000000000..8a088b76d --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDataSeeder.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.MicroService.AIService; +public class AIServiceDataSeeder : ITransientDependency +{ + protected ILogger Logger { get; } + protected ICurrentTenant CurrentTenant { get; } + + public AIServiceDataSeeder( + ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + + Logger = NullLogger.Instance; + } + + public virtual Task SeedAsync(DataSeedContext context) + { + return Task.CompletedTask; + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationEventHandler.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationEventHandler.cs new file mode 100644 index 000000000..b05842e54 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationEventHandler.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EntityFrameworkCore.Migrations; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.MicroService.AIService; +public class AIServiceDbMigrationEventHandler : EfCoreDatabaseMigrationEventHandlerBase +{ + protected AIServiceDataSeeder DataSeeder { get; } + + public AIServiceDbMigrationEventHandler( + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + ITenantStore tenantStore, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory, + AIServiceDataSeeder dataSeeder) + : base( + ConnectionStringNameAttribute.GetConnStringName(), + currentTenant, unitOfWorkManager, tenantStore, abpDistributedLock, distributedEventBus, loggerFactory) + { + DataSeeder = dataSeeder; + } + + protected async override Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated) + { + // 新租户数据种子 + var context = new DataSeedContext(eventData.Id); + if (eventData.Properties != null) + { + foreach (var property in eventData.Properties) + { + context.WithProperty(property.Key, property.Value); + } + } + + await DataSeeder.SeedAsync(context); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationService.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationService.cs new file mode 100644 index 000000000..445f0fd92 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceDbMigrationService.cs @@ -0,0 +1,36 @@ +using LINGYUN.Abp.Data.DbMigrator; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.MicroService.AIService; +public class AIServiceDbMigrationService : EfCoreRuntimeDbMigratorBase, ITransientDependency +{ + protected AIServiceDataSeeder DataSeeder { get; } + public AIServiceDbMigrationService( + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IServiceProvider serviceProvider, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory, + AIServiceDataSeeder dataSeeder) + : base( + ConnectionStringNameAttribute.GetConnStringName(), + unitOfWorkManager, serviceProvider, currentTenant, abpDistributedLock, distributedEventBus, loggerFactory) + { + DataSeeder = dataSeeder; + } + + protected async override Task SeedAsync() + { + // DbMigrator迁移数据种子 + await DataSeeder.SeedAsync(new DataSeedContext()); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs new file mode 100644 index 000000000..35d068464 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.AIManagement.Chats; +using LINGYUN.Abp.AIManagement.EntityFrameworkCore; +using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Workspaces; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.MicroService.AIService; + +[ConnectionStringName("Default")] +public class AIServiceMigrationsDbContext : + AbpDbContext, + IAIManagementDbContext +{ + public DbSet WorkspaceDefinitions { get; set; } + public DbSet TextChatMessageRecords { get; set; } + public DbSet ConversationRecords { get; set; } + public DbSet TokenUsageRecords { get; set; } + + public AIServiceMigrationsDbContext( + DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigureAIManagement(); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContextFactory.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContextFactory.cs new file mode 100644 index 000000000..afd229619 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContextFactory.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace LINGYUN.Abp.MicroService.AIService; +public class AIServiceMigrationsDbContextFactory : IDesignTimeDbContextFactory +{ + public AIServiceMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var connectionString = configuration.GetConnectionString("Default"); + + var builder = new DbContextOptionsBuilder() + .UseNpgsql(connectionString); + + return new AIServiceMigrationsDbContext(builder!.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../LINGYUN.Abp.MicroService.AIService.DbMigrator/")) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsEntityFrameworkCoreModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..1c84f02f9 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsEntityFrameworkCoreModule.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.AIManagement.EntityFrameworkCore; +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using LINGYUN.Abp.TextTemplating.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.PostgreSql; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; + +namespace LINGYUN.Abp.MicroService.AIService; + +[DependsOn( + typeof(AbpAIManagementEntityFrameworkCoreModule), + typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpTextTemplatingEntityFrameworkCoreModule), + typeof(AbpEntityFrameworkCorePostgreSqlModule), + typeof(AbpDataDbMigratorModule) + )] +public class AIServiceMigrationsEntityFrameworkCoreModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + // https://www.npgsql.org/efcore/release-notes/6.0.html#opting-out-of-the-new-timestamp-mapping-logic + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(); + + Configure(options => + { + options.UseNpgsql(); + }); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore.csproj new file mode 100644 index 000000000..c85236273 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore.csproj @@ -0,0 +1,35 @@ + + + + + + + false + net10.0 + enable + LINGYUN.Abp.MicroService.AIService + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.Designer.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.Designer.cs new file mode 100644 index 000000000..3b253c2ca --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.Designer.cs @@ -0,0 +1,303 @@ +// +using System; +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + [DbContext(typeof(AIServiceMigrationsDbContext))] + [Migration("20260127083027_Initial_AI_Service")] + partial class Initial_AI_Service + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.ConversationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UpdateAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("AbpAIConversations", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.TextChatMessageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplyMessage") + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITextChatMessages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tokens.TokenUsageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CachedInputTokenCount") + .HasColumnType("bigint"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("InputTokenCount") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("OutputTokenCount") + .HasColumnType("bigint"); + + b.Property("ReasoningTokenCount") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalTokenCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITokenUsages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiBaseUrl") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApiKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FrequencyPenalty") + .HasColumnType("real"); + + b.Property("Instructions") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxOutputTokens") + .HasColumnType("integer"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PresencePenalty") + .HasColumnType("real"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemPrompt") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Temperature") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIWorkspaceDefinitions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.cs new file mode 100644 index 000000000..ed6c57888 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260127083027_Initial_AI_Service.cs @@ -0,0 +1,148 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + /// + public partial class Initial_AI_Service : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpAIConversations", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: true), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ExpiredAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdateAt = table.Column(type: "timestamp with time zone", nullable: true), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAIConversations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpAITextChatMessages", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Content = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + ExtraProperties = table.Column(type: "text", nullable: false), + ConcurrencyStamp = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: true), + Workspace = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Role = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserId = table.Column(type: "uuid", nullable: true), + ConversationId = table.Column(type: "uuid", nullable: true), + ReplyMessage = table.Column(type: "text", nullable: true), + ReplyAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAITextChatMessages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpAITokenUsages", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: true), + MessageId = table.Column(type: "uuid", nullable: true), + ConversationId = table.Column(type: "uuid", nullable: true), + InputTokenCount = table.Column(type: "bigint", nullable: true), + OutputTokenCount = table.Column(type: "bigint", nullable: true), + TotalTokenCount = table.Column(type: "bigint", nullable: true), + CachedInputTokenCount = table.Column(type: "bigint", nullable: true), + ReasoningTokenCount = table.Column(type: "bigint", nullable: true), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAITokenUsages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpAIWorkspaceDefinitions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Provider = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ModelName = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + DisplayName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Description = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + ApiKey = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + ApiBaseUrl = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + SystemPrompt = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + Instructions = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + Temperature = table.Column(type: "real", nullable: true), + MaxOutputTokens = table.Column(type: "integer", nullable: true), + FrequencyPenalty = table.Column(type: "real", nullable: true), + PresencePenalty = table.Column(type: "real", nullable: true), + IsEnabled = table.Column(type: "boolean", nullable: false), + StateCheckers = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "text", nullable: false), + ConcurrencyStamp = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAIWorkspaceDefinitions", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAITextChatMessages_TenantId_ConversationId", + table: "AbpAITextChatMessages", + columns: new[] { "TenantId", "ConversationId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAITokenUsages_TenantId_ConversationId", + table: "AbpAITokenUsages", + columns: new[] { "TenantId", "ConversationId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAIWorkspaceDefinitions_Name", + table: "AbpAIWorkspaceDefinitions", + column: "Name", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpAIConversations"); + + migrationBuilder.DropTable( + name: "AbpAITextChatMessages"); + + migrationBuilder.DropTable( + name: "AbpAITokenUsages"); + + migrationBuilder.DropTable( + name: "AbpAIWorkspaceDefinitions"); + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs new file mode 100644 index 000000000..095a0771d --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs @@ -0,0 +1,300 @@ +// +using System; +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + [DbContext(typeof(AIServiceMigrationsDbContext))] + partial class AIServiceMigrationsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.ConversationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UpdateAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("AbpAIConversations", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.TextChatMessageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplyMessage") + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITextChatMessages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tokens.TokenUsageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CachedInputTokenCount") + .HasColumnType("bigint"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("InputTokenCount") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("OutputTokenCount") + .HasColumnType("bigint"); + + b.Property("ReasoningTokenCount") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalTokenCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITokenUsages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiBaseUrl") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApiKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FrequencyPenalty") + .HasColumnType("real"); + + b.Property("Instructions") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxOutputTokens") + .HasColumnType("integer"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PresencePenalty") + .HasColumnType("real"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemPrompt") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Temperature") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIWorkspaceDefinitions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.Configure.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.Configure.cs new file mode 100644 index 000000000..f654a1bc1 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.Configure.cs @@ -0,0 +1,423 @@ +using DotNetCore.CAP; +using LINGYUN.Abp.AIManagement; +using LINGYUN.Abp.AIManagement.Chats; +using LINGYUN.Abp.Localization.CultureMap; +using LINGYUN.Abp.LocalizationManagement; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.TextTemplating; +using LINGYUN.Abp.Wrapper; +using Medallion.Threading; +using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using StackExchange.Redis; +using System.Text.Encodings.Web; +using System.Text.Unicode; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.AntiForgery; +using Volo.Abp.Auditing; +using Volo.Abp.Caching; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.FeatureManagement; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Http.Client; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Security.Claims; +using Volo.Abp.SettingManagement; +using Volo.Abp.Threading; +using Volo.Abp.Timing; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.MicroService.AIService; + +public partial class AIServiceModule +{ + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + private void PreConfigureFeature() + { + OneTimeRunner.Run(() => + { + GlobalFeatureManager.Instance.Modules.Editions().EnableAll(); + }); + } + + private void PreConfigureApp(IConfiguration configuration) + { + PreConfigure(options => + { + // 以开放端口区别,应在0-31之间 + options.SnowflakeIdOptions.WorkerId = 19; + options.SnowflakeIdOptions.WorkerIdBits = 5; + options.SnowflakeIdOptions.DatacenterId = 1; + }); + + if (configuration.GetValue("App:ShowPii")) + { + IdentityModelEventSource.ShowPII = true; + } + } + + private void PreConfigureCAP(IConfiguration configuration) + { + PreConfigure(options => + { + options + .UsePostgreSql(mySqlOptions => + { + configuration.GetSection("CAP:PostgreSql").Bind(mySqlOptions); + }) + .UseRabbitMQ(rabbitMQOptions => + { + configuration.GetSection("CAP:RabbitMQ").Bind(rabbitMQOptions); + }) + .UseDashboard(); + }); + } + + private void ConfigureTextTemplating() + { + Configure(options => + { + options.IsDynamicTemplateDefinitionStoreEnabled = true; + }); + } + + private void ConfigureFeatureManagement() + { + Configure(options => + { + options.IsDynamicFeatureStoreEnabled = true; + }); + } + + private void ConfigureJsonSerializer(IConfiguration configuration) + { + // 统一时间日期格式 + Configure(options => + { + var jsonConfiguration = configuration.GetSection("Json"); + if (jsonConfiguration.Exists()) + { + jsonConfiguration.Bind(options); + } + }); + // 中文序列化的编码问题 + Configure(options => + { + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); + } + + private void ConfigureAIManagement() + { + Configure(options => + { + options.IsDynamicWorkspaceStoreEnabled = true; + options.SaveStaticWorkspacesToDatabase = true; + }); + } + + private void ConfigurePermissionManagement() + { + Configure(options => + { + options.IsDynamicPermissionStoreEnabled = true; + }); + } + + private void ConfigureSettingManagement() + { + Configure(options => + { + options.IsDynamicSettingStoreEnabled = true; + }); + } + + private void ConfigureTiming(IConfiguration configuration) + { + Configure(options => + { + configuration.GetSection("Clock").Bind(options); + }); + } + + private void ConfigureCaching(IConfiguration configuration) + { + Configure(options => + { + configuration.GetSection("DistributedCache").Bind(options); + }); + + Configure(options => + { + options.AutoEventSelectors.AddNamespace("Volo.Abp.TenantManagement"); + }); + + Configure(options => + { + var redisConfig = ConfigurationOptions.Parse(options.Configuration!); + options.ConfigurationOptions = redisConfig; + options.InstanceName = configuration["Redis:InstanceName"]; + }); + } + + private void ConfigureDistributedLocking(IServiceCollection services, IConfiguration configuration) + { + var distributedLockEnabled = configuration["DistributedLock:IsEnabled"]; + if (distributedLockEnabled.IsNullOrEmpty() || bool.Parse(distributedLockEnabled)) + { + services.AddSingleton(sp => + { + var connectionMultiplexer = sp.GetRequiredService(); + return new RedisDistributedSynchronizationProvider(connectionMultiplexer.GetDatabase()); + }); + } + } + + private void ConfigureMvc(IServiceCollection services, IConfiguration configuration) + { + Configure(options => + { + options.ExposeIntegrationServices = true; + }); + } + + private void ConfigureVirtualFileSystem() + { + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.MicroService.AIService"); + }); + } + + private void ConfigureMultiTenancy(IConfiguration configuration) + { + // 多租户 + Configure(options => + { + options.IsEnabled = true; + }); + + var tenantResolveCfg = configuration.GetSection("App:Domains"); + if (tenantResolveCfg.Exists()) + { + Configure(options => + { + var domains = tenantResolveCfg.Get() ?? []; + foreach (var domain in domains) + { + options.AddDomainTenantResolver(domain); + } + }); + } + } + + private void ConfigureIdentity(IConfiguration configuration) + { + Configure(options => + { + options.IsDynamicClaimsEnabled = true; + options.RemoteRefreshUrl = configuration["App:RefreshClaimsUrl"] + options.RemoteRefreshUrl; + }); + } + + private void ConfigureAuditing(IConfiguration configuration) + { + Configure(options => + { + // 是否启用实体变更记录 + var allEntitiesSelectorIsEnabled = configuration["Auditing:AllEntitiesSelector"]; + if (allEntitiesSelectorIsEnabled.IsNullOrWhiteSpace() || + (bool.TryParse(allEntitiesSelectorIsEnabled, out var enabled) && enabled)) + { + options.EntityHistorySelectors.AddAllEntities(); + } + }); + } + + private void ConfigureSwagger(IServiceCollection services, IConfiguration configuration) + { + // Swagger + services.AddAbpSwaggerGenWithOAuth( + configuration["AuthServer:Authority"]!, + new Dictionary + { + { "AIService", "AI Service API"} + }, + options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Title = "AI Service API", Version = "v1", + Contact = new OpenApiContact + { + Name = "colin", + Email = "colin.in@foxmail.com", + Url = new Uri("https://github.com/colinin") + }, + License = new OpenApiLicense + { + Name = "MIT", + Url = new Uri("https://github.com/colinin/abp-next-admin/blob/master/LICENSE") + } + }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Scheme = "bearer", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new string[] { } + } + }); + options.OperationFilter(); + }); + } + + private void ConfigureLocalization() + { + // 支持本地化语言类型 + Configure(options => + { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources"); + }); + + Configure(options => + { + var zhHansCultureMapInfo = new CultureMapInfo + { + TargetCulture = "zh-Hans", + SourceCultures = new string[] { "zh", "zh_CN", "zh-CN" } + }; + + options.CulturesMaps.Add(zhHansCultureMapInfo); + options.UiCulturesMaps.Add(zhHansCultureMapInfo); + }); + + Configure(options => + { + options.SaveStaticLocalizationsToDatabase = true; + }); + } + + private void ConfigureCors(IServiceCollection services, IConfiguration configuration) + { + services.AddCors(options => + { + options.AddDefaultPolicy(builder => + { + var corsOrigins = configuration.GetSection("App:CorsOrigins").Get>(); + if (corsOrigins == null || corsOrigins.Count == 0) + { + corsOrigins = configuration["App:CorsOrigins"]? + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToList() ?? new List(); + } + builder + .WithOrigins(corsOrigins + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .WithAbpWrapExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + } + + private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false) + { + Configure(options => + { + options.TokenCookie.HttpOnly = false; + options.TokenCookie.SameSite = SameSiteMode.Lax; + }); + + services.AddAlwaysAllowAuthorization(); + services.AddAlwaysAllowSession(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddAbpJwtBearer(options => + { + configuration.GetSection("AuthServer").Bind(options); + + var validIssuers = configuration.GetSection("AuthServer:ValidIssuers").Get>(); + if (validIssuers?.Count > 0) + { + options.TokenValidationParameters.ValidIssuers = validIssuers; + options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; + } + var validAudiences = configuration.GetSection("AuthServer:ValidAudiences").Get>(); + if (validAudiences?.Count > 0) + { + options.TokenValidationParameters.ValidAudiences = validAudiences; + } + }); + + services + .AddDataProtection() + .SetApplicationName("LINGYUN.Abp.Application") + .PersistKeysToStackExchangeRedis(() => + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!); + + return redis.GetDatabase(); + }, + "LINGYUN.Abp.Application:DataProtection:Protection-Keys"); + } + + private void ConfigureWrapper() + { + Configure(options => + { + options.IsEnabled = true; + + options.IgnoreControllers.Add(); + }); + } + + private void PreConfigureWrapper() + { + // 服务间调用不包装 + PreConfigure(options => + { + options.ProxyClientActions.Add( + (_, _, client) => + { + client.DefaultRequestHeaders.TryAddWithoutValidation(AbpHttpWrapConsts.AbpDontWrapResult, "true"); + }); + }); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs new file mode 100644 index 000000000..e18ca18d8 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs @@ -0,0 +1,103 @@ +using LINGYUN.Abp.AIManagement; +using LINGYUN.Abp.AspNetCore.HttpOverrides; +using LINGYUN.Abp.AspNetCore.Mvc.Localization; +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; +using LINGYUN.Abp.AuditLogging.Elasticsearch; +using LINGYUN.Abp.Claims.Mapping; +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.Emailing.Platform; +using LINGYUN.Abp.EventBus.CAP; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.Identity.Session.AspNetCore; +using LINGYUN.Abp.Localization.CultureMap; +using LINGYUN.Abp.Logging.Serilog.Elasticsearch; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.TextTemplating.Scriban; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.Autofac; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Identity; +using Volo.Abp.PermissionManagement.OpenIddict; +using Volo.Abp.Swashbuckle; + +namespace LINGYUN.Abp.MicroService.AIService; + +[DependsOn( + typeof(AbpCAPEventBusModule), + typeof(AbpSerilogEnrichersApplicationModule), + typeof(AbpSerilogEnrichersUniqueIdModule), + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpLoggingSerilogElasticsearchModule), + typeof(AbpAuditLoggingElasticsearchModule), + typeof(AbpAspNetCoreMvcUiMultiTenancyModule), + typeof(AbpAspNetCoreMvcLocalizationModule), + + typeof(AbpPermissionManagementDomainIdentityModule), + typeof(AbpPermissionManagementDomainOpenIddictModule), + + // 重写模板引擎支持外部本地化 + typeof(AbpTextTemplatingScribanModule), + + typeof(AbpIdentitySessionAspNetCoreModule), + + typeof(AbpAIManagementApplicationModule), + typeof(AbpAIManagementHttpApiModule), + typeof(AIServiceMigrationsEntityFrameworkCoreModule), + typeof(AbpDataDbMigratorModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpEmailingExceptionHandlingModule), + typeof(AbpHttpClientModule), + typeof(AbpSmsPlatformModule), + typeof(AbpEmailingPlatformModule), + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpLocalizationCultureMapModule), + typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpClaimsMappingModule), + typeof(AbpSwashbuckleModule), + typeof(AbpAutofacModule) + )] +public partial class AIServiceModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + PreConfigureWrapper(); + PreConfigureFeature(); + PreConfigureApp(configuration); + PreConfigureCAP(configuration); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + ConfigureWrapper(); + ConfigureLocalization(); + ConfigureVirtualFileSystem(); + ConfigureTextTemplating(); + ConfigureAIManagement(); + ConfigureSettingManagement(); + ConfigureFeatureManagement(); + ConfigurePermissionManagement(); + ConfigureIdentity(configuration); + ConfigureTiming(configuration); + ConfigureCaching(configuration); + ConfigureAuditing(configuration); + ConfigureMultiTenancy(configuration); + ConfigureJsonSerializer(configuration); + ConfigureMvc(context.Services, configuration); + ConfigureCors(context.Services, configuration); + ConfigureSwagger(context.Services, configuration); + ConfigureDistributedLocking(context.Services, configuration); + ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/FodyWeavers.xml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj new file mode 100644 index 000000000..dafa6b426 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj @@ -0,0 +1,71 @@ + + + + net10.0 + enable + enable + LINGYUN.Abp.MicroService.AIService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Program.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Program.cs new file mode 100644 index 000000000..85b652951 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Program.cs @@ -0,0 +1,99 @@ +using LINGYUN.Abp.Identity.Session.AspNetCore; +using LINGYUN.Abp.MicroService.AIService; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using Serilog; +using Volo.Abp.IO; +using Volo.Abp.Modularity.PlugIns; + +Log.Information("Starting AIService Host..."); + +try +{ + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .ConfigureAppConfiguration((context, config) => + { + if (context.Configuration.GetValue("AgileConfig:IsEnabled", false)) + { + config.AddAgileConfig(new AgileConfig.Client.ConfigClient(context.Configuration)); + } + }) + .UseSerilog((context, provider, config) => + { + config.ReadFrom.Configuration(context.Configuration); + }); + + builder.AddServiceDefaults(); + + await builder.AddApplicationAsync(options => + { + var applicationName = Environment.GetEnvironmentVariable("APPLICATION_NAME") ?? "AIService"; + options.ApplicationName = applicationName; + AbpSerilogEnrichersConsts.ApplicationName = applicationName; + + var pluginFolder = Path.Combine(Directory.GetCurrentDirectory(), "Modules"); + DirectoryHelper.CreateIfNotExists(pluginFolder); + options.PlugInSources.AddFolder(pluginFolder, SearchOption.AllDirectories); + }); + + var app = builder.Build(); + + await app.InitializeApplicationAsync(); + + app.MapDefaultEndpoints(); + + app.UseForwardedHeaders(); + // 本地化 + app.UseMapRequestLocalization(); + // http调用链 + app.UseCorrelationId(); + // 文件系统 + app.MapAbpStaticAssets(); + // 路由 + app.UseRouting(); + // 跨域 + app.UseCors(); + // 认证 + app.UseAuthentication(); + app.UseJwtTokenMiddleware(); + // 多租户 + app.UseMultiTenancy(); + // 会话 + app.UseAbpSession(); + // jwt + app.UseDynamicClaims(); + // 授权 + app.UseAuthorization(); + // Swagger + app.UseSwagger(); + // Swagger可视化界面 + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support AI Service API"); + + var configuration = app.Configuration; + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthScopes(configuration["AuthServer:Audience"]); + }); + // 审计日志 + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + // 路由 + app.UseConfiguredEndpoints(); + + await app.RunAsync(); +} +catch (Exception ex) +{ + if (ex is HostAbortedException) + { + throw; + } + + Log.Fatal(ex, "Host terminated unexpectedly!"); +} +finally +{ + await Log.CloseAndFlushAsync(); +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Properties/launchSettings.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Properties/launchSettings.json new file mode 100644 index 000000000..312571616 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "LINGYUN.Abp.MicroService.AIService": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:49787;http://localhost:49788" + } + } +} \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/TenantHeaderParamter.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/TenantHeaderParamter.cs new file mode 100644 index 000000000..f72407569 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/TenantHeaderParamter.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.MicroService.AIService; + +public class TenantHeaderParamter : IOperationFilter +{ + private readonly AbpMultiTenancyOptions _multiTenancyOptions; + private readonly AbpAspNetCoreMultiTenancyOptions _aspNetCoreMultiTenancyOptions; + public TenantHeaderParamter( + IOptions multiTenancyOptions, + IOptions aspNetCoreMultiTenancyOptions) + { + _multiTenancyOptions = multiTenancyOptions.Value; + _aspNetCoreMultiTenancyOptions = aspNetCoreMultiTenancyOptions.Value; + } + + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (_multiTenancyOptions.IsEnabled) + { + operation.Parameters = operation.Parameters ?? new List(); + operation.Parameters.Add(new OpenApiParameter + { + Name = _aspNetCoreMultiTenancyOptions.TenantKey, + In = ParameterLocation.Header, + Description = "Tenant Id in http header", + Required = false + }); + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json new file mode 100644 index 000000000..c9a9d8327 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json @@ -0,0 +1,116 @@ +{ + "App": { + "ShowPii": true, + "CorsOrigins": [ "http://localhost:5666", "http://localhost:30000" ], + "RefreshClaimsUrl": "http://localhost:30015" + }, + "Auditing": { + "AllEntitiesSelector": true + }, + "DistributedCache": { + "HideErrors": true, + "KeyPrefix": "LINGYUN.Abp.Application", + "GlobalCacheEntryOptions": { + "SlidingExpiration": "30:00:00", + "AbsoluteExpirationRelativeToNow": "60:00:00" + } + }, + "ConnectionStrings": { + "Default": "Host=127.0.0.1;Database=abp;Username=postgres;Password=123456" + }, + "CAP": { + "EventBus": { + "DefaultGroupName": "AIService", + "Version": "v1", + "FailedRetryInterval": 300, + "FailedRetryCount": 10, + "CollectorCleaningInterval": 3600000 + }, + "PostgreSql": { + "TableNamePrefix": "admin", + "ConnectionString": "Host=127.0.0.1;Database=abp;Username=postgres;Password=123456" + }, + "RabbitMQ": { + "HostName": "localhost", + "Port": 5672, + "UserName": "admin", + "Password": "123456", + "ExchangeName": "LINGYUN.Abp.Application", + "VirtualHost": "/" + } + }, + "DistributedLock": { + "IsEnabled": true, + "Redis": { + "Configuration": "localhost,defaultDatabase=13" + } + }, + "Redis": { + "Configuration": "localhost,defaultDatabase=10", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "Authority": "http://localhost:44385/", + "Audience": "admin-service", + "ValidAudiences": [ "lingyun-abp-application" ], + "MapInboundClaims": false, + "RequireHttpsMetadata": false, + "SwaggerClientId": "vue-oauth-client" + }, + "RemoteServices": { + "Platform": { + "BaseUrl": "http://localhost:30025", + "UseCurrentAccessToken": false + } + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://elasticsearch" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://elasticsearch", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } + } + ] + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.json new file mode 100644 index 000000000..b8e4da13c --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.json @@ -0,0 +1,91 @@ +{ + "Clock": { + "Kind": "Local" + }, + "Forwarded": { + "ForwardedHeaders": "XForwardedFor,XForwardedProto" + }, + "StringEncryption": { + "DefaultPassPhrase": "s46c5q55nxpeS8Ra", + "InitVectorBytes": "s83ng0abvd02js84", + "DefaultSalt": "sf&5)s3#" + }, + "Json": { + "InputDateTimeFormats": [ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-ddTHH:mm:ss" + ] + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Debug-.log", + "restrictedToMinimumLevel": "Debug", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Info-.log", + "restrictedToMinimumLevel": "Information", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Warn-.log", + "restrictedToMinimumLevel": "Warning", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Error-.log", + "restrictedToMinimumLevel": "Error", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Fatal-.log", + "restrictedToMinimumLevel": "Fatal", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + } + ] + } + } + ] + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.ApiGateway/yarp.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.ApiGateway/yarp.json index 61baae0e6..34e4940a0 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.ApiGateway/yarp.json +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.ApiGateway/yarp.json @@ -1,6 +1,21 @@ { "ReverseProxy": { "Routes": { + "ai-management-route": { + "ClusterId": "ai-management-cluster", + "Match": { + "Path": "/api/ai-management/{**everything}" + }, + "Transforms": [ + { + "HeaderPrefix": "X-Forwarded-", + "X-Forwarded": "Append" + }, + { + "ResponseHeadersAllowed": "_AbpWrapResult;_AbpDontWrapResult;_AbpErrorFormat" + } + ] + }, "abp-route": { "ClusterId": "admin-service-cluster", "Match": { @@ -451,6 +466,16 @@ } }, "Clusters": { + "ai-management-cluster": { + "Destinations": { + "destination1": { + "Address": "http://localhost:30070", + "Metadata": { + "SwaggerEndpoint": "http://localhost:30070" + } + } + } + }, "auth-server-cluster": { "Destinations": { "destination1": { diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/AppHost.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/AppHost.cs index 48a47b899..def8a420f 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/AppHost.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/AppHost.cs @@ -215,6 +215,18 @@ builder.AddProject("WorkflowS .WaitFor(rabbitmq) .WaitFor(taskService); +// AIService +AddDotNetProject< + Projects.LINGYUN_Abp_MicroService_AIService_DbMigrator, + Projects.LINGYUN_Abp_MicroService_AIService>( + builder: builder, + servicePrefix: "AI", + serviceSuffix: "Service", + migratorSuffix: "Migrator", + port: 30070, + portName: "ai", + waitProject: localizationService); + // ApiGateway var apigateway = builder.AddProject("ApiGateway") .WithHttpEndpoint(port: 30000, name: "gateway") diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/LINGYUN.Abp.MicroService.AppHost.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/LINGYUN.Abp.MicroService.AppHost.csproj index cccaa6637..807b9b7a0 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/LINGYUN.Abp.MicroService.AppHost.csproj +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AppHost/LINGYUN.Abp.MicroService.AppHost.csproj @@ -17,6 +17,8 @@ + +