189 lines
6 KiB
C#
189 lines
6 KiB
C#
|
|
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; }
|
|||
|
|
}
|