Web: Add REST style API

This commit is contained in:
genteure 2022-04-03 13:34:55 +08:00
parent 958683a1d1
commit abd62dc378
16 changed files with 662 additions and 15 deletions

View File

@ -85,7 +85,7 @@ namespace BililiveRecorder.Web.Schemas.Types
} }
} }
internal class SetRoomConfig public class SetRoomConfig // TODO MOVE THIS TYPE
{ {
public bool? AutoRecord { get; set; } public bool? AutoRecord { get; set; }
public Optional<RecordMode>? OptionalRecordMode { get; set; } public Optional<RecordMode>? OptionalRecordMode { get; set; }
@ -130,7 +130,7 @@ namespace BililiveRecorder.Web.Schemas.Types
} }
} }
internal class SetGlobalConfig public class SetGlobalConfig // TODO MOVE THIS TYPE
{ {
public Optional<RecordMode>? OptionalRecordMode { get; set; } public Optional<RecordMode>? OptionalRecordMode { get; set; }
public Optional<CuttingMode>? OptionalCuttingMode { get; set; } public Optional<CuttingMode>? OptionalCuttingMode { get; set; }

View File

@ -2,7 +2,7 @@ namespace BililiveRecorder.Web.Schemas.Types
{ {
public class RecorderVersion public class RecorderVersion
{ {
internal static readonly RecorderVersion Instance = new(); public static readonly RecorderVersion Instance = new();
public string Major { get; } = GitVersionInformation.Major; public string Major { get; } = GitVersionInformation.Major;
public string Minor { get; } = GitVersionInformation.Minor; public string Minor { get; } = GitVersionInformation.Minor;

View File

@ -0,0 +1,49 @@
using System;
using AutoMapper;
using BililiveRecorder.Core;
using BililiveRecorder.Core.Config.V2;
using BililiveRecorder.Web.Models;
using BililiveRecorder.Web.Schemas.Types;
using Microsoft.AspNetCore.Mvc;
namespace BililiveRecorder.Web.Api
{
[ApiController, Route("api/[controller]", Name = "[controller] [action]")]
public class ConfigController : ControllerBase
{
private readonly IMapper mapper;
private readonly IRecorder recorder;
public ConfigController(IMapper mapper, IRecorder recorder)
{
this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
this.recorder = recorder ?? throw new ArgumentNullException(nameof(recorder));
}
/// <summary>
/// 获取软件默认设置
/// </summary>
/// <returns></returns>
[HttpGet("default")]
public ActionResult<DefaultConfig> GetDefaultConfig() => DefaultConfig.Instance;
/// <summary>
/// 获取全局设置
/// </summary>
/// <returns></returns>
[HttpGet("global")]
public ActionResult<GlobalConfigDto> GetGlobalConfig() => this.mapper.Map<GlobalConfigDto>(this.recorder.Config.Global);
/// <summary>
/// 设置全局设置
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
[HttpPost("global")]
public ActionResult<GlobalConfigDto> SetGlobalConfig([FromBody] SetGlobalConfig config)
{
config.ApplyTo(this.recorder.Config.Global);
return this.mapper.Map<GlobalConfigDto>(this.recorder.Config.Global);
}
}
}

View File

@ -0,0 +1,23 @@
using AutoMapper;
using BililiveRecorder.Core;
using BililiveRecorder.Core.Config.V2;
using BililiveRecorder.Web.Models;
namespace BililiveRecorder.Web.Api
{
public class DataMappingProfile : Profile
{
public DataMappingProfile()
{
this.CreateMap<IRoom, RoomDto>()
.ForMember(x => x.RoomId, x => x.MapFrom(s => s.RoomConfig.RoomId))
.ForMember(x => x.AutoRecord, x => x.MapFrom(s => s.RoomConfig.AutoRecord));
this.CreateMap<RecordingStats, RoomStatsDto>();
this.CreateMap<RoomConfig, RoomConfigDto>();
this.CreateMap<GlobalConfig, GlobalConfigDto>();
}
}
}

View File

@ -0,0 +1,403 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using BililiveRecorder.Core;
using BililiveRecorder.Web.Models;
using BililiveRecorder.Web.Schemas.Types;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace BililiveRecorder.Web.Api
{
[ApiController, Route("api/[controller]", Name = "[controller] [action]")]
public class RoomController : ControllerBase
{
private readonly IMapper mapper;
private readonly IRecorder recorder;
public RoomController(IMapper mapper, IRecorder recorder)
{
this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
this.recorder = recorder ?? throw new ArgumentNullException(nameof(recorder));
}
private IRoom? FetchRoom(int roomId) => this.recorder.Rooms.FirstOrDefault(x => x.ShortId == roomId || x.RoomConfig.RoomId == roomId);
private IRoom? FetchRoom(Guid objectId) => this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId);
/// <summary>
/// 列出所有直播间
/// </summary>
/// <returns></returns>
[HttpGet]
public RoomDto[] GetRooms() => this.mapper.Map<RoomDto[]>(this.recorder.Rooms);
#region Create & Delete
/// <summary>
/// 添加直播间
/// </summary>
/// <param name="createRoom"></param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status400BadRequest)]
public ActionResult<RoomDto> CreateRoom([FromBody] CreateRoomDto createRoom)
{
if (createRoom.RoomId <= 0)
return this.BadRequest(new RestApiError { Code = RestApiErrorCode.RoomidOutOfRange, Message = "Roomid must be greater than 0." });
var room = this.FetchRoom(createRoom.RoomId);
if (room is not null)
return this.BadRequest(new RestApiError { Code = RestApiErrorCode.RoomExist, Message = "Can not add the same room multiple times." });
room = this.recorder.AddRoom(createRoom.RoomId, createRoom.AutoRecord);
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 删除直播间
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpDelete("{roomId:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> DeleteRoom(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
this.recorder.RemoveRoom(room);
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 删除直播间
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpDelete("{objectId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> DeleteRoom(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
this.recorder.RemoveRoom(room);
return this.mapper.Map<RoomDto>(room);
}
#endregion
#region Get Room
/// <summary>
/// 读取一个直播间
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpGet("{roomId:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> GetRoom(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 读取一个直播间
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpGet("{objectId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> GetRoom(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomDto>(room);
}
#endregion
#region Get Room Stats
/// <summary>
/// 读取直播间统计信息
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpGet("{roomId:int}/stats")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomStatsDto> GetRoomStats(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomStatsDto>(room.Stats);
}
/// <summary>
/// 读取直播间统计信息
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpGet("{objectId:guid}/stats")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomStatsDto> GetRoomStats(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomStatsDto>(room.Stats);
}
#endregion
#region Room Config
/// <summary>
/// 读取直播间设置
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpGet("{roomId:int}/config")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomConfigDto> GetRoomConfig(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomConfigDto>(room.RoomConfig);
}
/// <summary>
/// 读取直播间设置
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpGet("{objectId:guid}/config")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomConfigDto> GetRoomConfig(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
return this.mapper.Map<RoomConfigDto>(room.RoomConfig);
}
/// <summary>
/// 修改直播间设置
/// </summary>
/// <param name="roomId"></param>
/// <param name="config"></param>
/// <returns></returns>
[HttpPost("{roomId:int}/config")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomConfigDto> SetRoomConfig(int roomId, [FromBody] SetRoomConfig config)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
config.ApplyTo(room.RoomConfig);
return this.mapper.Map<RoomConfigDto>(room.RoomConfig);
}
/// <summary>
/// 修改直播间设置
/// </summary>
/// <param name="objectId"></param>
/// <param name="config"></param>
/// <returns></returns>
[HttpPost("{objectId:guid}/config")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomConfigDto> SetRoomConfig(Guid objectId, [FromBody] SetRoomConfig config)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
config.ApplyTo(room.RoomConfig);
return this.mapper.Map<RoomConfigDto>(room.RoomConfig);
}
#endregion
#region Room Action
/// <summary>
/// 开始录制
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpPost("{roomId:int}/start")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> StartRecording(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.StartRecord();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 开始录制
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpPost("{objectId:guid}/start")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> StartRecording(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.StartRecord();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 停止录制
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpPost("{roomId:int}/stop")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> StopRecording(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.StopRecord();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 停止录制
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpPost("{objectId:guid}/stop")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> StopRecording(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.StopRecord();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 手动分段
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpPost("{roomId:int}/split")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> SplitRecording(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.SplitOutput();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 手动分段
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpPost("{objectId:guid}/split")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public ActionResult<RoomDto> SplitRecording(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
room.SplitOutput();
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 刷新直播间信息
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[HttpPost("{roomId:int}/refresh")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public async Task<ActionResult<RoomDto>> RefreshRecordingAsync(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
await room.RefreshRoomInfoAsync().ConfigureAwait(false);
return this.mapper.Map<RoomDto>(room);
}
/// <summary>
/// 刷新直播间信息
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
[HttpPost("{objectId:guid}/refresh")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
public async Task<ActionResult<RoomDto>> RefreshRecordingAsync(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
await room.RefreshRoomInfoAsync().ConfigureAwait(false);
return this.mapper.Map<RoomDto>(room);
}
#endregion
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
namespace BililiveRecorder.Web.Api
{
[ApiController, Route("api/[controller]", Name = "[controller] [action]")]
public class VersionController : ControllerBase
{
/// <summary>
/// 读取软件版本信息
/// </summary>
/// <returns></returns>
[HttpGet]
public Schemas.Types.RecorderVersion GetVersion() => Schemas.Types.RecorderVersion.Instance;
}
}

View File

@ -2,10 +2,15 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.NewtonsoftJson" Version="5.0.2" /> <PackageReference Include="GraphQL.Server.Transports.AspNetCore.NewtonsoftJson" Version="5.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -15,6 +15,7 @@ namespace BililiveRecorder.Web
public ReadOnlyObservableCollection<IRoom> Rooms { get; } = new ReadOnlyObservableCollection<IRoom>(new ObservableCollection<IRoom>()); public ReadOnlyObservableCollection<IRoom> Rooms { get; } = new ReadOnlyObservableCollection<IRoom>(new ObservableCollection<IRoom>());
#pragma warning disable CS0067
public event EventHandler<AggregatedRoomEventArgs<RecordSessionStartedEventArgs>>? RecordSessionStarted; public event EventHandler<AggregatedRoomEventArgs<RecordSessionStartedEventArgs>>? RecordSessionStarted;
public event EventHandler<AggregatedRoomEventArgs<RecordSessionEndedEventArgs>>? RecordSessionEnded; public event EventHandler<AggregatedRoomEventArgs<RecordSessionEndedEventArgs>>? RecordSessionEnded;
public event EventHandler<AggregatedRoomEventArgs<RecordFileOpeningEventArgs>>? RecordFileOpening; public event EventHandler<AggregatedRoomEventArgs<RecordFileOpeningEventArgs>>? RecordFileOpening;
@ -22,6 +23,7 @@ namespace BililiveRecorder.Web
public event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats; public event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats;
public event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats; public event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
#pragma warning restore CS0067
public IRoom AddRoom(int roomid) => null!; public IRoom AddRoom(int roomid) => null!;

View File

@ -0,0 +1,8 @@
namespace BililiveRecorder.Web.Models
{
public class CreateRoomDto
{
public int RoomId { get; set; }
public bool AutoRecord { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BililiveRecorder.Web.Models
{
public class GlobalConfigDto
{
}
}

View File

@ -0,0 +1,9 @@
namespace BililiveRecorder.Web.Models
{
public class RestApiError
{
public RestApiErrorCode Code { get; set; }
public string Message { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace BililiveRecorder.Web.Models
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum RestApiErrorCode
{
/// <summary>
/// 错误
/// </summary>
Unknown,
/// <summary>
/// 房间号不在允许的范围内
/// </summary>
RoomidOutOfRange,
/// <summary>
/// 房间已存在
/// </summary>
RoomExist,
/// <summary>
/// 房间不存在
/// </summary>
RoomNotFound,
}
}

View File

@ -0,0 +1,7 @@
namespace BililiveRecorder.Web.Models
{
public class RoomConfigDto
{
// TODO auto generate this
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace BililiveRecorder.Web.Models
{
public class RoomDto
{
public Guid ObjectId { get; set; }
public int RoomId { get; set; }
public bool AutoRecord { get; set; }
public int ShortId { get; set; }
public string? Name { get; set; }
public string? Title { get; set; }
public string? AreaNameParent { get; set; }
public string? AreaNameChild { get; set; }
public bool Recording { get; set; }
public bool Streaming { get; set; }
public bool DanmakuConnected { get; set; }
public bool AutoRecordForThisSession { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace BililiveRecorder.Web.Models
{
public class RoomStatsDto
{
public TimeSpan SessionDuration { get; set; }
public TimeSpan SessionMaxTimestamp { get; set; }
public TimeSpan FileMaxTimestamp { get; set; }
public double DurationRatio { get; set; }
public long TotalInputBytes { get; set; }
public long TotalOutputBytes { get; set; }
public double NetworkMbps { get; set; }
}
}

View File

@ -1,5 +1,8 @@
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using BililiveRecorder.Core; using BililiveRecorder.Core;
using BililiveRecorder.Web.Api;
using BililiveRecorder.Web.Schemas; using BililiveRecorder.Web.Schemas;
using GraphQL; using GraphQL;
using GraphQL.Server; using GraphQL.Server;
@ -13,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
namespace BililiveRecorder.Web namespace BililiveRecorder.Web
{ {
@ -38,6 +42,12 @@ namespace BililiveRecorder.Web
services.TryAddSingleton<IRecorder>(new FakeRecorderForWeb()); services.TryAddSingleton<IRecorder>(new FakeRecorderForWeb());
services.AddAutoMapper(c =>
{
c.AddProfile<DataMappingProfile>();
});
// Graphql API
services services
.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader())) .AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()))
.AddSingleton<RecorderSchema>() .AddSingleton<RecorderSchema>()
@ -49,34 +59,77 @@ namespace BililiveRecorder.Web
var logger = provider.GetRequiredService<ILogger<Startup>>(); var logger = provider.GetRequiredService<ILogger<Startup>>();
options.UnhandledExceptionDelegate = ctx => logger.LogWarning(ctx.OriginalException, "Unhandled GraphQL Exception"); options.UnhandledExceptionDelegate = ctx => logger.LogWarning(ctx.OriginalException, "Unhandled GraphQL Exception");
}) })
.AddDefaultEndpointSelectorPolicy()
//.AddSystemTextJson() //.AddSystemTextJson()
.AddNewtonsoftJson() .AddNewtonsoftJson()
.AddWebSockets() .AddWebSockets()
.AddGraphTypes(typeof(RecorderSchema)) .AddGraphTypes(typeof(RecorderSchema))
; ;
// REST API
services
.AddSwaggerGen(c =>
{
c.SwaggerDoc("brec", new OpenApiInfo
{
Title = "录播姬 REST API",
Description = "录播姬网站 [rec.danmuji.org](https://rec.danmuji.org/) \n录播姬 GitHub [Bililive/BililiveRecorder](https://github.com/Bililive/BililiveRecorder) \n\n除了 REST API 以外,录播姬还有 Graphql API 可以使用。",
Version = "v1"
});
var filePath = Path.Combine(AppContext.BaseDirectory, "BililiveRecorder.Web.xml");
c.IncludeXmlComments(filePath);
})
.AddRouting(c =>
{
c.LowercaseUrls = true;
c.LowercaseQueryStrings = true;
})
.AddMvcCore(option =>
{
})
.AddApiExplorer();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) => app public void Configure(IApplicationBuilder app, IWebHostEnvironment env) => app
.UseCors() .UseCors()
.UseWebSockets() .UseWebSockets()
.UseGraphQLWebSockets<RecorderSchema>() .UseRouting()
.UseGraphQL<RecorderSchema>() .UseEndpoints(endpoints =>
.UseGraphQLPlayground()
.UseGraphQLGraphiQL()
.UseGraphQLAltair()
.UseGraphQLVoyager()
.Use(next => async context =>
{ {
if (context.Request.Path == "/") endpoints.MapControllers();
endpoints.MapGraphQL<RecorderSchema>();
endpoints.MapGraphQLWebSockets<RecorderSchema>();
endpoints.MapSwagger();
endpoints.MapGraphQLPlayground();
endpoints.MapGraphQLGraphiQL();
endpoints.MapGraphQLAltair();
endpoints.MapGraphQLVoyager();
endpoints.MapGet("/", async context =>
{ {
context.Response.ContentType = "text/html"; context.Response.ContentType = "text/html";
await context.Response.WriteAsync(ConstStrings.HOME_PAGE_HTML, encoding: System.Text.Encoding.UTF8).ConfigureAwait(false); await context.Response.WriteAsync(ConstStrings.HOME_PAGE_HTML, encoding: System.Text.Encoding.UTF8).ConfigureAwait(false);
} });
else
endpoints.MapGet("favicon.ico", async context =>
{ {
context.Response.Redirect("/"); context.Response.StatusCode = 404;
} await context.Response.WriteAsync(string.Empty);
});
})
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("brec/swagger.json", "录播姬 REST API");
})
.Use(next => async context =>
{
context.Response.Redirect("/");
await context.Response.WriteAsync(string.Empty);
}) })
; ;
} }