校历优化
This commit is contained in:
1 parent
0ef0d4d399
commit
4ef4164b00
6 files changed
+196
-39
No files matched your search
@@ -119,21 +119,23 @@ public sealed class AcademicCalendarController(AppDbContext db, HolidayCnClient
|
||||
catch (InvalidOperationException exception) { return ValidationProblem(exception.Message); }
|
||||
|
||||
var existing = await db.AcademicCalendarEvents.Where(item => item.AcademicTermId == request.AcademicTermId && item.Source == AcademicCalendarEventSource.HolidayCn && item.SourceYear == request.Year).ToListAsync(cancellationToken);
|
||||
var ranges = HolidayCnRanges.Group(source.Days);
|
||||
db.AcademicCalendarEvents.RemoveRange(existing);
|
||||
db.AcademicCalendarEvents.AddRange(source.Days.Select(day => new AcademicCalendarEvent
|
||||
db.AcademicCalendarEvents.AddRange(ranges.Select(range => new AcademicCalendarEvent
|
||||
{
|
||||
AcademicTermId = request.AcademicTermId,
|
||||
Title = day.Name,
|
||||
Title = range.Name,
|
||||
Kind = AcademicCalendarEventKind.PublicHoliday,
|
||||
StartsOn = day.Date,
|
||||
ReminderDays = day.IsOffDay ? 3 : 0,
|
||||
Description = day.IsOffDay ? "法定节假日或调休休息日" : "调休上班日",
|
||||
StartsOn = range.StartsOn,
|
||||
EndsOn = range.StartsOn == range.EndsOn ? null : range.EndsOn,
|
||||
ReminderDays = range.IsOffDay ? 3 : 0,
|
||||
Description = range.IsOffDay ? "法定节假日或调休休息日" : "调休上班日",
|
||||
Source = AcademicCalendarEventSource.HolidayCn,
|
||||
SourceYear = request.Year,
|
||||
IsOffDay = day.IsOffDay
|
||||
IsOffDay = range.IsOffDay
|
||||
}));
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return Ok(new { Year = request.Year, SyncedCount = source.Days.Count, ReplacedCount = existing.Count, source.Papers });
|
||||
return Ok(new { Year = request.Year, SyncedCount = ranges.Count, SourceDayCount = source.Days.Count, ReplacedCount = existing.Count, source.Papers });
|
||||
}
|
||||
|
||||
private async Task<ActionResult?> ValidateAsync(AcademicCalendarEventRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -21,3 +21,42 @@ public sealed class HolidayCnClient(HttpClient httpClient)
|
||||
|
||||
public sealed record HolidayCnYear(int Year, IReadOnlyList<string> Papers, IReadOnlyList<HolidayCnDay> Days);
|
||||
public sealed record HolidayCnDay(string Name, DateOnly Date, bool IsOffDay);
|
||||
|
||||
/// <summary>
|
||||
/// 将数据源中按日列出的同一连续节假日归并为一个校历日程。
|
||||
/// 调休上班日与休息日即使同名也必须分别保留,避免错误地把工作日包含在假期范围内。
|
||||
/// </summary>
|
||||
public static class HolidayCnRanges
|
||||
{
|
||||
public static IReadOnlyList<HolidayCnRange> Group(IEnumerable<HolidayCnDay> days) => days
|
||||
.OrderBy(day => day.Date)
|
||||
.GroupBy(day => new { day.Name, day.IsOffDay })
|
||||
.SelectMany(group => GroupConsecutive(group.Key.Name, group.Key.IsOffDay, group))
|
||||
.OrderBy(range => range.StartsOn)
|
||||
.ToList();
|
||||
|
||||
private static IEnumerable<HolidayCnRange> GroupConsecutive(
|
||||
string name,
|
||||
bool isOffDay,
|
||||
IEnumerable<HolidayCnDay> days)
|
||||
{
|
||||
HolidayCnDay? first = null;
|
||||
HolidayCnDay? previous = null;
|
||||
foreach (var day in days.OrderBy(item => item.Date))
|
||||
{
|
||||
if (first is not null && previous is not null && day.Date > previous.Date.AddDays(1))
|
||||
{
|
||||
yield return new HolidayCnRange(name, first.Date, previous.Date, isOffDay);
|
||||
first = null;
|
||||
}
|
||||
|
||||
first ??= day;
|
||||
previous = day;
|
||||
}
|
||||
|
||||
if (first is not null && previous is not null)
|
||||
yield return new HolidayCnRange(name, first.Date, previous.Date, isOffDay);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record HolidayCnRange(string Name, DateOnly StartsOn, DateOnly EndsOn, bool IsOffDay);
|
||||
@@ -1,6 +1,7 @@
|
||||
using CaptchaKit.AspNetCore;
|
||||
using CaptchaKit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Jiaowu.Api.Tests;
|
||||
|
||||
@@ -34,6 +35,28 @@ public sealed class CaptchaKitIntegrationTests
|
||||
Assert.True(challenge.Challenge.ImageBytes!.AsSpan().StartsWith(new byte[] { 137, 80, 78, 71 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Selection_visual_keeps_the_target_character_visible_above_noise()
|
||||
{
|
||||
var challenge = new SkiaTextCaptchaGenerator().CreateVisualChallenge(
|
||||
new CaptchaGenerationRequest("test", DateTimeOffset.UtcNow.AddMinutes(1), 5),
|
||||
"A", "choice-A", CaptchaChallengeType.Selection, "请选择对应字符");
|
||||
|
||||
using var bitmap = SKBitmap.Decode(challenge.Challenge.ImageBytes!);
|
||||
Assert.NotNull(bitmap);
|
||||
|
||||
var targetPixelCount = 0;
|
||||
for (var y = 0; y < bitmap!.Height; y++)
|
||||
for (var x = bitmap.Width / 4; x < bitmap.Width * 3 / 4; x++)
|
||||
{
|
||||
var pixel = bitmap.GetPixel(x, y);
|
||||
if (pixel.Red <= 30 && pixel.Green is >= 40 and <= 60 && pixel.Blue is >= 70 and <= 90)
|
||||
targetPixelCount++;
|
||||
}
|
||||
|
||||
Assert.True(targetPixelCount > 20, "The target character should be visibly rendered in the image center.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Random_generator_produces_each_enabled_challenge_type()
|
||||
{
|
||||
|
||||
Loaded 3 of 6 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user