Social credit validation and recalculation

This commit is contained in:
2025-11-02 02:11:34 +08:00
parent 70fdc247e7
commit 5e5f4528b9
9 changed files with 2826 additions and 14 deletions

View File

@@ -1,10 +1,12 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Credit;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Http;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@@ -266,4 +268,13 @@ public class AccountController(
.Take(take)
.ToListAsync();
}
}
[HttpPost("credits/validate")]
[Authorize]
[RequiredPermission("maintenance", "credits.validate.perform")]
public async Task<IActionResult> PerformSocialCreditValidation()
{
await socialCreditService.ValidateSocialCredits();
return Ok();
}
}

View File

@@ -1,6 +1,7 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Credit;
@@ -35,13 +36,37 @@ public class SocialCreditService(AppDatabase db, ICacheService cache)
{
var cached = await cache.GetAsync<double?>($"{CacheKeyPrefix}{accountId}");
if (cached.HasValue) return cached.Value;
var records = await db.SocialCreditRecords
.Where(x => x.AccountId == accountId)
.Where(x => x.AccountId == accountId && x.Status == SocialCreditRecordStatus.Active)
.SumAsync(x => x.Delta);
records += BaseSocialCredit;
await cache.SetAsync($"{CacheKeyPrefix}{accountId}", records);
return records;
}
}
public async Task ValidateSocialCredits()
{
var now = SystemClock.Instance.GetCurrentInstant();
var expiredRecords = await db.SocialCreditRecords
.Where(r => r.Status == SocialCreditRecordStatus.Active && r.ExpiredAt.HasValue && r.ExpiredAt <= now)
.Select(r => new { r.Id, r.AccountId, r.Delta })
.ToListAsync();
var groupedExpired = expiredRecords.GroupBy(er => er.AccountId)
.ToDictionary(g => g.Key, g => g.Sum(er => er.Delta));
foreach (var (accountId, totalDeltaSubtracted) in groupedExpired)
{
await db.AccountProfiles
.Where(p => p.AccountId == accountId)
.ExecuteUpdateAsync(p => p.SetProperty(v => v.SocialCredits, v => v.SocialCredits - totalDeltaSubtracted));
await cache.RemoveAsync($"{CacheKeyPrefix}{accountId}");
}
await db.SocialCreditRecords
.Where(r => r.Status == SocialCreditRecordStatus.Active && r.ExpiredAt.HasValue && r.ExpiredAt <= now)
.ExecuteUpdateAsync(r => r.SetProperty(x => x.Status, SocialCreditRecordStatus.Expired));
}
}

View File

@@ -0,0 +1,21 @@
using Quartz;
namespace DysonNetwork.Pass.Credit;
public class SocialCreditValidationJob(SocialCreditService socialCreditService, ILogger<SocialCreditValidationJob> logger) : IJob
{
public async Task Execute(IJobExecutionContext context)
{
logger.LogInformation("Starting social credit validation...");
try
{
await socialCreditService.ValidateSocialCredits();
logger.LogInformation("Social credit validation completed successfully.");
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred during social credit validation.");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Pass.Migrations
{
/// <inheritdoc />
public partial class AddSocialCreditRecordStatus : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "status",
table: "social_credit_records",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "status",
table: "social_credit_records");
}
}
}

View File

@@ -1601,6 +1601,10 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("reason_type");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");

View File

@@ -35,14 +35,7 @@ app.MapDefaultEndpoints();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
try
{
await db.Database.MigrateAsync();
}
catch (Exception err)
{
Console.WriteLine(err);
}
await db.Database.MigrateAsync();
}
// Configure application middleware pipeline
@@ -53,4 +46,4 @@ app.ConfigureGrpcServices();
app.UseSwaggerManifest("DysonNetwork.Pass");
app.Run();
app.Run();

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Pass.Credit;
using DysonNetwork.Pass.Handlers;
using DysonNetwork.Pass.Wallet;
using Quartz;
@@ -73,6 +74,13 @@ public static class ScheduledJobsConfiguration
.ForJob(lotteryDrawJob)
.WithIdentity("LotteryDrawTrigger")
.WithCronSchedule("0 0 0 * * ?"));
var socialCreditValidationJob = new JobKey("SocialCreditValidation");
q.AddJob<SocialCreditValidationJob>(opts => opts.WithIdentity(socialCreditValidationJob));
q.AddTrigger(opts => opts
.ForJob(socialCreditValidationJob)
.WithIdentity("SocialCreditValidationTrigger")
.WithCronSchedule("0 0 0 * * ?"));
});
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);

View File

@@ -4,12 +4,19 @@ using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Shared.Models;
public enum SocialCreditRecordStatus
{
Active,
Expired
}
public class SnSocialCreditRecord : ModelBase
{
public Guid Id { get; set; }
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
public double Delta { get; set; }
public SocialCreditRecordStatus Status { get; set; } = SocialCreditRecordStatus.Active;
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }