From 03549a181521009baf6d353a98f4cb8804602cdc Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Tue, 20 Aug 2019 23:41:36 +0800 Subject: Use etag for cache. --- Timeline/Services/UserAvatarService.cs | 97 ++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 15 deletions(-) (limited to 'Timeline/Services/UserAvatarService.cs') diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index a83b8a52..7b1f405c 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats; @@ -64,6 +65,12 @@ namespace Timeline.Services /// public interface IDefaultUserAvatarProvider { + /// + /// Get the etag of default avatar. + /// + /// + Task GetDefaultAvatarETag(); + /// /// Get the default avatar. /// @@ -82,6 +89,15 @@ namespace Timeline.Services public interface IUserAvatarService { + /// + /// Get the etag of a user's avatar. + /// + /// The username of the user to get avatar etag of. + /// The etag. + /// Thrown if is null or empty. + /// Thrown if the user does not exist. + Task GetAvatarETag(string username); + /// /// Get avatar of a user. If the user has no avatar, a default one is returned. /// @@ -107,22 +123,46 @@ namespace Timeline.Services { private readonly IHostingEnvironment _environment; - public DefaultUserAvatarProvider(IHostingEnvironment environment) + private readonly IETagGenerator _eTagGenerator; + + private byte[] _cacheData; + private DateTime _cacheLastModified; + private string _cacheETag; + + public DefaultUserAvatarProvider(IHostingEnvironment environment, IETagGenerator eTagGenerator) { _environment = environment; + _eTagGenerator = eTagGenerator; } - public async Task GetDefaultAvatar() + private async Task CheckAndInit() { + if (_cacheData != null) + return; + var path = Path.Combine(_environment.ContentRootPath, "default-avatar.png"); + _cacheData = await File.ReadAllBytesAsync(path); + _cacheLastModified = File.GetLastWriteTime(path); + _cacheETag = _eTagGenerator.Generate(_cacheData); + } + + public async Task GetDefaultAvatarETag() + { + await CheckAndInit(); + return _cacheETag; + } + + public async Task GetDefaultAvatar() + { + await CheckAndInit(); return new AvatarInfo { Avatar = new Avatar { Type = "image/png", - Data = await File.ReadAllBytesAsync(path) + Data = _cacheData }, - LastModified = File.GetLastWriteTime(path) + LastModified = _cacheLastModified }; } } @@ -161,12 +201,36 @@ namespace Timeline.Services private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider; private readonly IUserAvatarValidator _avatarValidator; - public UserAvatarService(ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider, IUserAvatarValidator avatarValidator) + private readonly IETagGenerator _eTagGenerator; + + public UserAvatarService( + ILogger logger, + DatabaseContext database, + IDefaultUserAvatarProvider defaultUserAvatarProvider, + IUserAvatarValidator avatarValidator, + IETagGenerator eTagGenerator) { _logger = logger; _database = database; _defaultUserAvatarProvider = defaultUserAvatarProvider; _avatarValidator = avatarValidator; + _eTagGenerator = eTagGenerator; + } + + public async Task GetAvatarETag(string username) + { + if (string.IsNullOrEmpty(username)) + throw new ArgumentException("Username is null or empty.", nameof(username)); + + var userId = await _database.Users.Where(u => u.Name == username).Select(u => u.Id).SingleOrDefaultAsync(); + if (userId == 0) + throw new UserNotExistException(username); + + var eTag = (await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.ETag }).SingleAsync()).ETag; + if (eTag == null) + return await _defaultUserAvatarProvider.GetDefaultAvatarETag(); + else + return eTag; } public async Task GetAvatar(string username) @@ -174,16 +238,17 @@ namespace Timeline.Services if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username is null or empty.", nameof(username)); - var user = await _database.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); - if (user == null) + var userId = await _database.Users.Where(u => u.Name == username).Select(u => u.Id).SingleOrDefaultAsync(); + if (userId == 0) throw new UserNotExistException(username); - await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); - var avatar = user.Avatar; + var avatar = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.Type, a.Data, a.LastModified }).SingleAsync(); - if ((avatar.Type == null) == (avatar.Data == null)) + if ((avatar.Type == null) != (avatar.Data == null)) + { _logger.LogCritical("Database corupted! One of type and data of a avatar is null but the other is not."); - // TODO: Throw an exception to indicate this. + throw new DatabaseCorruptedException(); + } if (avatar.Data == null) { @@ -218,12 +283,11 @@ namespace Timeline.Services throw new ArgumentException("Data of avatar is null.", nameof(avatar)); } - var user = await _database.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); - if (user == null) + var userId = await _database.Users.Where(u => u.Name == username).Select(u => u.Id).SingleOrDefaultAsync(); + if (userId == 0) throw new UserNotExistException(username); - await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); - var avatarEntity = user.Avatar; + var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == userId).SingleAsync(); if (avatar == null) { @@ -233,6 +297,7 @@ namespace Timeline.Services { avatarEntity.Data = null; avatarEntity.Type = null; + avatarEntity.ETag = null; avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); _logger.LogInformation("Updated an entry in user_avatars."); @@ -243,6 +308,7 @@ namespace Timeline.Services await _avatarValidator.Validate(avatar); avatarEntity.Type = avatar.Type; avatarEntity.Data = avatar.Data; + avatarEntity.ETag = _eTagGenerator.Generate(avatar.Data); avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); _logger.LogInformation("Updated an entry in user_avatars."); @@ -254,6 +320,7 @@ namespace Timeline.Services { public static void AddUserAvatarService(this IServiceCollection services) { + services.TryAddTransient(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); -- cgit v1.2.3