网站首页 网站源码
using System.Text.Json;
using Dpz.Core.Auth.Models;
using Dpz.Core.Auth.Service;
using Dpz.Core.EnumLibrary;
using Dpz.Core.Infrastructure;
using Dpz.Core.MongodbAccess;
using Dpz.Core.Public.Entity.Auth;
using Dpz.Core.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
using OpenIddict.Abstractions;
namespace Dpz.Core.Auth.Controllers;
[Authorize(nameof(Permissions.System))]
public class ApplicationController(
IOpenIddictApplicationManager openIddictApplicationManager,
IRepository<DpzApplication> applicationRepository,
IPinCodeValidator pinCodeValidator,
ILogger<ApplicationController> logger
) : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public async Task<IActionResult> Page(string? keyword = null, int page = 1, int limit = 10)
{
if (page < 1)
{
page = 1;
}
if (limit < 1)
{
limit = 1;
}
var filterDefinition = Builders<DpzApplication>.Filter.Empty;
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword = keyword.Trim();
var regex = new BsonRegularExpression(keyword, "i");
filterDefinition &= Builders<DpzApplication>.Filter.Or(
Builders<DpzApplication>.Filter.Regex(x => x.DisplayName, regex),
Builders<DpzApplication>.Filter.Regex(x => x.ClientId, regex),
Builders<DpzApplication>.Filter.Regex(x => x.RedirectUris, regex),
Builders<DpzApplication>.Filter.Regex(x => x.PostLogoutRedirectUri, regex)
);
}
var pagedList = await applicationRepository
.SearchFor(filterDefinition)
.SortByDescending(x => x.Id)
.ToPagedListAsync(page, limit);
var data = pagedList
.Select(x => new PageApplicationModel
{
Id = x.Id.ToString(),
ClientId = x.ClientId,
DisplayName = x.DisplayName,
ApplicationType = x.ApplicationType,
ClientType = x.ClientType,
RedirectUris = string.IsNullOrWhiteSpace(x.RedirectUris)
? []
: x
.RedirectUris.Split(
';',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.ToList(),
PostLogoutRedirectUri = x.PostLogoutRedirectUri,
Permissions = x.Permissions is { Count: > 0 } permissions
? permissions.ToList()
: [],
})
.ToList();
return Json(new LayuiPageWarp<PageApplicationModel>(data, pagedList.TotalItemCount));
}
[HttpGet]
public async Task<IActionResult> Upsert(string? id = null)
{
DpzApplication? application = null;
if (!string.IsNullOrWhiteSpace(id))
{
application = await openIddictApplicationManager.FindByIdAsync(id) as DpzApplication;
}
// 失败回填草稿
if (
application is null
&& TempData["AppDraft"] is string json
&& !string.IsNullOrWhiteSpace(json)
)
{
try
{
var draft = JsonSerializer.Deserialize<AppDraft>(json);
if (draft != null)
{
application = new DpzApplication
{
ClientId = draft.ClientId,
ClientSecret = draft.ClientSecret,
DisplayName = draft.DisplayName,
RedirectUris = draft.RedirectUris,
PostLogoutRedirectUri = draft.PostLogoutRedirectUri,
ApplicationType = draft.ApplicationType,
ClientType = draft.ClientType,
Permissions = draft.Permissions?.ToList() ?? new List<string>(),
Properties = string.IsNullOrWhiteSpace(draft.Logo)
? null
: new BsonDocument("Logo", draft.Logo),
};
}
}
catch (Exception e)
{
logger.LogError(e, "反序列化失败");
}
}
return View(application);
}
[HttpPost]
public async Task<IActionResult> Create(
DpzApplication application,
string pinCode,
string? logo
)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(application.ClientId))
{
errors.Add("请填写 ClientId");
}
if (string.IsNullOrWhiteSpace(application.DisplayName))
{
errors.Add("请填写显示名称");
}
if (string.IsNullOrWhiteSpace(application.ClientType))
{
errors.Add("请选择客户端类型");
}
if (
string.Equals(
application.ClientType,
OpenIddictConstants.ClientTypes.Confidential,
StringComparison.OrdinalIgnoreCase
) && string.IsNullOrWhiteSpace(application.ClientSecret)
)
{
errors.Add("机密客户端需填写 ClientSecret");
}
var (success, message) = await pinCodeValidator.ValidateAsync(User.NameIdentifier, pinCode);
if (!success)
{
errors.Add(message ?? "PIN 验证失败");
}
if (errors.Count > 0)
{
PersistDraft(application, logo);
TempData["message"] = string.Join(";", errors);
return RedirectToAction("Upsert");
}
if (!string.IsNullOrWhiteSpace(logo))
{
application.Properties ??= [];
application.Properties["Logo"] = logo;
}
var secret = application.ClientSecret;
application.ClientSecret = null;
await openIddictApplicationManager.CreateAsync(application, secret);
return RedirectToAction("Index");
}
[HttpPost]
public async Task<IActionResult> Update(
DpzApplication application,
string pinCode,
string? logo
)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(application.ClientId))
{
errors.Add("请填写 ClientId");
}
if (string.IsNullOrWhiteSpace(application.DisplayName))
{
errors.Add("请填写显示名称");
}
if (string.IsNullOrWhiteSpace(application.ClientType))
{
errors.Add("请选择客户端类型");
}
// 更新时不校验/不修改 ClientSecret(只在创建时设置)
var (success, message) = await pinCodeValidator.ValidateAsync(User.NameIdentifier, pinCode);
if (!success)
{
errors.Add(message ?? "PIN 验证失败");
}
if (errors.Count > 0)
{
PersistDraft(application, logo);
TempData["message"] = string.Join(";", errors);
return RedirectToAction("Upsert", new { id = application.Id });
}
// 读取现有实体,防止意外清空 ClientSecret
if (
await openIddictApplicationManager.FindByIdAsync(application.Id.ToString())
is DpzApplication existing
)
{
application.ClientSecret = existing.ClientSecret;
// 防护:不允许在更新时变更 ClientId
application.ClientId = existing.ClientId;
}
if (!string.IsNullOrWhiteSpace(logo))
{
application.Properties ??= [];
application.Properties["Logo"] = logo;
}
await openIddictApplicationManager.UpdateAsync(application);
return RedirectToAction("Index");
}
[HttpPost]
public async Task<IActionResult> Delete(string id, string pinCode)
{
var (success, message) = await pinCodeValidator.ValidateAsync(User.NameIdentifier, pinCode);
if (!success)
{
return Json(ResponseResult.ToFail(message));
}
var application = await openIddictApplicationManager.FindByIdAsync(id);
if (application == null)
{
return Json(ResponseResult.ToFail("未找到该应用"));
}
await openIddictApplicationManager.DeleteAsync(application);
return Json(ResponseResult.ToSuccess("删除成功"));
}
private void PersistDraft(DpzApplication application, string? logo)
{
var draft = new AppDraft
{
ClientId = application.ClientId,
ClientSecret = application.ClientSecret,
DisplayName = application.DisplayName,
RedirectUris = application.RedirectUris,
PostLogoutRedirectUri = application.PostLogoutRedirectUri,
ApplicationType = application.ApplicationType,
ClientType = application.ClientType,
Permissions = application.Permissions,
Logo = logo,
};
TempData["AppDraft"] = JsonSerializer.Serialize(draft);
}
}
file class AppDraft
{
public string? ClientId { get; init; }
public string? ClientSecret { get; init; }
public string? DisplayName { get; init; }
public string? RedirectUris { get; init; }
public string? PostLogoutRedirectUri { get; init; }
public string? ApplicationType { get; init; }
public string? ClientType { get; init; }
public List<string>? Permissions { get; init; }
public string? Logo { get; init; }
}
上述代码是一个 ASP.NET Core MVC 控制器,名为 ApplicationController,用于管理与 OpenIddict 应用程序相关的操作。该控制器实现了对应用程序的增、删、改、查功能,并且使用 MongoDB 作为数据存储。以下是代码的主要功能和结构的详细解释:
ApplicationController 继承自 Controller,并通过构造函数注入了多个服务,包括 OpenIddict 应用程序管理器、MongoDB 仓储、PIN 码验证器和日志记录器。[Authorize(nameof(Permissions.System))] 特性,确保只有具有特定权限的用户才能访问该控制器的操作。Index:返回应用程序的主视图。
Page:处理分页查询,支持根据关键字搜索应用程序。它使用 MongoDB 的过滤器构建查询,并返回符合条件的应用程序列表和总记录数。
Upsert:用于创建或更新应用程序的视图。根据传入的 id 参数判断是创建新应用程序还是编辑现有应用程序。如果找不到应用程序且存在草稿数据,则尝试从草稿中恢复数据。
Create:处理创建新应用程序的 POST 请求。它会验证输入的必要字段(如 ClientId、DisplayName、ClientType 等),并在验证失败时保存草稿数据。成功创建后,重定向到 Index 页面。
Update:处理更新现有应用程序的 POST 请求。与 Create 类似,它会验证输入并确保在更新时不修改 ClientId 和 ClientSecret。成功更新后,重定向到 Index 页面。
Delete:处理删除应用程序的 POST 请求。它会验证 PIN 码并查找应用程序,如果找到则删除该应用程序。
TempData,以便在表单验证失败时能够回填数据。IRepository<DpzApplication> 接口与 MongoDB 进行交互,执行 CRUD 操作。通过 SearchFor 方法进行查询,并使用 ToPagedListAsync 方法实现分页。List<string> 收集错误信息,并在验证失败时将错误信息存储在 TempData 中,以便在视图中显示。该控制器提供了一个完整的应用程序管理功能,允许用户通过 Web 界面创建、更新、删除和查询 OpenIddict 应用程序。它结合了 MongoDB 数据存储、输入验证和错误处理,确保了操作的安全性和用户体验。
