sozsoft-platform/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSchemaDictionary.cs

189 lines
6 KiB
C#
Raw Normal View History

2026-09-10 14:40:43 +00:00
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Sozsoft.Platform.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Sema bilgisinin tek kaynagi: <c>Sas_H_DbTableColumn</c> tablosundaki JSON veri sozlugu.
/// Sozluk kullaniciya gorunen basliklari ve lookup iliskilerini birlikte tasidigi icin canli
/// katalog yerine bu kullanilir — modelin gordugu adlar ekranda gorunen adlarla ayni olur.
/// </summary>
public interface IMcpSchemaDictionary
{
/// <summary>Sozlugun bulunup bulunmadigi ve ne zaman tazelendigi; yoksa null.</summary>
Task<McpDictionarySnapshot?> GetSnapshotAsync();
/// <summary>Sozlukteki listeler; sozluk yoksa bos liste.</summary>
Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync();
/// <summary>
/// Istegin kapsamina dusen sozluk kiraci adi. Sozluk ureten prosedurun
/// <c>@TenantName</c> parametresiyle ayni degeri tasimasi gerekir.
/// </summary>
string ResolveTenantName();
/// <summary>
/// Aksan ve buyuk/kucuk harf duyarsiz karsilastirma anahtari; "Musteri" aramasi
/// "Müşteri" basligini da bulur.
/// </summary>
string Normalize(string? value);
}
public class McpSchemaDictionary(
IRepository<DbTableColumn, Guid> dictionaryRepository,
ICurrentTenant currentTenant)
: DomainService, IMcpSchemaDictionary
{
private static readonly JsonSerializerOptions DictionaryJsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
private static readonly CultureInfo TurkishCulture = CultureInfo.GetCultureInfo("tr-TR");
public string ResolveTenantName()
=> currentTenant.Name ?? McpConsts.DefaultDictionaryTenantName;
public async Task<McpDictionarySnapshot?> GetSnapshotAsync()
{
// Tazelik bilgisi icin JsonData (buyuk sozluklerde megabaytlar) okunmaz.
using (currentTenant.Change(null))
{
var query = await ActiveRowsAsync(ResolveTenantName());
return query
.Select(row => new McpDictionarySnapshot(
row.CacheKey,
row.RefreshedAt,
row.FormCount,
row.FieldCount,
row.TenantName,
row.DefinitionCultureName,
row.UiCultureName))
.FirstOrDefault();
}
}
public async Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync()
{
var json = await GetJsonDataAsync();
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
var document = JsonSerializer.Deserialize<DictionaryDocument>(json, DictionaryJsonOptions);
return document?.ListForms ?? [];
}
public string Normalize(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var decomposed = value.ToLower(TurkishCulture).Normalize(NormalizationForm.FormD);
var builder = new StringBuilder(decomposed.Length);
foreach (var character in decomposed)
{
if (CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.NonSpacingMark)
{
continue;
}
builder.Append(character switch
{
'ı' => 'i',
'ğ' => 'g',
'ş' => 's',
_ => character
});
}
return builder.ToString();
}
private async Task<string?> GetJsonDataAsync()
{
// Sozluk host kapsamli bir tabloda tutulur; kiraci ayrimi TenantName kolonundadir.
using (currentTenant.Change(null))
{
var query = await ActiveRowsAsync(ResolveTenantName());
return query.Select(row => row.JsonData).FirstOrDefault();
}
}
private async Task<IQueryable<DbTableColumn>> ActiveRowsAsync(string tenantName)
{
var query = await dictionaryRepository.GetQueryableAsync();
return query
.Where(row => row.DictionaryType == McpConsts.DictionaryType
&& row.IsActive
&& row.TenantName == tenantName)
.OrderByDescending(row => row.RefreshedAt);
}
/// <summary>Sozluk JSON'unun kok nesnesi.</summary>
private sealed class DictionaryDocument
{
public List<McpDictionaryListForm>? ListForms { get; set; }
}
}
/// <summary>Sozluk satirinin tazelik bilgisi.</summary>
public sealed record McpDictionarySnapshot(
string? CacheKey,
DateTime RefreshedAt,
int FormCount,
int FieldCount,
string? TenantName,
string? DefinitionCultureName,
string? UiCultureName);
/// <summary>
/// Sozlukteki bir liste. Alan adlari <c>dbo.Sas_H_ListFormFieldsDictionaryJson</c> prosedurunun
/// urettigi JSON ile birebir ayni olmak zorundadir.
/// </summary>
public class McpDictionaryListForm
{
public string? ListFormCode { get; set; }
public string? DisplayName { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? TableName { get; set; }
public string? SqlSourceName { get; set; }
public string? SourceKind { get; set; }
public string? KeyFieldName { get; set; }
public string? SearchText { get; set; }
public List<McpDictionaryField>? Fields { get; set; }
}
/// <summary>Sozlukteki bir sutun.</summary>
public class McpDictionaryField
{
public string? Field { get; set; }
public string? DisplayName { get; set; }
public string? SqlDataType { get; set; }
public bool IsVisible { get; set; }
public bool IsBooleanColumn { get; set; }
public string? LookupTableName { get; set; }
public string? LookupValueColumn { get; set; }
public string? LookupDisplayColumn { get; set; }
public bool IsSensitive { get; set; }
public bool IsDefaultSelectColumn { get; set; }
}