Merge pull request #1727 from ONLYOFFICE/feature/accounts-optimization

Feature/accounts optimization
This commit is contained in:
Pavel Bannov 2023-09-11 04:36:07 -07:00 committed by GitHub
commit 00a9b332df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 8416 additions and 1118 deletions

View File

@ -181,7 +181,21 @@ public class CachedUserService : IUserService, ICachedService
CacheUserGroupRefItem = userServiceCache.CacheUserGroupRefItem;
}
public IQueryable<UserInfo> GetUsers(
public Task<int> GetUsersCountAsync(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text)
{
return Service.GetUsersCountAsync(tenant, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
}
public IAsyncEnumerable<UserInfo> GetUsers(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
@ -194,11 +208,9 @@ public class CachedUserService : IUserService, ICachedService
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
long offset)
{
return Service.GetUsers(tenant, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
return Service.GetUsers(tenant, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset);
}
public async Task<UserInfo> GetUserAsync(int tenant, Guid id)

View File

@ -180,8 +180,21 @@ public class UserManager
return await users.ToArrayAsync();
}
public Task<int> GetUsersCountAsync(
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text)
{
return _userService.GetUsersCountAsync(Tenant.Id, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
}
public IQueryable<UserInfo> GetUsers(
public IAsyncEnumerable<UserInfo> GetUsers(
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
@ -193,11 +206,9 @@ public class UserManager
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
long offset)
{
return _userService.GetUsers(Tenant.Id, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
return _userService.GetUsers(Tenant.Id, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset);
}
public async Task<string[]> GetUserNamesAsync(EmployeeStatus status)

View File

@ -30,7 +30,19 @@ namespace ASC.Core;
public interface IUserService
{
Task<IEnumerable<UserInfo>> GetUsersAsync(int tenant);
IQueryable<UserInfo> GetUsers(int tenant, bool isDocSpaceAdmin,
Task<int> GetUsersCountAsync(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text);
IAsyncEnumerable<UserInfo> GetUsers(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
@ -41,9 +53,7 @@ public interface IUserService
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count);
long offset);
Task<byte[]> GetUserPhotoAsync(int tenant, Guid id);
Task<DateTime> GetUserPasswordStampAsync(int tenant, Guid id);
Task<Group> GetGroupAsync(int tenant, Guid id);
@ -69,4 +79,4 @@ public interface IUserService
Task RemoveUserGroupRefAsync(int tenant, Guid userId, Guid groupId, UserGroupRefType refType);
Task SetUserPasswordHashAsync(int tenant, Guid id, string passwordHash);
Task SetUserPhotoAsync(int tenant, Guid id, byte[] photo);
}
}

View File

@ -33,6 +33,11 @@ public class EFUserService : IUserService
private readonly MachinePseudoKeys _machinePseudoKeys;
private readonly IMapper _mapper;
private static readonly Expression<Func<UserWithGroup, int>> _orderByUserType = u =>
u.Group == null ? 2 :
u.Group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 :
u.Group.UserGroupId == Users.Constants.GroupCollaborator.ID ? 3 : 4;
public EFUserService(
IDbContextFactory<UserDbContext> dbContextFactory,
MachinePseudoKeys machinePseudoKeys,
@ -232,7 +237,27 @@ public class EFUserService : IUserService
.ToListAsync();
}
public IQueryable<UserInfo> GetUsers(
public async Task<int> GetUsersCountAsync(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text)
{
await using var userDbContext = _dbContextFactory.CreateDbContext();
var q = GetUserQuery(userDbContext, tenant);
q = GetUserQueryForFilter(userDbContext, q, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
return await q.CountAsync();
}
public async IAsyncEnumerable<UserInfo> GetUsers(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
@ -245,49 +270,34 @@ public class EFUserService : IUserService
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
long offset)
{
var userDbContext = _dbContextFactory.CreateDbContext();
var totalQuery = GetUserQuery(userDbContext, tenant);
totalQuery = GetUserQueryForFilter(userDbContext, totalQuery, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
total = totalQuery.Count();
if (limit <= 0)
{
yield break;
}
await using var userDbContext = _dbContextFactory.CreateDbContext();
var q = GetUserQuery(userDbContext, tenant);
q = GetUserQueryForFilter(userDbContext, q, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
var orderedQuery = q.OrderBy(r => r.ActivationStatus == EmployeeActivationStatus.Pending);
var orderedQuery = q.OrderBy(r => r.ActivationStatus);
q = orderedQuery;
if (!string.IsNullOrEmpty(sortBy))
{
if (sortBy == "type")
{
var q1 = from user in q
join userGroup in userDbContext.UserGroups.Where(g => !g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID
|| g.UserGroupId == Users.Constants.GroupCollaborator.ID))
on user.Id equals userGroup.Userid into joinedGroup
from @group in joinedGroup.DefaultIfEmpty()
select new { user, @group };
var q1 = (from user in q
join userGroup in userDbContext.UserGroups.Where(g =>
!g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID ||
g.UserGroupId == Users.Constants.GroupCollaborator.ID)) on user.Id equals userGroup.Userid into joinedGroup
from @group in joinedGroup.DefaultIfEmpty()
select new UserWithGroup { User = user, Group = @group }).OrderBy(r => r.User.ActivationStatus);
if (sortOrderAsc)
{
q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending)
.ThenBy(r => r.group == null ? 2 :
r.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 :
r.group.UserGroupId == Users.Constants.GroupCollaborator.ID ? 3 : 4)
.Select(r => r.user);
}
else
{
q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending)
.ThenByDescending(u => u.group == null ? 2 :
u.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 :
u.group.UserGroupId == Users.Constants.GroupCollaborator.ID ? 3 : 4)
.Select(r => r.user);
}
q = (sortOrderAsc ? q1.ThenBy(_orderByUserType) : q1.ThenByDescending(_orderByUserType)).Select(r => r.User);
}
else
{
@ -295,19 +305,17 @@ public class EFUserService : IUserService
}
}
if (offset != 0)
if (offset > 0)
{
q = q.Skip((int)offset);
}
if (limit != 0)
q = q.Take((int)limit);
await foreach (var user in q.ToAsyncEnumerable())
{
q = q.Take((int)limit);
yield return _mapper.Map<User, UserInfo>(user);
}
count = q.Count();
return q.ProjectTo<UserInfo>(_mapper.ConfigurationProvider);
}
public IQueryable<UserInfo> GetUsers(int tenant, out int total)
@ -793,6 +801,12 @@ public class DbUserSecurity
public UserSecurity UserSecurity { get; set; }
}
public class UserWithGroup
{
public User User { get; set; }
public UserGroup Group { get; set; }
}
static file class Queries
{
public static readonly Func<UserDbContext, int, Guid, Task<DateTime?>> LastModifiedAsync =

View File

@ -109,6 +109,15 @@ public static class DbUserExtension
entity.HasIndex(e => new { e.TenantId, e.UserName })
.HasDatabaseName("username");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.FirstName })
.HasDatabaseName("tenant_activation_status_firstname");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.LastName })
.HasDatabaseName("tenant_activation_status_lastname");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.Email })
.HasDatabaseName("tenant_activation_status_email");
entity.Property(e => e.Id)
.HasColumnName("id")
.HasColumnType("varchar(38)")
@ -257,6 +266,15 @@ public static class DbUserExtension
entity.HasIndex(e => new { e.UserName, e.TenantId })
.HasDatabaseName("username");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.FirstName })
.HasDatabaseName("tenant_activation_status_firstname");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.LastName })
.HasDatabaseName("tenant_activation_status_lastname");
entity.HasIndex(e => new { e.TenantId, e.ActivationStatus, e.Email })
.HasDatabaseName("tenant_activation_status_email");
entity.Property(e => e.Id)
.HasColumnName("id")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ASC.Migrations.MySql.SaaS.Migrations
{
/// <inheritdoc />
public partial class MigrationContext_Upgrade3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "tenant_activation_status_email",
table: "core_user",
columns: new[] { "tenant", "activation_status", "email" });
migrationBuilder.CreateIndex(
name: "tenant_activation_status_firstname",
table: "core_user",
columns: new[] { "tenant", "activation_status", "firstname" });
migrationBuilder.CreateIndex(
name: "tenant_activation_status_lastname",
table: "core_user",
columns: new[] { "tenant", "activation_status", "lastname" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "tenant_activation_status_email",
table: "core_user");
migrationBuilder.DropIndex(
name: "tenant_activation_status_firstname",
table: "core_user");
migrationBuilder.DropIndex(
name: "tenant_activation_status_lastname",
table: "core_user");
}
}
}

