XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

EARSystem

【ASP】耳间,网页端的情绪智能推荐系统

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using EARSystem.Core;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace EARSystem.Infrastructure;

public record SeedCredentials(string Email, string Password);
public class DemoSeeder(AppDbContext db, UserManager<ApplicationUser> users, RoleManager<IdentityRole> roles, TimeProvider clock)
{
    public async Task InitializeAsync(bool demo, string? credentialsPath = null, string? adminEmail = null, string? adminPassword = null,
        double centerLng = 120.155, double centerLat = 30.274)
    {
        foreach (var role in new[] { "Admin", "User" }) if (!await roles.RoleExistsAsync(role)) await roles.CreateAsync(new(role));
        if (!await db.Rules.AnyAsync()) db.Rules.Add(new RuleConfiguration());
        await db.SaveChangesAsync();
        var credentials = new List<SeedCredentials>();
        if (!string.IsNullOrWhiteSpace(adminEmail) && !string.IsNullOrWhiteSpace(adminPassword))
            await CreateUser(adminEmail, "管理员", false, "Admin", adminPassword, credentials);
        else if (demo) await CreateUser("admin@ear.local", "演示管理员", true, "Admin", Password(), credentials);
        if (!demo) return;
        var rule = await db.Rules.SingleAsync();
        // A completed seed is never replayed: deleted or edited demo records stay that way.
        if (rule.DemoSeededAt.HasValue) {
            await SaveCredentials();
            return;
        }
        var members = new List<ApplicationUser>();
        var demoPassword = Password();
        for (var i = 1; i <= 20; i++)
            members.Add(await CreateUser($"demo{i:00}@ear.local", i == 1 ? "小耳同学" : $"示例用户 {i:00}", true, "User", demoPassword, credentials));
        // Persist generated credentials before the larger data generation operation.
        await SaveCredentials();
        if (!await db.Places.AnyAsync(x => x.Source == PlaceSource.Demo)) {
            var random = new Random(42);
            string[] names = ["一碗好心情", "慢慢咖啡", "绿意小径", "纸间书屋", "留白展厅", "周末游戏屋"];
            string[] descriptions = ["用一顿喜欢的饭,为日常留一点满足。", "找个舒服的角落,让节奏慢下来。", "走进绿意里,留一点时间给自己。",
                "翻几页书,安静地专注一会儿。", "看看展览,让新的灵感自然发生。", "约上朋友,在轻松互动里补充能量。"];
            for (var c = 0; c < 6; c++) for (var i = 0; i < 10; i++) {
                var p = new Place { Name = $"{names[c]} · 示例 {i + 1:00}", Address = $"杭州演示区域 · 自建示例位置 {c * 10 + i + 1:00}",
                    Description = descriptions[c] + " 本地点为虚构演示样本,不代表真实商家。", Category = (PlaceCategory)c,
                    Longitude = centerLng + (random.NextDouble() - .5) * .045,
                    Latitude = centerLat + (random.NextDouble() - .5) * .036,
                    Price = i == 8 ? null : c == 2 ? 0 : c == 0 ? 25 + i * 8 : c == 1 ? 18 + i * 5 : 10 + i * 4,
                    Rating = i == 9 ? null : Math.Round(3.6 + random.NextDouble() * 1.3, 1),
                    OpenHour = 0, CloseHour = 0, Source = PlaceSource.Demo };
                var a = c switch { 0 => new double[] { 3, 4, 1, 3, 4, 1 }, 1 => [4, 5, 2, 2, 3, 4],
                    2 => [4, 4, 5, 2, 2, 2], 3 => [5, 4, 2, 1, 1, 5], 4 => [4, 4, 2, 2, 2, 3], _ => [1, 4, 1, 5, 5, 1] };
                p.Atmosphere = new() { PlaceId = p.Id, Quiet = a[0], Comfort = a[1], Nature = a[2], Liveliness = a[3],
                    Social = a[4], Focus = a[5], BaselineCount = i == 8 ? 0 : 8 + i * 3, Evidence = "合成演示基线;不代表实测或真实用户评价" };
                db.Places.Add(p);
            }
            await db.SaveChangesAsync();
        }
        var places = await db.Places.Where(x => x.Source == PlaceSource.Demo).OrderBy(x => x.Name).ToArrayAsync();
        var now = clock.GetUtcNow();
        var today = ChinaTime.Day(now);
        var rng = new Random(20261006);
        foreach (var (user, userIndex) in members.Select((u, i) => (u, i))) {
            if (await db.Moods.AnyAsync(x => x.UserId == user.Id)) continue;
            db.Preferences.Add(new() { UserId = user.Id, FavoriteCategoriesJson = Labels.Json(new[] { PlaceCategory.Cafe, PlaceCategory.Park }) });
            for (var day = 89; day >= 0; day--) {
                if (rng.Next(10) == 0 && day != 0) continue;
                var recorded = ChinaTime.Start(today.AddDays(-day)).AddHours(9 + userIndex % 6);
                if (recorded > now) recorded = now.AddMinutes(-15);
                var mood = Math.Clamp((int)Math.Round(3.4 + Math.Sin((89 - day + userIndex) / 6.0) + rng.NextDouble() - .5), 1, 5);
                db.Moods.Add(new() { UserId = user.Id, Mood = mood, Energy = rng.Next(2, 6), Stress = rng.Next(1, 5), RecordedAt = recorded,
                    Thoughts = day == 0 ? "今天想给自己一点轻松的时间。路过咖啡馆时,想找个安静的角落坐一会儿。" : "合成演示日记:记录日常的小变化。",
                    TagsJson = Labels.Json(new[] { mood >= 4 ? "开心" : mood <= 2 ? "低落" : "平静" }) });
                if (day % 7 == 1) {
                    var p = places[(day + userIndex * 3) % places.Length];
                    db.Experiences.Add(new() { UserId = user.Id, PlaceId = p.Id, VisitedAt = recorded, VisitDay = ChinaTime.DayKey(recorded),
                        Satisfaction = rng.Next(3, 6), MoodBefore = 3, MoodAfter = mood, Quiet = 4, Comfort = 4, Nature = 3,
                        Liveliness = 2, Social = 3, Focus = 3, Contribute = true, Thoughts = "合成演示体验:给忙碌的日常留一个停顿。" });
                }
            }
            foreach (var p in places.Skip(userIndex % 10).Take(4)) db.Favorites.Add(new() { UserId = user.Id, PlaceId = p.Id });
        }
        rule.DemoSeededAt = now;
        await db.SaveChangesAsync();

        async Task SaveCredentials() {
            if (credentialsPath != null && credentials.Count > 0) {
                Directory.CreateDirectory(Path.GetDirectoryName(credentialsPath)!);
                await File.AppendAllLinesAsync(credentialsPath, credentials.Select(x => $"{x.Email}\t{x.Password}"));
                credentials.Clear();
            }
        }
    }

    private async Task<ApplicationUser> CreateUser(string email, string displayName, bool isDemo, string role, string password, List<SeedCredentials> credentials)
    {
        var existing = await users.FindByEmailAsync(email);
        if (existing != null) return existing;
        var user = new ApplicationUser { UserName = email, Email = email, DisplayName = displayName, IsDemo = isDemo, EmailConfirmed = true };
        var result = await users.CreateAsync(user, password);
        if (!result.Succeeded) throw new InvalidOperationException("初始化账号失败,请检查管理员密码要求。");
        await users.AddToRoleAsync(user, role);
        credentials.Add(new(email, password));
        return user;
    }
    private static string Password() => "Ear!" + Convert.ToHexString(RandomNumberGenerator.GetBytes(16)) + "a9";
}