-
Notifications
You must be signed in to change notification settings - Fork 1
LOG: vaspahomov
- В обучении есть темы, которые нужно закреплять практикой. Каждому студенту требуется свое количество практики. Одна из таких тем - это оценка сложности алгоритмов.
- Студенты не всегда готовы выделить время за компьютером для дополнительной практики, но готовы тренироваться по несколько минут в день на телефоне, в моменты, когда есть свободное время без доступа к полноценному компьютеру. Сейчас они тратят это время на соц-сети, а могут тратить на обучение.
- В рамках командного проекта сделать бота для телеграмма, который позволит тренироваться в оценке сложности алгоритмов в свободное время прямо на своем телефоне. Для этого разработать механику, плавного увеличения сложности задач, с учетом успеваемости пользователя. Задания должны быть каждый раз уникальными и генерироваться на лету, чтобы исключить возможность запоминания уже увиденных вариантов.
- Обобщить бота так, чтобы его можно было использовать для других тем, отличных от оценки сложности алгоритмов.
- Обобщить код так, чтобы незначительными доработками можно было сделать его доступным через другие платформы для чат-ботов.
Решение построено из микросервисов, взаимодействующих между собой по протоколу HTTP.
Бизнес-логика викторины, построенная согласно принципам DDD.
Внешнее REST API для работы с задачами и уровнями. Построено на фреймворке ASP.NET Core.
Внешнее REST API для работы с викториной и пользователями. Построено на фреймворке ASP.NET Core.
Интерфейс для работы с MongoDB: заданиями, пользователями и другой статистикой.
Построенный на ASP.NET Core webhook-bot для Telegram.
Интерфейс для работы с MongoDB: состояниями пользователей и их данными для аутентификации в Task API.
Веб-сервис для редактирования и дизайна уровней викторины. Фронтенд написан на React JS, бекэнд - на ASP.NET Core.
-
Создать REST HTTP API для сервиса викторины
- API для взаимодействия пользовательского интерфейса викторины с сервисом
- API для редактиования заданий в сервисе
-
Создание редактора для сервиса
-
Спроектировать ролевую модель для редактора уровней
Архитектура HTTP API основана на фрэймфорке ASP.NET
С помощью средств ASP.NET добавлены HTTP методы, которые позволяют взаимодействовать с сервисом викторины. Каждый запрос выполняется параллельно, что позволяет одновременно взаимодействовать с сервисом большому колличеству пользователей.
С сервисе викторины Quibble 3 ASP.NET контроллера:
QuizServiceController
TaskServiceController
TemplateController
Во всех методах контроллеров:
- Получение http запроса.
- Валидация полученных данных
- Вызов метода API из слоя Application, которому делегирована логика приложения.
- Формирование DTO (Data transfer object)
DTO формируются с помощью AutoMapper, который импортирован Nuget пактеом в структуру проекта. Пример DTO, который сериализуется в JSON с помощью Newtonsoft из Nuget пакета.
using System;
using Newtonsoft.Json;
namespace QuizWebApp.Services.QuizService.DTO
{
public class TopicInfoDTO
{
public TopicInfoDTO(string name, Guid id)
{
Name = name;
Id = id;
}
[JsonProperty("id")] public Guid Id { get; }
[JsonProperty("name")] public string Name { get; }
}
}
На все контроллеры написана документация и подключен Swagger для выведения этой документации в удобном формате
Сервис предназначен для использования сервиса викторины пользовательскими интерфейсам. В данный момент реализованн Telegram бот, который пользуется данным api. Архитектура с использованием HTTP API позволяет создать несколько UI, которые будут пользоваться одним сервисом викторины. Все эти UI будут иметь общие базы уровней и пользователей. К викторине можно подключить другое web приложение, например web сайт.
Пример кода контроллера с несколькимим методами
namespace QuizWebApp.Services.QuizService
{
[Route("api")]
[ApiController]
public class QuizServiceController : ControllerBase
{
private readonly IQuizService applicationApi;
public QuizServiceController(IQuizService applicationApi)
{
this.applicationApi = applicationApi;
}
/// <summary>
/// Возвращает список всех тем.
/// </summary>
/// <remarks>
/// Sample request:
/// GET api/topics
/// </remarks>
/// <response code="200"> Возвращает список тем</response>
[HttpGet("topics")]
public ActionResult<IEnumerable<TopicInfoDTO>> GetTopics()
{
var topics = applicationApi.GetTopicsInfo().Value;
return Ok(topics.Select(Mapper.Map<TopicInfoDTO>));
}
/// <summary>
/// Отправить ответ на сервер.
/// </summary>
/// <remarks>
/// Sample request:
/// POST api/0/sendAnswer
/// "O(1)"
/// </remarks>
/// <response code="200"> Возвращает bool верный ли ответ</response>
/// <response code="403"> Данная операция не доступна пользователю</response>
[HttpPost("{userId}/sendAnswer")]
public ActionResult<bool> SendAnswer([FromBody] string answer, Guid userId)
{
var (isSuccess, _, result, error) = applicationApi.CheckAnswer(userId, answer);
if (isSuccess)
return Ok(result);
if (error is AccessDeniedException)
return Forbid();
return InternalServerError(error.Message);
}
private ObjectResult InternalServerError(object value) =>
StatusCode(StatusCodes.Status500InternalServerError, value);
}
}
Возвращает список всех тем.
GET /api/topics
Возвращает всех список уровней в теме.
GET /api/{topicId}/levels
Возвращает список уровней в теме доступных пользователю.
GET /api/{userId}/{topicId}/availableLevels
Возвращает прогресс пользователя.
GET /api/{topicId}/{levelId}/progress
Получить задачу.
GET /api/{topicId}/{levelId}/task
Возвращает следующую задачу из текущих темы и уровня пользователя.
GET /api/{userId}/nextTask
Возвращает подсказку на текущую задачу.
GET /api/{userId}/hint
Отправить ответ на сервер.
POST /api/{userId}/sendAnswer
Контроллер для редактирования коллекции заданий в сервисе викторины. С его помощью можно добавлять и удалять
Topic
,Level
,TemplateGenerator
в сервисе викторины. В данный момент к данному контроллеру подключен UI редактора уровней в виде web сайта. Также в контроллере есть возможость загружать целиком тему в форматахJSON
иHJSON
.
Получить генераторы из уровня.
GET /service/{topicId}/{levelId}/templateGenerators
Добавляет в сервис новый пустую тему
POST /service/topic
Удаляет тему из сервиса
DELETE /service/topic/{topicId}
Добавляет в сервис пустой уровень
POST /service/{topicId}/level
Удаляет уровень из сервиса.
DELETE /service/{topicId}/level/{levelId}
Добавляет в сервис новый генератор
POST /service/{topicId}/{levelId}/templateGenerator
Удаляет генератор из сервиса.
DELETE /service/{topicId}/{levelId}/generator/{generatorId}
Загружает тему в формате HJSON в БД, автоматически проставляя ей айди.
POST /service/hjsonTopic
Получить весь топик в формате HJSON без айди
GET /service/{topicId}/hjsonTopic
Загружает тему в формате JSON в БД, автоматически проставляя ей айди.
POST /service/jsonTopic
Получить тему без айди в нем
GET /service/{topicId}/jsonTopic
В данном контроллере реализованы методы, помогающие при редактировании уровней.
/renderTask
позволяет предпросмотретьTemplateGenerator
перед добавлением. Также есть метод, который позволяет посмотреть уже встроенные в сервис шаблоны, которые можно использовать в своих.
Рендерит и возвращает задачу по шаблону из запроса
POST /templates/renderTask
Показывает набор существующих по умолчанию подстановок и их значений
GET /templates/substitutions/examples
LevelManager основан на ASP.NET в качестве backend и ReactJS в качестве frontend.
Главной обязаностью backend части LevelManager'а является запуск ReactJS приложения.
Конфигурация запуска прописывается в файле Startup.cs
подобным образом.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment()) spa.UseReactDevelopmentServer("start");
});
}
Также на backend есть ProxyController
который является прослойкой между API сервиса и приложением редактора. С помощью этого контроллера мы обходим CORS политику.
ProxyController
принимает запросы с frontend части нашего приложения и отправляет запросы на HTTP API викторины с помощью интрефейса IQuizServiceExtended.cs
, которому делегированы обязаности запросов.
Интерфейс IQuizServiceExtended.cs
using System;
using System.Collections.Generic;
using QuizRequestExtendedService.DTO;
namespace QuizRequestExtendedService
{
public interface IQuizServiceExtended
{
IEnumerable<TopicDTO> GetTopics();
IEnumerable<LevelDTO> GetLevels(Guid topicId);
IEnumerable<AdminTemplateGeneratorDTO> GetTemplateGenerators(Guid topicId, Guid levelId);
Guid AddEmptyTopic(EmptyTopicDTO topic);
void DeleteTopic(Guid topicId);
Guid AddEmptyLevel(Guid topicId, EmptyLevelDTO level);
void DeleteLevel(Guid topicId, Guid levelId);
Guid AddEmptyGenerator(Guid topicId, Guid levelId, TemplateGeneratorDTO topic);
void DeleteTemplateGenerator(Guid topicId, Guid levelId, Guid generatorId);
TemplateGeneratorDTO RenderTask(TemplateGeneratorForRenderDTO templateGenerator);
}
}
Пример реализации запросов в классе Requester.cs
, который наследует IQuizServiceExtended.cs
.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using QuizRequestExtendedService.DTO;
using RestSharp;
namespace QuizRequestExtendedService
{
public class Requester : IQuizServiceExtended
{
private readonly string serverUri;
private const int MaxRetries = 5;
public Requester(string serverUri)
{
this.serverUri = serverUri;
}
public IEnumerable<TopicDTO> GetTopics()
{
var client = new RestClient(serverUri + "/api/topics");
var content = SendGetRequest(client, Method.GET);
var topics = JsonConvert.DeserializeObject<List<TopicDTO>>(content.Content);
return topics;
}
public TemplateGeneratorDTO RenderTask(TemplateGeneratorForRenderDTO templateGenerator)
{
var client = new RestClient(serverUri + $"/templates/renderTask");
var request = new RestRequest(Method.POST);
request.AddJsonBody(templateGenerator);
var content = client.Execute(request);
var task = JsonConvert.DeserializeObject<TemplateGeneratorDTO>(content.Content);
return task;
}
private IRestResponse SendGetRequest(IRestClient client, Method method, Parameter parameter = null)
{
var request = new RestRequest(method);
if (parameter != null)
request.AddParameter(parameter);
for (var i = 0; i < MaxRetries; i++)
{
var response = client.Execute(request);
if (response.IsSuccessful)
return response;
}
return null;
}
}
}
Frontend чать приложния реализованна на ReactJS.
У Frontend чати редактора есть:
- Страница авторизации
- Компонент меню
- Компоненты упраления (редактирование заданий)
На Frontend реалозована правовая модель. Не все пользователи имеют доступ ко всем элементам управления.
Пример кода с реализацией правовой модели.
export const isAuthenticated = user => !!user;
export const isAllowed = (user, rights) =>
rights.some(right => user.rights.includes(right));
export const hasRole = (user, roles) =>
roles.some(role => user.roles.includes(role));
Пример ипользования isAllowed
{(isAllowed(this.state.user, ['can_edit_topics'])) ?
<div className="center">
<button
className="button1"
onClick={() => this.setState({createTopic: !this.state.createTopic})}
>
Создание Topic
</button>
{(this.state.createTopic) ? < CreateTopicForm/> : undefined}
<br/>
</div>
: undefined}