View File

@ -5125,6 +5125,15 @@ namespace ASC.Migrations.MySql.SaaS.Migrations
b.HasIndex("TenantId", "UserName")
.HasDatabaseName("username");
b.HasIndex("TenantId", "ActivationStatus", "Email")
.HasDatabaseName("tenant_activation_status_email");
b.HasIndex("TenantId", "ActivationStatus", "FirstName")
.HasDatabaseName("tenant_activation_status_firstname");
b.HasIndex("TenantId", "ActivationStatus", "LastName")
.HasDatabaseName("tenant_activation_status_lastname");
b.ToTable("core_user", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");

File diff suppressed because it is too large Load Diff

View File

@ -171,11 +171,12 @@ public class UserPhotoManagerCache
return null;
}
if (size != Size.Empty && !val.TryGetValue(UserPhotoManager.ToCache(size), out fileName))
if (!val.TryGetValue(UserPhotoManager.ToCache(size), out fileName))
{
return null;
}
else if (String.IsNullOrEmpty(fileName))
if (String.IsNullOrEmpty(fileName))
{
fileName = val.Values.FirstOrDefault(x => !string.IsNullOrEmpty(x) && x.Contains("_orig_"));
}
@ -562,6 +563,9 @@ public class UserPhotoManager
await Task.WhenAll(t1, t2, t3, t4, t5);
}
_userPhotoManagerCache.AddToCache(userID, Size.Empty, fileName, await _tenantManager.GetCurrentTenantIdAsync());
return (photoUrl, fileName);
}