feat: add admin menu management crud
This commit is contained in:
108
src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs
Normal file
108
src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Module.Authorization.Attributes;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
|
||||
namespace TakeoutSaaS.AdminApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单管理(可增删改查)。
|
||||
/// </summary>
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize]
|
||||
[Route("api/admin/v{version:apiVersion}/menus")]
|
||||
public sealed class MenusController(IMediator mediator) : BaseApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前租户的菜单列表(平铺)。
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[PermissionAuthorize("identity:menu:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MenuDefinitionDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MenuDefinitionDto>>> List(CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询菜单列表
|
||||
var result = await mediator.Send(new ListMenusQuery(), cancellationToken);
|
||||
|
||||
// 2. 返回数据
|
||||
return ApiResponse<IReadOnlyList<MenuDefinitionDto>>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取菜单详情。
|
||||
/// </summary>
|
||||
[HttpGet("{menuId:long}")]
|
||||
[PermissionAuthorize("identity:menu:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MenuDefinitionDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<MenuDefinitionDto>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ApiResponse<MenuDefinitionDto>> Detail(long menuId, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询详情
|
||||
var result = await mediator.Send(new MenuDetailQuery { Id = menuId }, cancellationToken);
|
||||
|
||||
// 2. 返回或 404
|
||||
return result is null
|
||||
? ApiResponse<MenuDefinitionDto>.Error(StatusCodes.Status404NotFound, "菜单不存在")
|
||||
: ApiResponse<MenuDefinitionDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建菜单。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[PermissionAuthorize("identity:menu:create")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MenuDefinitionDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MenuDefinitionDto>> Create([FromBody, Required] CreateMenuCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 创建菜单
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
// 2. 返回创建结果
|
||||
return ApiResponse<MenuDefinitionDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新菜单。
|
||||
/// </summary>
|
||||
[HttpPut("{menuId:long}")]
|
||||
[PermissionAuthorize("identity:menu:update")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MenuDefinitionDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<MenuDefinitionDto>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ApiResponse<MenuDefinitionDto>> Update(
|
||||
long menuId,
|
||||
[FromBody, Required] UpdateMenuCommand command,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 绑定菜单 ID
|
||||
command = command with { Id = menuId };
|
||||
|
||||
// 2. 执行更新
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
// 3. 返回或 404
|
||||
return result is null
|
||||
? ApiResponse<MenuDefinitionDto>.Error(StatusCodes.Status404NotFound, "菜单不存在")
|
||||
: ApiResponse<MenuDefinitionDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单。
|
||||
/// </summary>
|
||||
[HttpDelete("{menuId:long}")]
|
||||
[PermissionAuthorize("identity:menu:delete")]
|
||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<bool>> Delete(long menuId, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 删除菜单
|
||||
var result = await mediator.Send(new DeleteMenuCommand { Id = menuId }, cancellationToken);
|
||||
|
||||
// 2. 返回执行结果
|
||||
return ApiResponse<bool>.Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 创建菜单命令。
|
||||
/// </summary>
|
||||
public sealed record CreateMenuCommand : IRequest<MenuDefinitionDto>
|
||||
{
|
||||
public long ParentId { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Path { get; init; } = string.Empty;
|
||||
public string Component { get; init; } = string.Empty;
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string? Icon { get; init; }
|
||||
public bool IsIframe { get; init; }
|
||||
public string? Link { get; init; }
|
||||
public bool KeepAlive { get; init; }
|
||||
public int SortOrder { get; init; }
|
||||
public IReadOnlyCollection<string> RequiredPermissions { get; init; } = [];
|
||||
public IReadOnlyCollection<string> MetaPermissions { get; init; } = [];
|
||||
public IReadOnlyCollection<string> MetaRoles { get; init; } = [];
|
||||
public IReadOnlyCollection<MenuAuthItemDto> AuthList { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单命令。
|
||||
/// </summary>
|
||||
public sealed record DeleteMenuCommand : IRequest<bool>
|
||||
{
|
||||
public long Id { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 更新菜单命令。
|
||||
/// </summary>
|
||||
public sealed record UpdateMenuCommand : IRequest<MenuDefinitionDto?>
|
||||
{
|
||||
public long Id { get; init; }
|
||||
public long ParentId { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Path { get; init; } = string.Empty;
|
||||
public string Component { get; init; } = string.Empty;
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string? Icon { get; init; }
|
||||
public bool IsIframe { get; init; }
|
||||
public string? Link { get; init; }
|
||||
public bool KeepAlive { get; init; }
|
||||
public int SortOrder { get; init; }
|
||||
public IReadOnlyCollection<string> RequiredPermissions { get; init; } = [];
|
||||
public IReadOnlyCollection<string> MetaPermissions { get; init; } = [];
|
||||
public IReadOnlyCollection<string> MetaRoles { get; init; } = [];
|
||||
public IReadOnlyCollection<MenuAuthItemDto> AuthList { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单定义 DTO。
|
||||
/// </summary>
|
||||
public sealed record MenuDefinitionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单 ID。
|
||||
/// </summary>
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 父级菜单 ID。
|
||||
/// </summary>
|
||||
public long ParentId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称(路由 name)。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路由路径。
|
||||
/// </summary>
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 组件路径。
|
||||
/// </summary>
|
||||
public string Component { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 标题。
|
||||
/// </summary>
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 图标。
|
||||
/// </summary>
|
||||
public string? Icon { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否 iframe。
|
||||
/// </summary>
|
||||
public bool IsIframe { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 链接。
|
||||
/// </summary>
|
||||
public string? Link { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否缓存。
|
||||
/// </summary>
|
||||
public bool KeepAlive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序。
|
||||
/// </summary>
|
||||
public int SortOrder { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 访问权限。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> RequiredPermissions { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Meta.permissions。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> MetaPermissions { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Meta.roles。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> MetaRoles { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 按钮权限列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MenuAuthItemDto> AuthList { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 创建菜单处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateMenuCommandHandler(
|
||||
IMenuRepository menuRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<CreateMenuCommand, MenuDefinitionDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MenuDefinitionDto> Handle(CreateMenuCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 构造实体
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var entity = new MenuDefinition
|
||||
{
|
||||
TenantId = tenantId,
|
||||
ParentId = request.ParentId,
|
||||
Name = request.Name.Trim(),
|
||||
Path = request.Path.Trim(),
|
||||
Component = request.Component.Trim(),
|
||||
Title = request.Title.Trim(),
|
||||
Icon = request.Icon?.Trim(),
|
||||
IsIframe = request.IsIframe,
|
||||
Link = string.IsNullOrWhiteSpace(request.Link) ? null : request.Link.Trim(),
|
||||
KeepAlive = request.KeepAlive,
|
||||
SortOrder = request.SortOrder,
|
||||
RequiredPermissions = MenuMapper.JoinCodes(request.RequiredPermissions),
|
||||
MetaPermissions = MenuMapper.JoinCodes(request.MetaPermissions),
|
||||
MetaRoles = MenuMapper.JoinCodes(request.MetaRoles),
|
||||
AuthListJson = request.AuthList.Count == 0
|
||||
? null
|
||||
: System.Text.Json.JsonSerializer.Serialize(request.AuthList)
|
||||
};
|
||||
|
||||
// 2. 持久化
|
||||
await menuRepository.AddAsync(entity, cancellationToken);
|
||||
await menuRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 3. 返回 DTO
|
||||
return MenuMapper.ToDto(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteMenuCommandHandler(
|
||||
IMenuRepository menuRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<DeleteMenuCommand, bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(DeleteMenuCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 删除目标及可能的孤儿由外层保证
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
await menuRepository.DeleteAsync(request.Id, tenantId, cancellationToken);
|
||||
|
||||
// 2. 持久化
|
||||
await menuRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 3. 返回执行结果
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class ListMenusQueryHandler(
|
||||
IMenuRepository menuRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ListMenusQuery, IReadOnlyList<MenuDefinitionDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<MenuDefinitionDto>> Handle(ListMenusQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
|
||||
// 2. 查询列表
|
||||
var entities = await menuRepository.GetByTenantAsync(tenantId, cancellationToken);
|
||||
|
||||
// 3. 映射 DTO
|
||||
var items = entities.Select(MenuMapper.ToDto).ToList();
|
||||
|
||||
// 4. 返回结果
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class MenuDetailQueryHandler(
|
||||
IMenuRepository menuRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<MenuDetailQuery, MenuDefinitionDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MenuDefinitionDto?> Handle(MenuDetailQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
|
||||
// 2. 查询实体
|
||||
var entity = await menuRepository.FindByIdAsync(request.Id, tenantId, cancellationToken);
|
||||
if (entity is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 映射并返回
|
||||
return MenuMapper.ToDto(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Text.Json;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单映射辅助。
|
||||
/// </summary>
|
||||
internal static class MenuMapper
|
||||
{
|
||||
public static MenuDefinitionDto ToDto(MenuDefinition entity)
|
||||
{
|
||||
// 1. 解析权限字段
|
||||
var requiredPermissions = SplitCodes(entity.RequiredPermissions);
|
||||
var metaPermissions = SplitCodes(entity.MetaPermissions);
|
||||
var metaRoles = SplitCodes(entity.MetaRoles);
|
||||
|
||||
// 2. 解析按钮权限
|
||||
var authList = string.IsNullOrWhiteSpace(entity.AuthListJson)
|
||||
? []
|
||||
: JsonSerializer.Deserialize<List<MenuAuthItemDto>>(entity.AuthListJson) ?? [];
|
||||
|
||||
// 3. 输出 DTO
|
||||
return new MenuDefinitionDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
ParentId = entity.ParentId,
|
||||
Name = entity.Name,
|
||||
Path = entity.Path,
|
||||
Component = entity.Component,
|
||||
Title = entity.Title,
|
||||
Icon = entity.Icon,
|
||||
IsIframe = entity.IsIframe,
|
||||
Link = entity.Link,
|
||||
KeepAlive = entity.KeepAlive,
|
||||
SortOrder = entity.SortOrder,
|
||||
RequiredPermissions = requiredPermissions,
|
||||
MetaPermissions = metaPermissions,
|
||||
MetaRoles = metaRoles,
|
||||
AuthList = authList
|
||||
};
|
||||
}
|
||||
|
||||
public static void FillEntity(MenuDefinition entity, MenuDefinitionDto dto)
|
||||
{
|
||||
// 1. 赋值基础字段
|
||||
entity.ParentId = dto.ParentId;
|
||||
entity.Name = dto.Name;
|
||||
entity.Path = dto.Path;
|
||||
entity.Component = dto.Component;
|
||||
entity.Title = dto.Title;
|
||||
entity.Icon = dto.Icon;
|
||||
entity.IsIframe = dto.IsIframe;
|
||||
entity.Link = dto.Link;
|
||||
entity.KeepAlive = dto.KeepAlive;
|
||||
entity.SortOrder = dto.SortOrder;
|
||||
|
||||
// 2. 权限与按钮
|
||||
entity.RequiredPermissions = JoinCodes(dto.RequiredPermissions);
|
||||
entity.MetaPermissions = JoinCodes(dto.MetaPermissions);
|
||||
entity.MetaRoles = JoinCodes(dto.MetaRoles);
|
||||
entity.AuthListJson = dto.AuthList.Count == 0
|
||||
? null
|
||||
: JsonSerializer.Serialize(dto.AuthList);
|
||||
}
|
||||
|
||||
public static MenuDefinitionDto FromCommand(MenuDefinition? existing, long tenantId, string name, MenuDefinitionDto payload)
|
||||
{
|
||||
// 1. 构造实体
|
||||
var entity = existing ?? new MenuDefinition
|
||||
{
|
||||
TenantId = tenantId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// // 填充字段
|
||||
FillEntity(entity, payload);
|
||||
|
||||
// 2. 返回 DTO 映射
|
||||
return ToDto(entity);
|
||||
}
|
||||
|
||||
public static string JoinCodes(IEnumerable<string> codes)
|
||||
{
|
||||
return string.Join(',', codes.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public static string[] SplitCodes(string? codes)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(codes))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return codes
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新菜单处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateMenuCommandHandler(
|
||||
IMenuRepository menuRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<UpdateMenuCommand, MenuDefinitionDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MenuDefinitionDto?> Handle(UpdateMenuCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验存在
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var entity = await menuRepository.FindByIdAsync(request.Id, tenantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "菜单不存在");
|
||||
|
||||
// 2. 更新字段
|
||||
entity.ParentId = request.ParentId;
|
||||
entity.Name = request.Name.Trim();
|
||||
entity.Path = request.Path.Trim();
|
||||
entity.Component = request.Component.Trim();
|
||||
entity.Title = request.Title.Trim();
|
||||
entity.Icon = request.Icon?.Trim();
|
||||
entity.IsIframe = request.IsIframe;
|
||||
entity.Link = string.IsNullOrWhiteSpace(request.Link) ? null : request.Link.Trim();
|
||||
entity.KeepAlive = request.KeepAlive;
|
||||
entity.SortOrder = request.SortOrder;
|
||||
entity.RequiredPermissions = MenuMapper.JoinCodes(request.RequiredPermissions);
|
||||
entity.MetaPermissions = MenuMapper.JoinCodes(request.MetaPermissions);
|
||||
entity.MetaRoles = MenuMapper.JoinCodes(request.MetaRoles);
|
||||
entity.AuthListJson = request.AuthList.Count == 0
|
||||
? null
|
||||
: System.Text.Json.JsonSerializer.Serialize(request.AuthList);
|
||||
|
||||
// 3. 持久化
|
||||
await menuRepository.UpdateAsync(entity, cancellationToken);
|
||||
await menuRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 4. 返回 DTO
|
||||
return MenuMapper.ToDto(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询菜单列表(当前租户)。
|
||||
/// </summary>
|
||||
public sealed class ListMenusQuery : IRequest<IReadOnlyList<MenuDefinitionDto>>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单详情查询。
|
||||
/// </summary>
|
||||
public sealed class MenuDetailQuery : IRequest<MenuDefinitionDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单 ID。
|
||||
/// </summary>
|
||||
public long Id { get; init; }
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public sealed class AdminAuthService(
|
||||
IRoleRepository roleRepository,
|
||||
IPermissionRepository permissionRepository,
|
||||
IRolePermissionRepository rolePermissionRepository,
|
||||
IMenuRepository menuRepository,
|
||||
IPasswordHasher<IdentityUser> passwordHasher,
|
||||
IJwtTokenService jwtTokenService,
|
||||
IRefreshTokenStore refreshTokenStore,
|
||||
@@ -29,6 +30,7 @@ public sealed class AdminAuthService(
|
||||
private readonly IRoleRepository _roleRepository = roleRepository;
|
||||
private readonly IPermissionRepository _permissionRepository = permissionRepository;
|
||||
private readonly IRolePermissionRepository _rolePermissionRepository = rolePermissionRepository;
|
||||
private readonly IMenuRepository _menuRepository = menuRepository;
|
||||
|
||||
/// <summary>
|
||||
/// 管理后台登录:验证账号密码并生成令牌。
|
||||
@@ -108,8 +110,12 @@ public sealed class AdminAuthService(
|
||||
{
|
||||
// 1. 读取档案以获取权限
|
||||
var profile = await GetProfileAsync(userId, cancellationToken);
|
||||
// 2. 生成菜单树
|
||||
var menu = AdminMenuProvider.BuildMenuTree(profile.Permissions);
|
||||
// 2. 读取菜单定义
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var definitions = await _menuRepository.GetByTenantAsync(tenantId, cancellationToken);
|
||||
|
||||
// 3. 生成菜单树
|
||||
var menu = BuildMenuTree(definitions, profile.Permissions);
|
||||
return menu;
|
||||
}
|
||||
|
||||
@@ -208,6 +214,103 @@ public sealed class AdminAuthService(
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<MenuNodeDto> BuildMenuTree(
|
||||
IReadOnlyList<Domain.Identity.Entities.MenuDefinition> definitions,
|
||||
IReadOnlyList<string> permissions)
|
||||
{
|
||||
// 1. 权限集合
|
||||
var permissionSet = new HashSet<string>(permissions ?? [], StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 2. 父子分组
|
||||
var parentLookup = definitions
|
||||
.GroupBy(x => x.ParentId)
|
||||
.ToDictionary(g => g.Key, g => g.OrderBy(d => d.SortOrder).ToList());
|
||||
|
||||
// 3. 递归构造并过滤
|
||||
IReadOnlyList<MenuNodeDto> BuildChildren(long parentId)
|
||||
{
|
||||
if (!parentLookup.TryGetValue(parentId, out var children))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// 1. 收集可见节点
|
||||
var result = new List<MenuNodeDto>();
|
||||
foreach (var def in children)
|
||||
{
|
||||
// 1.1 构造节点
|
||||
var node = MapMenuNode(def);
|
||||
|
||||
// 1.2 递归子节点
|
||||
var sub = BuildChildren(def.Id);
|
||||
|
||||
// 1.3 可见性
|
||||
var required = node.RequiredPermissions ?? [];
|
||||
var visible = required.Length == 0 || required.Any(permissionSet.Contains);
|
||||
|
||||
// 1.4 收集
|
||||
if (visible || sub.Count > 0)
|
||||
{
|
||||
result.Add(node with { Children = sub });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 返回本层节点
|
||||
return result;
|
||||
}
|
||||
|
||||
// 4. 返回根节点
|
||||
return BuildChildren(0);
|
||||
}
|
||||
|
||||
private static MenuNodeDto MapMenuNode(Domain.Identity.Entities.MenuDefinition definition)
|
||||
{
|
||||
// 1. 解析权限
|
||||
var requiredPermissions = SplitCodes(definition.RequiredPermissions);
|
||||
var metaPermissions = SplitCodes(definition.MetaPermissions);
|
||||
var metaRoles = SplitCodes(definition.MetaRoles);
|
||||
|
||||
// 2. 解析按钮权限
|
||||
var authList = string.IsNullOrWhiteSpace(definition.AuthListJson)
|
||||
? []
|
||||
: System.Text.Json.JsonSerializer.Deserialize<List<MenuAuthItemDto>>(definition.AuthListJson) ?? [];
|
||||
|
||||
// 3. 构造节点
|
||||
return new MenuNodeDto
|
||||
{
|
||||
Name = definition.Name,
|
||||
Path = definition.Path,
|
||||
Component = definition.Component,
|
||||
Meta = new MenuMetaDto
|
||||
{
|
||||
Title = definition.Title,
|
||||
Icon = definition.Icon,
|
||||
KeepAlive = definition.KeepAlive,
|
||||
IsIframe = definition.IsIframe,
|
||||
Link = definition.Link,
|
||||
Roles = metaRoles,
|
||||
Permissions = metaPermissions,
|
||||
AuthList = authList
|
||||
},
|
||||
Children = [],
|
||||
RequiredPermissions = requiredPermissions
|
||||
};
|
||||
}
|
||||
|
||||
private static string[] SplitCodes(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// 1. 拆分去重
|
||||
return value
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private async Task<string[]> ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Identity.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 管理端菜单定义。
|
||||
/// </summary>
|
||||
public sealed class MenuDefinition : MultiTenantEntityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 父级菜单 ID,根节点为 0。
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单名称(前端路由 name)。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路由路径。
|
||||
/// </summary>
|
||||
public string Path { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 组件路径(不含 .vue)。
|
||||
/// </summary>
|
||||
public string Component { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 标题。
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 图标标识。
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否 iframe。
|
||||
/// </summary>
|
||||
public bool IsIframe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外链或 iframe 地址。
|
||||
/// </summary>
|
||||
public string? Link { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否缓存。
|
||||
/// </summary>
|
||||
public bool KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序。
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 访问该菜单所需的权限集合(逗号分隔)。
|
||||
/// </summary>
|
||||
public string RequiredPermissions { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Meta.permissions(逗号分隔)。
|
||||
/// </summary>
|
||||
public string MetaPermissions { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Meta.roles(逗号分隔)。
|
||||
/// </summary>
|
||||
public string MetaRoles { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。
|
||||
/// </summary>
|
||||
public string? AuthListJson { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Identity.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单仓储。
|
||||
/// </summary>
|
||||
public interface IMenuRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 按租户获取菜单列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<MenuDefinition>> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ID 查询菜单。
|
||||
/// </summary>
|
||||
Task<MenuDefinition?> FindByIdAsync(long id, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增菜单。
|
||||
/// </summary>
|
||||
Task AddAsync(MenuDefinition menu, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新菜单。
|
||||
/// </summary>
|
||||
Task UpdateAsync(MenuDefinition menu, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单。
|
||||
/// </summary>
|
||||
Task DeleteAsync(long id, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Infrastructure.Common.Extensions;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Options;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Repositories;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Services;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using DomainIdentityUser = TakeoutSaaS.Domain.Identity.Entities.IdentityUser;
|
||||
@@ -53,6 +54,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<IUserRoleRepository, EfUserRoleRepository>();
|
||||
services.AddScoped<IRolePermissionRepository, EfRolePermissionRepository>();
|
||||
services.AddScoped<IRoleTemplateRepository, EfRoleTemplateRepository>();
|
||||
services.AddScoped<IMenuRepository, EfMenuRepository>();
|
||||
services.AddScoped<IJwtTokenService, JwtTokenService>();
|
||||
services.AddScoped<IRefreshTokenStore, RedisRefreshTokenStore>();
|
||||
services.AddScoped<ILoginRateLimiter, RedisLoginRateLimiter>();
|
||||
|
||||
@@ -58,6 +58,11 @@ public sealed class IdentityDbContext(
|
||||
/// </summary>
|
||||
public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
|
||||
|
||||
/// <summary>
|
||||
/// 菜单定义集合。
|
||||
/// </summary>
|
||||
public DbSet<MenuDefinition> MenuDefinitions => Set<MenuDefinition>();
|
||||
|
||||
/// <summary>
|
||||
/// 配置实体模型。
|
||||
/// </summary>
|
||||
@@ -73,6 +78,7 @@ public sealed class IdentityDbContext(
|
||||
ConfigurePermission(modelBuilder.Entity<Permission>());
|
||||
ConfigureUserRole(modelBuilder.Entity<UserRole>());
|
||||
ConfigureRolePermission(modelBuilder.Entity<RolePermission>());
|
||||
ConfigureMenuDefinition(modelBuilder.Entity<MenuDefinition>());
|
||||
ApplyTenantQueryFilters(modelBuilder);
|
||||
}
|
||||
|
||||
@@ -191,4 +197,26 @@ public sealed class IdentityDbContext(
|
||||
builder.HasIndex(x => x.TenantId);
|
||||
builder.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMenuDefinition(EntityTypeBuilder<MenuDefinition> builder)
|
||||
{
|
||||
builder.ToTable("menu_definitions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.TenantId).IsRequired();
|
||||
builder.Property(x => x.ParentId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Path).HasMaxLength(256).IsRequired();
|
||||
builder.Property(x => x.Component).HasMaxLength(256).IsRequired();
|
||||
builder.Property(x => x.Title).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Icon).HasMaxLength(64);
|
||||
builder.Property(x => x.Link).HasMaxLength(512);
|
||||
builder.Property(x => x.SortOrder).IsRequired();
|
||||
builder.Property(x => x.RequiredPermissions).HasMaxLength(1024);
|
||||
builder.Property(x => x.MetaPermissions).HasMaxLength(1024);
|
||||
builder.Property(x => x.MetaRoles).HasMaxLength(1024);
|
||||
builder.Property(x => x.AuthListJson).HasColumnType("text");
|
||||
ConfigureAuditableEntity(builder);
|
||||
ConfigureSoftDeleteEntity(builder);
|
||||
builder.HasIndex(x => new { x.TenantId, x.ParentId, x.SortOrder });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Identity.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单仓储 EF 实现。
|
||||
/// </summary>
|
||||
public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuRepository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<MenuDefinition>> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId)
|
||||
.OrderBy(x => x.ParentId)
|
||||
.ThenBy(x => x.SortOrder)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<MenuDefinition>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<MenuDefinition?> FindByIdAsync(long id, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task AddAsync(MenuDefinition menu, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dbContext.MenuDefinitions.AddAsync(menu, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UpdateAsync(MenuDefinition menu, CancellationToken cancellationToken = default)
|
||||
{
|
||||
dbContext.MenuDefinitions.Update(menu);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteAsync(long id, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询目标
|
||||
var entity = await dbContext.MenuDefinitions
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
// 2. 存在则删除
|
||||
if (entity is not null)
|
||||
{
|
||||
dbContext.MenuDefinitions.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||
{
|
||||
[DbContext(typeof(IdentityDbContext))]
|
||||
[Migration("20251205131436_AddMenuDefinitions")]
|
||||
partial class AddMenuDefinitions
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Account")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("登录账号。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("展示名称。");
|
||||
|
||||
b.Property<long?>("MerchantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属商户(平台管理员为空)。");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("密码哈希。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Account")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("identity_users", null, t =>
|
||||
{
|
||||
t.HasComment("管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MenuDefinition", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AuthListJson")
|
||||
.HasColumnType("text")
|
||||
.HasComment("按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。");
|
||||
|
||||
b.Property<string>("Component")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("组件路径(不含 .vue)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("图标标识。");
|
||||
|
||||
b.Property<bool>("IsIframe")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否 iframe。");
|
||||
|
||||
b.Property<bool>("KeepAlive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否缓存。");
|
||||
|
||||
b.Property<string>("Link")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("外链或 iframe 地址。");
|
||||
|
||||
b.Property<string>("MetaPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.permissions(逗号分隔)。");
|
||||
|
||||
b.Property<string>("MetaRoles")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.roles(逗号分隔)。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("菜单名称(前端路由 name)。");
|
||||
|
||||
b.Property<long>("ParentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("父级菜单 ID,根节点为 0。");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("路由路径。");
|
||||
|
||||
b.Property<string>("RequiredPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("访问该菜单所需的权限集合(逗号分隔)。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("排序。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("标题。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId", "ParentId", "SortOrder");
|
||||
|
||||
b.ToTable("menu_definitions", null, t =>
|
||||
{
|
||||
t.HasComment("管理端菜单定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("昵称。");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 OpenId。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 UnionId,可能为空。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "OpenId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("mini_users", null, t =>
|
||||
{
|
||||
t.HasComment("小程序用户实体。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.Permission", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("权限编码(租户内唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("描述。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("权限名称。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("permissions", null, t =>
|
||||
{
|
||||
t.HasComment("权限定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.Role", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("角色编码(租户内唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("描述。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("角色名称。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("roles", null, t =>
|
||||
{
|
||||
t.HasComment("角色定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RolePermission", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<long>("PermissionId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("权限 ID。");
|
||||
|
||||
b.Property<long>("RoleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("角色 ID。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_permissions", null, t =>
|
||||
{
|
||||
t.HasComment("角色-权限关系。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RoleTemplate", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("模板描述。");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("模板名称。");
|
||||
|
||||
b.Property<string>("TemplateCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("模板编码(唯一)。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateCode")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_templates", null, t =>
|
||||
{
|
||||
t.HasComment("角色模板定义(平台级)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RoleTemplatePermission", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("PermissionCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("权限编码。");
|
||||
|
||||
b.Property<long>("RoleTemplateId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("模板 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleTemplateId", "PermissionCode")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_template_permissions", null, t =>
|
||||
{
|
||||
t.HasComment("角色模板-权限关系(平台级)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.UserRole", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<long>("RoleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("角色 ID。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("用户 ID。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_roles", null, t =>
|
||||
{
|
||||
t.HasComment("用户-角色关系。");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuDefinitions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "menu_definitions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ParentId = table.Column<long>(type: "bigint", nullable: false, comment: "父级菜单 ID,根节点为 0。"),
|
||||
Name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "菜单名称(前端路由 name)。"),
|
||||
Path = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false, comment: "路由路径。"),
|
||||
Component = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false, comment: "组件路径(不含 .vue)。"),
|
||||
Title = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false, comment: "标题。"),
|
||||
Icon = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true, comment: "图标标识。"),
|
||||
IsIframe = table.Column<bool>(type: "boolean", nullable: false, comment: "是否 iframe。"),
|
||||
Link = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true, comment: "外链或 iframe 地址。"),
|
||||
KeepAlive = table.Column<bool>(type: "boolean", nullable: false, comment: "是否缓存。"),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false, comment: "排序。"),
|
||||
RequiredPermissions = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false, comment: "访问该菜单所需的权限集合(逗号分隔)。"),
|
||||
MetaPermissions = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false, comment: "Meta.permissions(逗号分隔)。"),
|
||||
MetaRoles = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false, comment: "Meta.roles(逗号分隔)。"),
|
||||
AuthListJson = table.Column<string>(type: "text", nullable: true, comment: "按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。"),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间(UTC)。"),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间(UTC),从未更新时为 null。"),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间(UTC),未删除时为 null。"),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
|
||||
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
|
||||
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
|
||||
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_menu_definitions", x => x.Id);
|
||||
},
|
||||
comment: "管理端菜单定义。");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_menu_definitions_TenantId_ParentId_SortOrder",
|
||||
table: "menu_definitions",
|
||||
columns: new[] { "TenantId", "ParentId", "SortOrder" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "menu_definitions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,125 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MenuDefinition", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AuthListJson")
|
||||
.HasColumnType("text")
|
||||
.HasComment("按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。");
|
||||
|
||||
b.Property<string>("Component")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("组件路径(不含 .vue)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("图标标识。");
|
||||
|
||||
b.Property<bool>("IsIframe")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否 iframe。");
|
||||
|
||||
b.Property<bool>("KeepAlive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否缓存。");
|
||||
|
||||
b.Property<string>("Link")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("外链或 iframe 地址。");
|
||||
|
||||
b.Property<string>("MetaPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.permissions(逗号分隔)。");
|
||||
|
||||
b.Property<string>("MetaRoles")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.roles(逗号分隔)。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("菜单名称(前端路由 name)。");
|
||||
|
||||
b.Property<long>("ParentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("父级菜单 ID,根节点为 0。");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("路由路径。");
|
||||
|
||||
b.Property<string>("RequiredPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("访问该菜单所需的权限集合(逗号分隔)。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("排序。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("标题。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId", "ParentId", "SortOrder");
|
||||
|
||||
b.ToTable("menu_definitions", null, t =>
|
||||
{
|
||||
t.HasComment("管理端菜单定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
|
||||
Reference in New Issue
Block a user