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

EARSystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using EARSystem.Core;
using EARSystem.Infrastructure;
using EARSystem.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace EARSystem.Web.Controllers;

[Authorize(Roles = "Admin")]
public class AdminController(AppDbContext db) : Controller
{
    public async Task<IActionResult> Index(CancellationToken ct) {
        ViewBag.Users = await db.Users.CountAsync(ct);
        ViewBag.Recommendations = await db.Recommendations.CountAsync(ct);
        var durations = await db.Recommendations.Where(x => x.Mode == DataMode.Demo).OrderByDescending(x => x.CreatedAt)
            .Select(x => x.DurationMs).Take(100).ToArrayAsync(ct);
        ViewBag.Latency = durations.Length > 0 ? Math.Round(durations.Average(), 1) : 0;
        ViewBag.Rule = await db.Rules.AsNoTracking().SingleAsync(ct);
        return View(await db.Places.AsNoTracking().Include(x => x.Atmosphere).OrderBy(x => x.Source).ThenBy(x => x.Name).ToArrayAsync(ct));
    }
    [HttpGet]
    public async Task<IActionResult> Edit(Guid? id, CancellationToken ct) {
        if (id == null) return View(new AdminPlaceInput());
        var p = await db.Places.AsNoTracking().Include(x => x.Atmosphere).SingleOrDefaultAsync(x => x.Id == id, ct);
        if (p == null) return NotFound();
        var a = p.Atmosphere ?? new();
        return View(new AdminPlaceInput { Id = p.Id, Name = p.Name, Address = p.Address, Description = p.Description,
            Category = p.Category, Source = p.Source, Longitude = p.Longitude, Latitude = p.Latitude, Price = p.Price,
            Rating = p.Rating, OpenHour = p.OpenHour, CloseHour = p.CloseHour, Quiet = a.Quiet, Comfort = a.Comfort,
            Nature = a.Nature, Liveliness = a.Liveliness, Social = a.Social, Focus = a.Focus, BaselineCount = a.BaselineCount, Evidence = a.Evidence });
    }
    [HttpPost]
    public async Task<IActionResult> Edit(AdminPlaceInput input, CancellationToken ct) {
        if (!Enum.IsDefined(input.Source) || !Enum.IsDefined(input.Category)) ModelState.AddModelError("", "类别或来源无效。");
        if (input.Source == PlaceSource.Collected && input.BaselineCount > 0 && string.IsNullOrWhiteSpace(input.Evidence))
            ModelState.AddModelError("", "真实采集基线必须填写采集来源及样本说明。");
        if (!ModelState.IsValid) return View(input);
        var p = input.Id.HasValue ? await db.Places.Include(x => x.Atmosphere).SingleOrDefaultAsync(x => x.Id == input.Id, ct) : new Place();
        if (p == null) return NotFound();
        if (input.Id.HasValue && p.Source != input.Source) {
            ModelState.AddModelError("", "已有地点的数据来源不可更改,请新建独立地点以保持数据隔离。"); return View(input);
        }
        p.Name = input.Name; p.Address = input.Address; p.Description = input.Description; p.Category = input.Category; p.Source = input.Source;
        p.Longitude = input.Longitude; p.Latitude = input.Latitude; p.Price = input.Price; p.Rating = input.Rating;
        p.OpenHour = input.OpenHour; p.CloseHour = input.CloseHour;
        p.Atmosphere ??= new() { PlaceId = p.Id };
        p.Atmosphere.Quiet = input.Quiet; p.Atmosphere.Comfort = input.Comfort; p.Atmosphere.Nature = input.Nature;
        p.Atmosphere.Liveliness = input.Liveliness; p.Atmosphere.Social = input.Social; p.Atmosphere.Focus = input.Focus;
        p.Atmosphere.BaselineCount = input.BaselineCount; p.Atmosphere.Evidence = input.Evidence;
        if (!input.Id.HasValue) db.Places.Add(p);
        await db.SaveChangesAsync(ct); TempData["Notice"] = "地点已保存。"; return RedirectToAction(nameof(Index));
    }
    [HttpPost]
    public async Task<IActionResult> Rules(RuleInput input, CancellationToken ct) {
        if (!input.Weights.IsValid()) return BadRequest(new { title = "权重须为 0–1,且六项之和等于 1。" });
        var rule = await db.Rules.SingleAsync(ct);
        if (rule.ConcurrencyStamp != input.ConcurrencyStamp) return Conflict(new { title = "配置已修改,请刷新后重试。" });
        rule.WeightsJson = Labels.Json(input.Weights); rule.Version = DateTimeOffset.UtcNow.ToString("yyyyMMddHHmmssfff");
        rule.ConcurrencyStamp = Guid.NewGuid(); await db.SaveChangesAsync(ct);
        TempData["Notice"] = "推荐权重已更新,新推荐将使用新版本。"; return RedirectToAction(nameof(Index));
    }
    [HttpPost]
    public async Task<IActionResult> Delete(Guid id, CancellationToken ct) {
        var p = await db.Places.FindAsync([id], ct);
        if (p == null) return NotFound();
        if (await db.Experiences.AnyAsync(x => x.PlaceId == id, ct) || await db.Favorites.AnyAsync(x => x.PlaceId == id, ct)) {
            TempData["Notice"] = "地点已有收藏或体验,请保留记录并编辑信息。"; return RedirectToAction(nameof(Index));
        }
        db.Places.Remove(p); await db.SaveChangesAsync(ct); return RedirectToAction(nameof(Index));
    }
}