Implement Caching
This commit is contained in:
parent
ac2c8be4a2
commit
88b0615a30
@ -11,9 +11,13 @@ services:
|
||||
- "8080:8080"
|
||||
networks:
|
||||
- qtc-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
redis:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
# Traefik Config Example
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
@ -37,6 +41,11 @@ services:
|
||||
timeout: 30s
|
||||
restart: always
|
||||
|
||||
redis:
|
||||
container_name: qtc-cache
|
||||
image: redis
|
||||
networks:
|
||||
- qtc-backend
|
||||
|
||||
volumes:
|
||||
qtc-data:
|
||||
|
@ -1,11 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using qtc_api.Dtos.User;
|
||||
using System.Net.Mime;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using qtc_api.Extensions;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace qtc_api.Controllers
|
||||
{
|
||||
@ -14,17 +9,28 @@ namespace qtc_api.Controllers
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UsersController(IUserService userService)
|
||||
private readonly IDistributedCache cache;
|
||||
public UsersController(IUserService userService, IDistributedCache distributedCache)
|
||||
{
|
||||
_userService = userService;
|
||||
cache = distributedCache;
|
||||
}
|
||||
|
||||
[HttpGet("all")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ServiceResponse<List<UserInformationDto>>>> GetAllUsers()
|
||||
{
|
||||
var users = await _userService.GetAllUsers();
|
||||
var users = new ServiceResponse<List<UserInformationDto>>();
|
||||
|
||||
string recordId = $"Users_{DateTime.Now.ToString("yyyyMMdd_hhmm")}";
|
||||
users = await cache.GetRecordAsync<ServiceResponse<List<UserInformationDto>>>(recordId);
|
||||
|
||||
if(users == null)
|
||||
{
|
||||
users = await _userService.GetAllUsers();
|
||||
await cache.SetRecordAsync(recordId, users);
|
||||
}
|
||||
|
||||
return Ok(users);
|
||||
}
|
||||
|
||||
@ -32,7 +38,17 @@ namespace qtc_api.Controllers
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ServiceResponse<UserInformationDto>>> GetUserInformation(string id)
|
||||
{
|
||||
var user = await _userService.GetUserInformationById(id);
|
||||
var user = new ServiceResponse<UserInformationDto>();
|
||||
|
||||
string recordId = $"User_{DateTime.Now.ToString("yyyyMMdd_hhmm")}";
|
||||
user = await cache.GetRecordAsync<ServiceResponse<UserInformationDto>>(recordId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
user = await _userService.GetUserInformationById(id);
|
||||
await cache.SetRecordAsync(recordId, user);
|
||||
}
|
||||
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
@ -83,6 +99,11 @@ namespace qtc_api.Controllers
|
||||
if(id != null && id == user.Id)
|
||||
{
|
||||
var updatedUser = await _userService.UpdateUserInfo(user);
|
||||
|
||||
// always try to overwrite cache when updating user info
|
||||
string recordId = $"User_{DateTime.Now.ToString("yyyyMMdd_hhmm")}";
|
||||
await cache.SetRecordAsync(recordId, updatedUser);
|
||||
|
||||
return Ok(updatedUser);
|
||||
} else
|
||||
{
|
||||
@ -114,6 +135,19 @@ namespace qtc_api.Controllers
|
||||
|
||||
var response = await _userService.UpdateUserPic(userId, file);
|
||||
|
||||
// always try to overwrite cache when updating pfp
|
||||
string recordId = $"UserPfp_{DateTime.Now.ToString("yyyyMMdd_hhmm")}";
|
||||
using(var stream = file.OpenReadStream())
|
||||
{
|
||||
using(var ms = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(ms);
|
||||
await cache.SetImageAsync(recordId, ms.ToArray());
|
||||
ms.Dispose();
|
||||
}
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
} else
|
||||
{
|
||||
@ -129,15 +163,36 @@ namespace qtc_api.Controllers
|
||||
[Authorize]
|
||||
public async Task<ActionResult> GetUserProfilePicture(string userId)
|
||||
{
|
||||
var result = await _userService.GetUserPic(userId);
|
||||
string recordId = $"UserPfp_{DateTime.Now.ToString("yyyyMMdd_hhmm")}";
|
||||
byte[] pfpBytes = await cache.GetImageAsync(recordId);
|
||||
|
||||
var result = new ServiceResponse<FileContentResult>();
|
||||
if (pfpBytes == null)
|
||||
{
|
||||
result = await _userService.GetUserPic(userId);
|
||||
if (result != null && result.Success && result.Data != null)
|
||||
{
|
||||
pfpBytes = result.Data.FileContents;
|
||||
await cache.SetImageAsync(recordId, pfpBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// explicitly set from cache
|
||||
result.Success = true;
|
||||
result.Data = new FileContentResult(pfpBytes, "image/jpeg");
|
||||
result.Message = $"{userId}.pfp";
|
||||
}
|
||||
|
||||
if (result != null && result.Success != false)
|
||||
{
|
||||
return result.Data!;
|
||||
} else if (result!.Message == "User Does Not Have A Profile Picture." || result!.Message == "User Content Folder Does Not Exist Yet.")
|
||||
}
|
||||
else if (result!.Message == "User Does Not Have A Profile Picture." || result!.Message == "User Content Folder Does Not Exist Yet.")
|
||||
{
|
||||
return BadRequest("User has no profile picture.");
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("Failed To Get Profile Picture.");
|
||||
}
|
||||
|
53
qtc-net-server/Extensions/DistributedCacheExtensions.cs
Normal file
53
qtc-net-server/Extensions/DistributedCacheExtensions.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace qtc_api.Extensions
|
||||
{
|
||||
public static class DistributedCacheExtensions
|
||||
{
|
||||
public static async Task SetRecordAsync<T> (this IDistributedCache cache, string recordId, T data, TimeSpan? absoluteExpireTime = null, TimeSpan? unusuedExpireTime = null)
|
||||
{
|
||||
var options = new DistributedCacheEntryOptions();
|
||||
|
||||
options.AbsoluteExpirationRelativeToNow = absoluteExpireTime ?? TimeSpan.FromSeconds(60);
|
||||
options.SlidingExpiration = unusuedExpireTime;
|
||||
|
||||
var jsonData = JsonSerializer.Serialize(data);
|
||||
await cache.SetStringAsync(recordId, jsonData, options);
|
||||
}
|
||||
|
||||
public static async Task SetImageAsync(this IDistributedCache cache, string recordId, byte[] data, TimeSpan? absoluteExpireTime = null, TimeSpan? unusuedExpireTime = null)
|
||||
{
|
||||
var options = new DistributedCacheEntryOptions();
|
||||
|
||||
options.AbsoluteExpirationRelativeToNow = absoluteExpireTime ?? TimeSpan.FromSeconds(60);
|
||||
options.SlidingExpiration = unusuedExpireTime;
|
||||
|
||||
await cache.SetAsync(recordId, data, options);
|
||||
}
|
||||
|
||||
public static async Task<T?> GetRecordAsync<T>(this IDistributedCache cache, string recordId)
|
||||
{
|
||||
var jsonData = await cache.GetStringAsync(recordId);
|
||||
|
||||
if(jsonData == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T>(jsonData);
|
||||
}
|
||||
|
||||
public static async Task<byte[]?> GetImageAsync(this IDistributedCache cache, string recordId)
|
||||
{
|
||||
var bytes = await cache.GetAsync(recordId);
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ global using qtc_api.Services.TokenService;
|
||||
global using qtc_api.Hubs;
|
||||
using qtc_api.Services.RoomService;
|
||||
using qtc_api.Services.ContactService;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -23,6 +22,18 @@ builder.Services.AddDbContext<DataContext>();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
builder.Services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
var redisConnectionString = Environment.GetEnvironmentVariable("REDIS_CONNECTIONSTRING");
|
||||
if (redisConnectionString != null)
|
||||
options.Configuration = redisConnectionString;
|
||||
|
||||
if (!builder.Environment.IsProduction())
|
||||
options.InstanceName = "QtCNetServerDev_";
|
||||
else
|
||||
options.InstanceName = "QtCNetServer_";
|
||||
});
|
||||
|
||||
builder.Services.AddAuthentication().AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
|
@ -20,9 +20,11 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.10.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.3" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user