Implement Caching
This commit is contained in:
parent
ac2c8be4a2
commit
88b0615a30
@ -11,9 +11,13 @@ services:
|
|||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
networks:
|
networks:
|
||||||
- qtc-backend
|
- qtc-backend
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
restart: unless-stopped
|
||||||
# Traefik Config Example
|
# Traefik Config Example
|
||||||
# labels:
|
# labels:
|
||||||
# - "traefik.enable=true"
|
# - "traefik.enable=true"
|
||||||
@ -37,6 +41,11 @@ services:
|
|||||||
timeout: 30s
|
timeout: 30s
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: qtc-cache
|
||||||
|
image: redis
|
||||||
|
networks:
|
||||||
|
- qtc-backend
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
qtc-data:
|
qtc-data:
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.AspNetCore.Http;
|
using qtc_api.Extensions;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using qtc_api.Dtos.User;
|
|
||||||
using System.Net.Mime;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace qtc_api.Controllers
|
namespace qtc_api.Controllers
|
||||||
{
|
{
|
||||||
@ -14,17 +9,28 @@ namespace qtc_api.Controllers
|
|||||||
public class UsersController : ControllerBase
|
public class UsersController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IDistributedCache cache;
|
||||||
public UsersController(IUserService userService)
|
public UsersController(IUserService userService, IDistributedCache distributedCache)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
cache = distributedCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("all")]
|
[HttpGet("all")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<ServiceResponse<List<UserInformationDto>>>> GetAllUsers()
|
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);
|
return Ok(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +38,17 @@ namespace qtc_api.Controllers
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<ServiceResponse<UserInformationDto>>> GetUserInformation(string id)
|
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);
|
return Ok(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +99,11 @@ namespace qtc_api.Controllers
|
|||||||
if(id != null && id == user.Id)
|
if(id != null && id == user.Id)
|
||||||
{
|
{
|
||||||
var updatedUser = await _userService.UpdateUserInfo(user);
|
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);
|
return Ok(updatedUser);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
@ -114,6 +135,19 @@ namespace qtc_api.Controllers
|
|||||||
|
|
||||||
var response = await _userService.UpdateUserPic(userId, file);
|
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);
|
return Ok(response);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
@ -129,15 +163,36 @@ namespace qtc_api.Controllers
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult> GetUserProfilePicture(string userId)
|
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)
|
if (result != null && result.Success != false)
|
||||||
{
|
{
|
||||||
return result.Data!;
|
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.");
|
return BadRequest("User has no profile picture.");
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return BadRequest("Failed To Get Profile Picture.");
|
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;
|
global using qtc_api.Hubs;
|
||||||
using qtc_api.Services.RoomService;
|
using qtc_api.Services.RoomService;
|
||||||
using qtc_api.Services.ContactService;
|
using qtc_api.Services.ContactService;
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -23,6 +22,18 @@ builder.Services.AddDbContext<DataContext>();
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSignalR();
|
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 =>
|
builder.Services.AddAuthentication().AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
@ -20,9 +20,11 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" 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.IdentityModel.Tokens" Version="8.10.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
|
||||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.3" />
|
<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" Version="8.1.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user