mirror of https://github.com/abpframework/abp.git
csharpabpc-sharpframeworkblazoraspnet-coredotnet-coreaspnetcorearchitecturesaasdomain-driven-designangularmulti-tenancy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
11 KiB
257 lines
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Newtonsoft.Json;
|
|
using Volo.Abp.Caching;
|
|
using Volo.Docs.Documents.FullSearch.Elastic;
|
|
using Volo.Docs.Projects;
|
|
|
|
namespace Volo.Docs.Documents
|
|
{
|
|
public class DocumentAppService : DocsAppServiceBase, IDocumentAppService
|
|
{
|
|
private readonly IProjectRepository _projectRepository;
|
|
private readonly IDocumentRepository _documentRepository;
|
|
private readonly IDocumentSourceFactory _documentStoreFactory;
|
|
protected IDistributedCache<LanguageConfig> LanguageCache { get; }
|
|
protected IDistributedCache<DocumentResourceDto> ResourceCache { get; }
|
|
protected IDistributedCache<DocumentUpdateInfo> DocumentUpdateCache { get; }
|
|
protected IHostEnvironment HostEnvironment { get; }
|
|
private readonly IDocumentFullSearch _documentFullSearch;
|
|
private readonly DocsElasticSearchOptions _docsElasticSearchOptions;
|
|
|
|
public DocumentAppService(
|
|
IProjectRepository projectRepository,
|
|
IDocumentRepository documentRepository,
|
|
IDocumentSourceFactory documentStoreFactory,
|
|
IDistributedCache<LanguageConfig> languageCache,
|
|
IDistributedCache<DocumentResourceDto> resourceCache,
|
|
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
|
|
IHostEnvironment hostEnvironment,
|
|
IDocumentFullSearch documentFullSearch,
|
|
IOptions<DocsElasticSearchOptions> docsElasticSearchOptions)
|
|
{
|
|
_projectRepository = projectRepository;
|
|
_documentRepository = documentRepository;
|
|
_documentStoreFactory = documentStoreFactory;
|
|
LanguageCache = languageCache;
|
|
ResourceCache = resourceCache;
|
|
DocumentUpdateCache = documentUpdateCache;
|
|
HostEnvironment = hostEnvironment;
|
|
_documentFullSearch = documentFullSearch;
|
|
_docsElasticSearchOptions = docsElasticSearchOptions.Value;
|
|
}
|
|
|
|
public virtual async Task<DocumentWithDetailsDto> GetAsync(GetDocumentInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
|
|
return await GetDocumentWithDetailsDtoAsync(
|
|
project,
|
|
input.Name,
|
|
input.LanguageCode,
|
|
input.Version
|
|
);
|
|
}
|
|
|
|
public virtual async Task<DocumentWithDetailsDto> GetDefaultAsync(GetDefaultDocumentInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
|
|
return await GetDocumentWithDetailsDtoAsync(
|
|
project,
|
|
project.DefaultDocumentName + "." + project.Format,
|
|
input.LanguageCode,
|
|
input.Version
|
|
);
|
|
}
|
|
|
|
public virtual async Task<NavigationNode> GetNavigationAsync(GetNavigationDocumentInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
|
|
var navigationDocument = await GetDocumentWithDetailsDtoAsync(
|
|
project,
|
|
project.NavigationDocumentName,
|
|
input.LanguageCode,
|
|
input.Version
|
|
);
|
|
|
|
var navigationNode = JsonConvert.DeserializeObject<NavigationNode>(navigationDocument.Content);
|
|
|
|
var leafs = navigationNode.Items.GetAllNodes(x => x.Items)
|
|
.Where(x => !x.Path.IsNullOrWhiteSpace())
|
|
.ToList();
|
|
|
|
foreach (var leaf in leafs)
|
|
{
|
|
var cacheKey = $"DocumentUpdateInfo{project.Id}#{leaf.Path}#{input.LanguageCode}#{input.Version}";
|
|
var documentUpdateInfo = await DocumentUpdateCache.GetAsync(cacheKey);
|
|
if (documentUpdateInfo != null)
|
|
{
|
|
leaf.CreationTime = documentUpdateInfo.CreationTime;
|
|
leaf.LastUpdatedTime = documentUpdateInfo.LastUpdatedTime;
|
|
leaf.LastSignificantUpdateTime = documentUpdateInfo.LastSignificantUpdateTime;
|
|
}
|
|
}
|
|
|
|
return navigationNode;
|
|
}
|
|
|
|
public async Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
var cacheKey = $"Resource@{project.ShortName}#{input.LanguageCode}#{input.Name}#{input.Version}";
|
|
input.Version = string.IsNullOrWhiteSpace(input.Version) ? project.LatestVersionBranchName : input.Version;
|
|
|
|
async Task<DocumentResourceDto> GetResourceAsync()
|
|
{
|
|
var source = _documentStoreFactory.Create(project.DocumentStoreType);
|
|
var documentResource = await source.GetResource(project, input.Name, input.LanguageCode, input.Version);
|
|
|
|
return ObjectMapper.Map<DocumentResource, DocumentResourceDto>(documentResource);
|
|
}
|
|
|
|
if (HostEnvironment.IsDevelopment())
|
|
{
|
|
return await GetResourceAsync();
|
|
}
|
|
|
|
return await ResourceCache.GetOrAddAsync(
|
|
cacheKey,
|
|
GetResourceAsync,
|
|
() => new DistributedCacheEntryOptions
|
|
{
|
|
//TODO: Configurable?
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6),
|
|
SlidingExpiration = TimeSpan.FromMinutes(30)
|
|
}
|
|
);
|
|
}
|
|
|
|
public async Task<List<DocumentSearchOutput>> SearchAsync(DocumentSearchInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
|
|
var esDocs = await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version);
|
|
|
|
return esDocs.Select(esDoc => new DocumentSearchOutput//TODO: auto map
|
|
{
|
|
Name = esDoc.Name,
|
|
FileName = esDoc.FileName,
|
|
Version = esDoc.Version,
|
|
LanguageCode = esDoc.LanguageCode,
|
|
Highlight = esDoc.Highlight
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<bool> FullSearchEnabledAsync()
|
|
{
|
|
return await Task.FromResult(_docsElasticSearchOptions.Enable);
|
|
}
|
|
|
|
public async Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input)
|
|
{
|
|
var project = await _projectRepository.GetAsync(input.ProjectId);
|
|
|
|
try
|
|
{
|
|
if (string.IsNullOrWhiteSpace(project.ParametersDocumentName))
|
|
{
|
|
return await Task.FromResult<DocumentParametersDto>(null);
|
|
}
|
|
|
|
var document = await GetDocumentWithDetailsDtoAsync(
|
|
project,
|
|
project.ParametersDocumentName,
|
|
input.LanguageCode,
|
|
input.Version
|
|
);
|
|
|
|
return JsonConvert.DeserializeObject<DocumentParametersDto>(document.Content);
|
|
}
|
|
catch (DocumentNotFoundException)
|
|
{
|
|
Logger.LogWarning($"Parameter file ({project.ParametersDocumentName}) not found.");
|
|
return new DocumentParametersDto();
|
|
}
|
|
}
|
|
|
|
protected virtual async Task<DocumentWithDetailsDto> GetDocumentWithDetailsDtoAsync(
|
|
Project project,
|
|
string documentName,
|
|
string languageCode,
|
|
string version)
|
|
{
|
|
version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version;
|
|
|
|
async Task<DocumentWithDetailsDto> GetDocumentAsync(Document oldDocument = null)
|
|
{
|
|
Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the source...");
|
|
|
|
var source = _documentStoreFactory.Create(project.DocumentStoreType);
|
|
var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version, oldDocument?.LastSignificantUpdateTime);
|
|
|
|
await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version);
|
|
await _documentRepository.InsertAsync(sourceDocument, true);
|
|
|
|
Logger.LogInformation($"Document retrieved: {documentName}");
|
|
|
|
var cacheKey = $"DocumentUpdateInfo{sourceDocument.ProjectId}#{sourceDocument.Name}#{sourceDocument.LanguageCode}#{sourceDocument.Version}";
|
|
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
|
|
{
|
|
Name = sourceDocument.Name,
|
|
CreationTime = sourceDocument.CreationTime,
|
|
LastUpdatedTime = sourceDocument.LastUpdatedTime,
|
|
LastSignificantUpdateTime = sourceDocument.LastSignificantUpdateTime
|
|
});
|
|
|
|
return CreateDocumentWithDetailsDto(project, sourceDocument);
|
|
}
|
|
|
|
if (HostEnvironment.IsDevelopment())
|
|
{
|
|
return await GetDocumentAsync();
|
|
}
|
|
|
|
var document = await _documentRepository.FindAsync(project.Id, documentName, languageCode, version);
|
|
if (document == null)
|
|
{
|
|
return await GetDocumentAsync();
|
|
}
|
|
|
|
//Only the latest version (dev) of the document needs to update the cache.
|
|
if (!project.LatestVersionBranchName.IsNullOrWhiteSpace() &&
|
|
document.Version == project.LatestVersionBranchName &&
|
|
//TODO: Configurable cache time?
|
|
document.LastCachedTime + TimeSpan.FromHours(2) < DateTime.Now)
|
|
{
|
|
return await GetDocumentAsync(document);
|
|
}
|
|
|
|
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";
|
|
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
|
|
{
|
|
Name = document.Name,
|
|
CreationTime = document.CreationTime,
|
|
LastUpdatedTime = document.LastUpdatedTime,
|
|
LastSignificantUpdateTime = document.LastSignificantUpdateTime
|
|
});
|
|
|
|
return CreateDocumentWithDetailsDto(project, document);
|
|
}
|
|
|
|
protected virtual DocumentWithDetailsDto CreateDocumentWithDetailsDto(Project project, Document document)
|
|
{
|
|
var documentDto = ObjectMapper.Map<Document, DocumentWithDetailsDto>(document);
|
|
documentDto.Project = ObjectMapper.Map<Project, ProjectDto>(project);
|
|
documentDto.Contributors = ObjectMapper.Map<List<DocumentContributor>, List<DocumentContributorDto>>(document.Contributors);
|
|
return documentDto;
|
|
}
|
|
}
|
|
}
|