37 changed files with 1745 additions and 312 deletions
@ -0,0 +1,18 @@ |
|||
using MediatR; |
|||
using CellularManagement.Application.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限命令
|
|||
/// </summary>
|
|||
public sealed record CreatePermissionCommand( |
|||
/// <summary>
|
|||
/// 权限名称
|
|||
/// </summary>
|
|||
string Name, |
|||
|
|||
/// <summary>
|
|||
/// 权限描述
|
|||
/// </summary>
|
|||
string? Description) : IRequest<OperationResult<CreatePermissionResponse>>; |
@ -0,0 +1,62 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
|
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限命令处理器
|
|||
/// </summary>
|
|||
public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermissionCommand, OperationResult<CreatePermissionResponse>> |
|||
{ |
|||
private readonly IPermissionRepository _permissionRepository; |
|||
private readonly ILogger<CreatePermissionCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public CreatePermissionCommandHandler( |
|||
IPermissionRepository permissionRepository, |
|||
ILogger<CreatePermissionCommandHandler> logger) |
|||
{ |
|||
_permissionRepository = permissionRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理创建权限请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<CreatePermissionResponse>> Handle( |
|||
CreatePermissionCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 检查权限是否已存在
|
|||
var existingPermission = await _permissionRepository.GetByNameAsync(request.Name, cancellationToken); |
|||
if (existingPermission != null) |
|||
{ |
|||
_logger.LogWarning("权限 {PermissionName} 已存在", request.Name); |
|||
return OperationResult<CreatePermissionResponse>.CreateFailure("权限已存在"); |
|||
} |
|||
|
|||
// 创建权限
|
|||
var permission = Permission.Create(request.Name, request.Description); |
|||
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken); |
|||
|
|||
_logger.LogInformation("权限 {PermissionName} 创建成功", request.Name); |
|||
|
|||
return OperationResult<CreatePermissionResponse>.CreateSuccess( |
|||
new CreatePermissionResponse(createdPermission.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建权限 {PermissionName} 失败", request.Name); |
|||
return OperationResult<CreatePermissionResponse>.CreateFailure("创建权限失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限响应
|
|||
/// </summary>
|
|||
public sealed record CreatePermissionResponse( |
|||
/// <summary>
|
|||
/// 权限ID
|
|||
/// </summary>
|
|||
int PermissionId); |
@ -0,0 +1,59 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Domain.Entities; |
|||
|
|||
/// <summary>
|
|||
/// 权限实体
|
|||
/// </summary>
|
|||
public class Permission |
|||
{ |
|||
/// <summary>
|
|||
/// 权限ID
|
|||
/// </summary>
|
|||
public int Id { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限名称
|
|||
/// </summary>
|
|||
public string Name { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限描述
|
|||
/// </summary>
|
|||
public string? Description { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreatedAt { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 角色权限关联
|
|||
/// </summary>
|
|||
public ICollection<RolePermission> RolePermissions { get; private set; } |
|||
|
|||
private Permission() { } |
|||
|
|||
/// <summary>
|
|||
/// 创建权限
|
|||
/// </summary>
|
|||
public static Permission Create(string name, string? description = null) |
|||
{ |
|||
return new Permission |
|||
{ |
|||
Name = name, |
|||
Description = description, |
|||
CreatedAt = DateTime.UtcNow, |
|||
RolePermissions = new List<RolePermission>() |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 更新权限信息
|
|||
/// </summary>
|
|||
public void Update(string name, string? description = null) |
|||
{ |
|||
Name = name; |
|||
Description = description; |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Domain.Entities; |
|||
|
|||
/// <summary>
|
|||
/// 角色权限关联实体
|
|||
/// </summary>
|
|||
public class RolePermission |
|||
{ |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
public int RoleId { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限ID
|
|||
/// </summary>
|
|||
public int PermissionId { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreatedAt { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 角色
|
|||
/// </summary>
|
|||
public AppRole Role { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限
|
|||
/// </summary>
|
|||
public Permission Permission { get; private set; } |
|||
|
|||
private RolePermission() { } |
|||
|
|||
/// <summary>
|
|||
/// 创建角色权限关联
|
|||
/// </summary>
|
|||
public static RolePermission Create(int roleId, int permissionId) |
|||
{ |
|||
return new RolePermission |
|||
{ |
|||
RoleId = roleId, |
|||
PermissionId = permissionId, |
|||
CreatedAt = DateTime.UtcNow |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using CellularManagement.Domain.Entities; |
|||
|
|||
namespace CellularManagement.Domain.Repositories; |
|||
|
|||
/// <summary>
|
|||
/// 权限命令仓储接口
|
|||
/// 负责权限的写入操作
|
|||
/// </summary>
|
|||
public interface IPermissionCommandRepository : ICommandRepository<Permission> |
|||
{ |
|||
/// <summary>
|
|||
/// 添加角色权限
|
|||
/// </summary>
|
|||
Task AddRolePermissionAsync(RolePermission rolePermission, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限
|
|||
/// </summary>
|
|||
Task DeleteRolePermissionAsync(int roleId, int permissionId, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 权限查询仓储接口
|
|||
/// 负责权限的读取操作
|
|||
/// </summary>
|
|||
public interface IPermissionQueryRepository : IQueryRepository<Permission> |
|||
{ |
|||
/// <summary>
|
|||
/// 根据名称获取权限
|
|||
/// </summary>
|
|||
Task<Permission?> GetByNameAsync(string name, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 获取角色的所有权限
|
|||
/// </summary>
|
|||
Task<IEnumerable<Permission>> GetRolePermissionsAsync(int roleId, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 权限仓储接口
|
|||
/// 组合命令和查询仓储接口
|
|||
/// </summary>
|
|||
public interface IPermissionRepository : IPermissionCommandRepository, IPermissionQueryRepository |
|||
{ |
|||
/// <summary>
|
|||
/// 获取所有权限
|
|||
/// </summary>
|
|||
Task<IEnumerable<Permission>> GetAllAsync(CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取权限
|
|||
/// </summary>
|
|||
Task<Permission?> GetByIdAsync(int id, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 更新权限
|
|||
/// </summary>
|
|||
Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 删除权限
|
|||
/// </summary>
|
|||
Task DeleteAsync(Permission permission, CancellationToken cancellationToken = default); |
|||
} |
@ -1,25 +1,68 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using CellularManagement.Domain.Entities; |
|||
|
|||
namespace CellularManagement.Domain.Repositories; |
|||
|
|||
/// <summary>
|
|||
/// 用户角色仓储接口
|
|||
/// 用户角色命令仓储接口
|
|||
/// 负责用户角色关系的写入操作
|
|||
/// </summary>
|
|||
public interface IUserRoleRepository |
|||
public interface IUserRoleCommandRepository : ICommandRepository<UserRole> |
|||
{ |
|||
/// <summary>
|
|||
/// 添加用户角色关系
|
|||
/// </summary>
|
|||
/// <param name="userRole">用户角色关系实体</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>添加的用户角色关系实体</returns>
|
|||
Task<UserRole> AddAsync(UserRole userRole, CancellationToken cancellationToken = default); |
|||
Task<UserRole> AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 删除用户角色关系
|
|||
/// </summary>
|
|||
Task DeleteUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 批量添加用户角色关系
|
|||
/// </summary>
|
|||
Task AddUserRolesAsync(IEnumerable<UserRole> userRoles, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 获取用户角色
|
|||
/// 批量删除用户角色关系
|
|||
/// </summary>
|
|||
Task DeleteUserRolesAsync(string userId, IEnumerable<string> roleIds, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 用户角色查询仓储接口
|
|||
/// 负责用户角色关系的读取操作
|
|||
/// </summary>
|
|||
public interface IUserRoleQueryRepository : IQueryRepository<UserRole> |
|||
{ |
|||
/// <summary>
|
|||
/// 获取用户的所有角色
|
|||
/// </summary>
|
|||
/// <param name="userId">用户ID</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>用户角色列表</returns>
|
|||
Task<IList<string>> GetUserRolesAsync(string userId, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 获取角色的所有用户
|
|||
/// </summary>
|
|||
Task<IList<string>> GetRoleUsersAsync(string roleId, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 检查用户是否拥有指定角色
|
|||
/// </summary>
|
|||
Task<bool> HasRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default); |
|||
|
|||
/// <summary>
|
|||
/// 获取用户角色关系
|
|||
/// </summary>
|
|||
Task<UserRole?> GetUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 用户角色仓储接口
|
|||
/// 组合命令和查询仓储接口
|
|||
/// </summary>
|
|||
public interface IUserRoleRepository : IUserRoleCommandRepository, IUserRoleQueryRepository |
|||
{ |
|||
} |
@ -0,0 +1,201 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Infrastructure.Context; |
|||
using System.Linq.Expressions; |
|||
|
|||
namespace CellularManagement.Infrastructure.Repositories; |
|||
|
|||
/// <summary>
|
|||
/// 权限仓储实现类
|
|||
/// </summary>
|
|||
public class PermissionRepository : |
|||
CommandRepository<Permission>, |
|||
IQueryRepository<Permission>, |
|||
IPermissionRepository |
|||
{ |
|||
private readonly AppDbContext _context; |
|||
private readonly ILogger<PermissionRepository> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化权限仓储
|
|||
/// </summary>
|
|||
public PermissionRepository( |
|||
AppDbContext context, |
|||
IUnitOfWork unitOfWork, |
|||
ILogger<PermissionRepository> logger) |
|||
: base(context, unitOfWork, logger) |
|||
{ |
|||
_context = context; |
|||
_logger = logger; |
|||
} |
|||
|
|||
#region IQueryRepository<Permission> 实现
|
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取权限
|
|||
/// </summary>
|
|||
public async Task<Permission?> GetByIdAsync(string id, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.FindAsync(new object[] { id }, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有权限
|
|||
/// </summary>
|
|||
public async Task<IEnumerable<Permission>> GetAllAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据条件查询权限
|
|||
/// </summary>
|
|||
public async Task<IEnumerable<Permission>> FindAsync(Expression<Func<Permission, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.Where(predicate).ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 分页查询权限
|
|||
/// </summary>
|
|||
public async Task<(int TotalCount, IEnumerable<Permission> Items)> GetPagedAsync(int pageNumber, int pageSize, CancellationToken cancellationToken = default) |
|||
{ |
|||
var totalCount = await _context.Permissions.CountAsync(cancellationToken); |
|||
var items = await _context.Permissions |
|||
.Skip((pageNumber - 1) * pageSize) |
|||
.Take(pageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
return (totalCount, items); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据条件分页查询权限
|
|||
/// </summary>
|
|||
public async Task<(int TotalCount, IEnumerable<Permission> Items)> GetPagedAsync( |
|||
Expression<Func<Permission, bool>> predicate, |
|||
int pageNumber, |
|||
int pageSize, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var query = _context.Permissions.Where(predicate); |
|||
var totalCount = await query.CountAsync(cancellationToken); |
|||
var items = await query |
|||
.Skip((pageNumber - 1) * pageSize) |
|||
.Take(pageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
return (totalCount, items); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取第一个符合条件的权限
|
|||
/// </summary>
|
|||
public async Task<Permission?> FirstOrDefaultAsync(Expression<Func<Permission, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.FirstOrDefaultAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查是否存在符合条件的权限
|
|||
/// </summary>
|
|||
public async Task<bool> AnyAsync(Expression<Func<Permission, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.AnyAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 统计符合条件的权限数量
|
|||
/// </summary>
|
|||
public async Task<int> CountAsync(Expression<Func<Permission, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.CountAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region IPermissionQueryRepository 实现
|
|||
|
|||
/// <summary>
|
|||
/// 根据名称获取权限
|
|||
/// </summary>
|
|||
public async Task<Permission?> GetByNameAsync(string name, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions |
|||
.FirstOrDefaultAsync(p => p.Name == name, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取角色的所有权限
|
|||
/// </summary>
|
|||
public async Task<IEnumerable<Permission>> GetRolePermissionsAsync(int roleId, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.RolePermissions |
|||
.Where(rp => rp.RoleId == roleId) |
|||
.Select(rp => rp.Permission) |
|||
.ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region IPermissionCommandRepository 实现
|
|||
|
|||
/// <summary>
|
|||
/// 添加角色权限
|
|||
/// </summary>
|
|||
public async Task AddRolePermissionAsync(RolePermission rolePermission, CancellationToken cancellationToken = default) |
|||
{ |
|||
await _context.RolePermissions.AddAsync(rolePermission, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限
|
|||
/// </summary>
|
|||
public async Task DeleteRolePermissionAsync(int roleId, int permissionId, CancellationToken cancellationToken = default) |
|||
{ |
|||
var rolePermission = await _context.RolePermissions |
|||
.FirstOrDefaultAsync(rp => rp.RoleId == roleId && rp.PermissionId == permissionId, cancellationToken); |
|||
|
|||
if (rolePermission != null) |
|||
{ |
|||
_context.RolePermissions.Remove(rolePermission); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region IPermissionRepository 实现
|
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取权限
|
|||
/// </summary>
|
|||
public async Task<Permission?> GetByIdAsync(int id, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.Permissions.FindAsync(new object[] { id }, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 更新权限
|
|||
/// </summary>
|
|||
public async Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default) |
|||
{ |
|||
_context.Permissions.Update(permission); |
|||
await Task.CompletedTask; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除权限
|
|||
/// </summary>
|
|||
public async Task DeleteAsync(Permission permission, CancellationToken cancellationToken = default) |
|||
{ |
|||
_context.Permissions.Remove(permission); |
|||
await Task.CompletedTask; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
@ -1,68 +1,216 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Infrastructure.Context; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Infrastructure.Repositories; |
|||
|
|||
/// <summary>
|
|||
/// 用户角色仓储实现类
|
|||
/// </summary>
|
|||
public class UserRoleRepository : IUserRoleRepository |
|||
public class UserRoleRepository : |
|||
CommandRepository<UserRole>, |
|||
IQueryRepository<UserRole>, |
|||
IUserRoleRepository |
|||
{ |
|||
private readonly AppDbContext _context; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
private readonly ILogger<UserRoleRepository> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化仓储
|
|||
/// </summary>
|
|||
/// <param name="context">数据库上下文</param>
|
|||
/// <param name="unitOfWork">工作单元</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public UserRoleRepository( |
|||
AppDbContext context, |
|||
IUnitOfWork unitOfWork, |
|||
ILogger<UserRoleRepository> logger) |
|||
ILogger<UserRoleRepository> logger) |
|||
: base(context, unitOfWork, logger) |
|||
{ |
|||
_context = context; |
|||
_unitOfWork = unitOfWork; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public async Task<UserRole> AddAsync(UserRole userRole, CancellationToken cancellationToken = default) |
|||
#region IUserRoleCommandRepository 实现
|
|||
|
|||
/// <summary>
|
|||
/// 添加用户角色关系
|
|||
/// </summary>
|
|||
public async Task<UserRole> AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) |
|||
{ |
|||
await _context.UserRoles.AddAsync(userRole, cancellationToken); |
|||
return userRole; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除用户角色关系
|
|||
/// </summary>
|
|||
public async Task DeleteUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default) |
|||
{ |
|||
try |
|||
var userRole = await _context.UserRoles |
|||
.FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); |
|||
|
|||
if (userRole != null) |
|||
{ |
|||
await _context.UserRoles.AddAsync(userRole, cancellationToken); |
|||
await _unitOfWork.SaveChangesAsync(cancellationToken); |
|||
return userRole; |
|||
_context.UserRoles.Remove(userRole); |
|||
} |
|||
catch (Exception ex) |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量添加用户角色关系
|
|||
/// </summary>
|
|||
public async Task AddUserRolesAsync(IEnumerable<UserRole> userRoles, CancellationToken cancellationToken = default) |
|||
{ |
|||
await _context.UserRoles.AddRangeAsync(userRoles, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量删除用户角色关系
|
|||
/// </summary>
|
|||
public async Task DeleteUserRolesAsync(string userId, IEnumerable<string> roleIds, CancellationToken cancellationToken = default) |
|||
{ |
|||
var userRoles = await _context.UserRoles |
|||
.Where(ur => ur.UserId == userId && roleIds.Contains(ur.RoleId)) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
if (userRoles.Any()) |
|||
{ |
|||
_logger.LogError(ex, "添加用户角色关系时发生错误,用户ID:{UserId},角色ID:{RoleId}", |
|||
userRole.UserId, userRole.RoleId); |
|||
throw; |
|||
_context.UserRoles.RemoveRange(userRoles); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
#endregion
|
|||
|
|||
#region IUserRoleQueryRepository 实现
|
|||
|
|||
/// <summary>
|
|||
/// 获取用户的所有角色
|
|||
/// </summary>
|
|||
public async Task<IList<string>> GetUserRolesAsync(string userId, CancellationToken cancellationToken = default) |
|||
{ |
|||
try |
|||
{ |
|||
return await _context.UserRoles |
|||
.Include(ur => ur.Role) |
|||
.Where(ur => ur.UserId == userId) |
|||
.Select(ur => ur.Role.Name) |
|||
.ToListAsync(cancellationToken); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "获取用户角色时发生错误,用户ID:{UserId}", userId); |
|||
throw; |
|||
} |
|||
return await _context.UserRoles |
|||
.Include(ur => ur.Role) |
|||
.Where(ur => ur.UserId == userId) |
|||
.Select(ur => ur.Role.Name) |
|||
.ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取角色的所有用户
|
|||
/// </summary>
|
|||
public async Task<IList<string>> GetRoleUsersAsync(string roleId, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles |
|||
.Include(ur => ur.User) |
|||
.Where(ur => ur.RoleId == roleId) |
|||
.Select(ur => ur.UserId) |
|||
.ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查用户是否拥有指定角色
|
|||
/// </summary>
|
|||
public async Task<bool> HasRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles |
|||
.AnyAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取用户角色关系
|
|||
/// </summary>
|
|||
public async Task<UserRole?> GetUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles |
|||
.FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region IQueryRepository<UserRole> 实现
|
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取用户角色关系
|
|||
/// </summary>
|
|||
public async Task<UserRole?> GetByIdAsync(string id, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.FindAsync(new object[] { id }, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有用户角色关系
|
|||
/// </summary>
|
|||
public async Task<IEnumerable<UserRole>> GetAllAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据条件查询用户角色关系
|
|||
/// </summary>
|
|||
public async Task<IEnumerable<UserRole>> FindAsync(System.Linq.Expressions.Expression<Func<UserRole, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.Where(predicate).ToListAsync(cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 分页查询用户角色关系
|
|||
/// </summary>
|
|||
public async Task<(int TotalCount, IEnumerable<UserRole> Items)> GetPagedAsync(int pageNumber, int pageSize, CancellationToken cancellationToken = default) |
|||
{ |
|||
var totalCount = await _context.UserRoles.CountAsync(cancellationToken); |
|||
var items = await _context.UserRoles |
|||
.Skip((pageNumber - 1) * pageSize) |
|||
.Take(pageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
return (totalCount, items); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据条件分页查询用户角色关系
|
|||
/// </summary>
|
|||
public async Task<(int TotalCount, IEnumerable<UserRole> Items)> GetPagedAsync( |
|||
System.Linq.Expressions.Expression<Func<UserRole, bool>> predicate, |
|||
int pageNumber, |
|||
int pageSize, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var query = _context.UserRoles.Where(predicate); |
|||
var totalCount = await query.CountAsync(cancellationToken); |
|||
var items = await query |
|||
.Skip((pageNumber - 1) * pageSize) |
|||
.Take(pageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
return (totalCount, items); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取第一个符合条件的用户角色关系
|
|||
/// </summary>
|
|||
public async Task<UserRole?> FirstOrDefaultAsync(System.Linq.Expressions.Expression<Func<UserRole, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.FirstOrDefaultAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查是否存在符合条件的用户角色关系
|
|||
/// </summary>
|
|||
public async Task<bool> AnyAsync(System.Linq.Expressions.Expression<Func<UserRole, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.AnyAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 统计符合条件的用户角色关系数量
|
|||
/// </summary>
|
|||
public async Task<int> CountAsync(System.Linq.Expressions.Expression<Func<UserRole, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await _context.UserRoles.CountAsync(predicate, cancellationToken); |
|||
} |
|||
|
|||
#endregion
|
|||
} |
@ -0,0 +1,83 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using MediatR; |
|||
using CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
using CellularManagement.Application.Common; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.AspNetCore.Http; |
|||
using CellularManagement.Presentation.Abstractions; |
|||
|
|||
namespace CellularManagement.Presentation.Controllers; |
|||
|
|||
/// <summary>
|
|||
/// 权限管理控制器
|
|||
/// 提供权限管理相关的 API 接口,包括创建、删除和查询权限功能
|
|||
/// </summary>
|
|||
[Authorize(Roles = "Admin")] // 只有管理员可以访问
|
|||
public class PermissionsController : ApiController |
|||
{ |
|||
private readonly ILogger<PermissionsController> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化权限控制器
|
|||
/// </summary>
|
|||
/// <param name="mediator">MediatR 中介者,用于处理命令和查询</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public PermissionsController( |
|||
IMediator mediator, |
|||
ILogger<PermissionsController> logger) : base(mediator) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建新权限
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 示例请求:
|
|||
///
|
|||
/// POST /api/permissions/create
|
|||
/// {
|
|||
/// "name": "CreateUser",
|
|||
/// "description": "创建用户的权限"
|
|||
/// }
|
|||
///
|
|||
/// </remarks>
|
|||
/// <param name="command">创建权限命令,包含权限名称和描述</param>
|
|||
/// <returns>
|
|||
/// 创建结果,包含:
|
|||
/// - 成功:返回权限ID
|
|||
/// - 失败:返回错误信息
|
|||
/// </returns>
|
|||
/// <response code="200">创建成功,返回权限ID</response>
|
|||
/// <response code="400">创建失败,返回错误信息</response>
|
|||
[HttpPost] |
|||
[ProducesResponseType(typeof(OperationResult<CreatePermissionResponse>), StatusCodes.Status200OK)] |
|||
[ProducesResponseType(typeof(OperationResult<CreatePermissionResponse>), StatusCodes.Status400BadRequest)] |
|||
public async Task<ActionResult<OperationResult<CreatePermissionResponse>>> CreatePermission([FromBody] CreatePermissionCommand command) |
|||
{ |
|||
try |
|||
{ |
|||
var result = await mediator.Send(command); |
|||
|
|||
if (result.IsSuccess) |
|||
{ |
|||
_logger.LogInformation("权限 {PermissionName} 创建成功", command.Name); |
|||
} |
|||
else |
|||
{ |
|||
_logger.LogWarning("权限 {PermissionName} 创建失败: {Error}", |
|||
command.Name, |
|||
result.ErrorMessages?.FirstOrDefault()); |
|||
} |
|||
|
|||
return Ok(result); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建权限 {PermissionName} 时发生异常", command.Name); |
|||
return StatusCode(StatusCodes.Status500InternalServerError, |
|||
OperationResult<CreatePermissionResponse>.CreateFailure("系统错误,请稍后重试")); |
|||
} |
|||
} |
|||
} |
@ -1,12 +1,18 @@ |
|||
import { BrowserRouter } from 'react-router-dom'; |
|||
import { RecoilRoot } from 'recoil'; |
|||
import { AppRouter } from './routes/AppRouter'; |
|||
import { AuthProvider } from './contexts/AuthContext'; |
|||
import { ThemeProvider } from './providers/ThemeProvider'; |
|||
|
|||
export function App() { |
|||
return ( |
|||
<RecoilRoot> |
|||
<BrowserRouter> |
|||
<AppRouter /> |
|||
<AuthProvider> |
|||
<ThemeProvider> |
|||
<AppRouter /> |
|||
</ThemeProvider> |
|||
</AuthProvider> |
|||
</BrowserRouter> |
|||
</RecoilRoot> |
|||
); |
|||
|
@ -0,0 +1,61 @@ |
|||
import { Environment } from '../types/config.types'; |
|||
|
|||
// 基础配置管理器类
|
|||
export abstract class BaseConfigManager<T> { |
|||
protected static instance: any; |
|||
protected config!: T; |
|||
protected environment: Environment; |
|||
|
|||
protected constructor() { |
|||
this.environment = this.getCurrentEnvironment(); |
|||
this.config = this.getDefaultConfig(); |
|||
} |
|||
|
|||
// 获取单例实例
|
|||
public static getInstance(): any { |
|||
if (!this.instance) { |
|||
this.instance = new (this as any)(); |
|||
} |
|||
return this.instance; |
|||
} |
|||
|
|||
// 获取当前环境
|
|||
protected getCurrentEnvironment(): Environment { |
|||
const env = import.meta.env.MODE || 'development'; |
|||
return (env as Environment) || 'development'; |
|||
} |
|||
|
|||
// 获取配置
|
|||
public getConfig(): T { |
|||
return { ...this.config }; |
|||
} |
|||
|
|||
// 更新配置
|
|||
public updateConfig(newConfig: Partial<T>): void { |
|||
this.config = { |
|||
...this.config, |
|||
...newConfig |
|||
}; |
|||
} |
|||
|
|||
// 重置配置
|
|||
public resetConfig(): void { |
|||
this.config = this.getDefaultConfig(); |
|||
} |
|||
|
|||
// 获取默认配置(需要子类实现)
|
|||
protected abstract getDefaultConfig(): T; |
|||
|
|||
// 环境检查方法
|
|||
public isDevelopment(): boolean { |
|||
return this.environment === 'development'; |
|||
} |
|||
|
|||
public isStaging(): boolean { |
|||
return this.environment === 'staging'; |
|||
} |
|||
|
|||
public isProduction(): boolean { |
|||
return this.environment === 'production'; |
|||
} |
|||
} |
@ -0,0 +1,142 @@ |
|||
import { z } from 'zod'; |
|||
import type { ApiConfig, AuthConfig, AppConfig, MockConfig, Environment } from '../types/config.types'; |
|||
|
|||
// 默认配置
|
|||
const DEFAULT_CONFIG = { |
|||
// API配置
|
|||
VITE_API_BASE_URL: 'http://localhost:5202/api', |
|||
VITE_API_TIMEOUT: '30000', |
|||
VITE_API_VERSION: 'v1', |
|||
VITE_API_MAX_RETRIES: '3', |
|||
VITE_API_RETRY_DELAY: '1000', |
|||
|
|||
// 应用配置
|
|||
VITE_APP_TITLE: '蜂窝管理系统', |
|||
VITE_APP_DESCRIPTION: '蜂窝管理系统 - 一个现代化的蜂窝网络管理平台', |
|||
|
|||
// 认证配置
|
|||
VITE_AUTH_TOKEN_KEY: 'access_token', |
|||
VITE_AUTH_REFRESH_TOKEN_KEY: 'refresh_token', |
|||
VITE_AUTH_TOKEN_EXPIRES_KEY: 'token_expires', |
|||
|
|||
// Mock配置
|
|||
VITE_ENABLE_MOCK: 'true', |
|||
VITE_MOCK_DELAY: '500', |
|||
|
|||
// 环境标识
|
|||
MODE: 'development' |
|||
} as const; |
|||
|
|||
// 环境变量验证模式
|
|||
const envSchema = z.object({ |
|||
// API配置
|
|||
VITE_API_BASE_URL: z.string().url().default(DEFAULT_CONFIG.VITE_API_BASE_URL), |
|||
VITE_API_TIMEOUT: z.string().transform(Number).default(DEFAULT_CONFIG.VITE_API_TIMEOUT), |
|||
VITE_API_VERSION: z.string().default(DEFAULT_CONFIG.VITE_API_VERSION), |
|||
VITE_API_MAX_RETRIES: z.string().transform(Number).default(DEFAULT_CONFIG.VITE_API_MAX_RETRIES), |
|||
VITE_API_RETRY_DELAY: z.string().transform(Number).default(DEFAULT_CONFIG.VITE_API_RETRY_DELAY), |
|||
|
|||
// 应用配置
|
|||
VITE_APP_TITLE: z.string().default(DEFAULT_CONFIG.VITE_APP_TITLE), |
|||
VITE_APP_DESCRIPTION: z.string().default(DEFAULT_CONFIG.VITE_APP_DESCRIPTION), |
|||
|
|||
// 认证配置
|
|||
VITE_AUTH_TOKEN_KEY: z.string().default(DEFAULT_CONFIG.VITE_AUTH_TOKEN_KEY), |
|||
VITE_AUTH_REFRESH_TOKEN_KEY: z.string().default(DEFAULT_CONFIG.VITE_AUTH_REFRESH_TOKEN_KEY), |
|||
VITE_AUTH_TOKEN_EXPIRES_KEY: z.string().default(DEFAULT_CONFIG.VITE_AUTH_TOKEN_EXPIRES_KEY), |
|||
|
|||
// Mock配置
|
|||
VITE_ENABLE_MOCK: z.string().transform((val: string) => val === 'true').default(DEFAULT_CONFIG.VITE_ENABLE_MOCK), |
|||
VITE_MOCK_DELAY: z.string().transform(Number).default(DEFAULT_CONFIG.VITE_MOCK_DELAY), |
|||
|
|||
// 环境标识
|
|||
MODE: z.enum(['development', 'staging', 'production']).default(DEFAULT_CONFIG.MODE) |
|||
}); |
|||
|
|||
// 环境配置类型
|
|||
type EnvConfig = z.infer<typeof envSchema>; |
|||
|
|||
// 环境配置管理器
|
|||
export class EnvConfigManager { |
|||
private static instance: EnvConfigManager; |
|||
private config: EnvConfig; |
|||
|
|||
private constructor() { |
|||
try { |
|||
this.config = envSchema.parse(import.meta.env); |
|||
} catch (error) { |
|||
if (error instanceof z.ZodError) { |
|||
const errorMessages = error.errors.map(err => |
|||
`${err.path.join('.')}: ${err.message}` |
|||
); |
|||
throw new Error(`环境变量验证失败:\n${errorMessages.join('\n')}`); |
|||
} |
|||
throw error; |
|||
} |
|||
} |
|||
|
|||
// 获取单例实例
|
|||
public static getInstance(): EnvConfigManager { |
|||
if (!EnvConfigManager.instance) { |
|||
EnvConfigManager.instance = new EnvConfigManager(); |
|||
} |
|||
return EnvConfigManager.instance; |
|||
} |
|||
|
|||
// 获取API配置
|
|||
public getApiConfig() { |
|||
return { |
|||
baseURL: this.config.VITE_API_BASE_URL, |
|||
timeout: this.config.VITE_API_TIMEOUT, |
|||
version: this.config.VITE_API_VERSION, |
|||
maxRetries: this.config.VITE_API_MAX_RETRIES, |
|||
retryDelay: this.config.VITE_API_RETRY_DELAY |
|||
}; |
|||
} |
|||
|
|||
// 获取认证配置
|
|||
public getAuthConfig() { |
|||
return { |
|||
tokenKey: this.config.VITE_AUTH_TOKEN_KEY, |
|||
refreshTokenKey: this.config.VITE_AUTH_REFRESH_TOKEN_KEY, |
|||
tokenExpiresKey: this.config.VITE_AUTH_TOKEN_EXPIRES_KEY |
|||
}; |
|||
} |
|||
|
|||
// 获取应用配置
|
|||
public getAppConfig() { |
|||
return { |
|||
title: this.config.VITE_APP_TITLE, |
|||
description: this.config.VITE_APP_DESCRIPTION |
|||
}; |
|||
} |
|||
|
|||
// 获取Mock配置
|
|||
public getMockConfig() { |
|||
return { |
|||
enabled: this.config.VITE_ENABLE_MOCK, |
|||
delay: this.config.VITE_MOCK_DELAY |
|||
}; |
|||
} |
|||
|
|||
// 获取当前环境
|
|||
public getEnvironment() { |
|||
return this.config.MODE; |
|||
} |
|||
|
|||
// 环境检查方法
|
|||
public isDevelopment() { |
|||
return this.config.MODE === 'development'; |
|||
} |
|||
|
|||
public isStaging() { |
|||
return this.config.MODE === 'staging'; |
|||
} |
|||
|
|||
public isProduction() { |
|||
return this.config.MODE === 'production'; |
|||
} |
|||
} |
|||
|
|||
// 导出默认实例
|
|||
export const envConfig = EnvConfigManager.getInstance(); |
@ -1,61 +0,0 @@ |
|||
import { HttpClientConfig } from '@/lib/http-client'; |
|||
|
|||
export class HttpClientConfigManager { |
|||
private static instance: HttpClientConfigManager; |
|||
private config: HttpClientConfig; |
|||
|
|||
private constructor() { |
|||
this.config = { |
|||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5202/api', |
|||
timeout: Number(import.meta.env.VITE_API_TIMEOUT) || 10000, |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
'X-API-Version': import.meta.env.VITE_API_VERSION || 'v1', |
|||
}, |
|||
}; |
|||
} |
|||
|
|||
public static getInstance(): HttpClientConfigManager { |
|||
if (!HttpClientConfigManager.instance) { |
|||
HttpClientConfigManager.instance = new HttpClientConfigManager(); |
|||
} |
|||
return HttpClientConfigManager.instance; |
|||
} |
|||
|
|||
public getConfig(): HttpClientConfig { |
|||
return { ...this.config }; |
|||
} |
|||
|
|||
public updateConfig(newConfig: Partial<HttpClientConfig>): void { |
|||
this.config = { |
|||
...this.config, |
|||
...newConfig, |
|||
headers: { |
|||
...this.config.headers, |
|||
...newConfig.headers, |
|||
}, |
|||
}; |
|||
} |
|||
|
|||
public setBaseURL(baseURL: string): void { |
|||
this.config.baseURL = baseURL; |
|||
} |
|||
|
|||
public setTimeout(timeout: number): void { |
|||
this.config.timeout = timeout; |
|||
} |
|||
|
|||
public setHeader(key: string, value: string): void { |
|||
if (!this.config.headers) { |
|||
this.config.headers = {}; |
|||
} |
|||
this.config.headers[key] = value; |
|||
} |
|||
|
|||
public removeHeader(key: string): void { |
|||
if (this.config.headers) { |
|||
const { [key]: removed, ...headers } = this.config.headers; |
|||
this.config.headers = headers; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,92 @@ |
|||
import { BaseConfigManager } from '../core/base-config-manager'; |
|||
import { HttpClientConfig } from '../types/config.types'; |
|||
import { envConfig } from '../core/env.config'; |
|||
|
|||
// HTTP客户端配置管理器
|
|||
export class HttpClientConfigManager extends BaseConfigManager<HttpClientConfig> { |
|||
protected getDefaultConfig(): HttpClientConfig { |
|||
const apiConfig = envConfig.getApiConfig(); |
|||
return { |
|||
baseURL: apiConfig.baseURL, |
|||
timeout: apiConfig.timeout, |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
'Accept': 'application/json', |
|||
'X-API-Version': apiConfig.version |
|||
}, |
|||
maxRetries: apiConfig.maxRetries, |
|||
retryDelay: apiConfig.retryDelay |
|||
}; |
|||
} |
|||
|
|||
// 更新配置
|
|||
public updateConfig(newConfig: Partial<HttpClientConfig>): void { |
|||
this.validateConfig(newConfig); |
|||
super.updateConfig(newConfig); |
|||
} |
|||
|
|||
// 配置验证
|
|||
private validateConfig(config: Partial<HttpClientConfig>): void { |
|||
if (config.timeout && config.timeout < 0) { |
|||
throw new Error('超时时间不能为负数'); |
|||
} |
|||
|
|||
if (config.maxRetries && config.maxRetries < 0) { |
|||
throw new Error('重试次数不能为负数'); |
|||
} |
|||
|
|||
if (config.retryDelay && config.retryDelay < 0) { |
|||
throw new Error('重试延迟不能为负数'); |
|||
} |
|||
|
|||
if (config.baseURL && !config.baseURL.startsWith('http')) { |
|||
throw new Error('baseURL 必须是有效的 HTTP(S) URL'); |
|||
} |
|||
} |
|||
|
|||
// 设置基础URL
|
|||
public setBaseURL(baseURL: string): void { |
|||
this.validateConfig({ baseURL }); |
|||
this.config.baseURL = baseURL; |
|||
} |
|||
|
|||
// 设置超时时间
|
|||
public setTimeout(timeout: number): void { |
|||
this.validateConfig({ timeout }); |
|||
this.config.timeout = timeout; |
|||
} |
|||
|
|||
// 设置请求头
|
|||
public setHeader(key: string, value: string): void { |
|||
if (!this.config.headers) { |
|||
this.config.headers = {}; |
|||
} |
|||
this.config.headers[key] = value; |
|||
} |
|||
|
|||
// 移除请求头
|
|||
public removeHeader(key: string): void { |
|||
if (this.config.headers) { |
|||
const { [key]: removed, ...headers } = this.config.headers; |
|||
this.config.headers = headers; |
|||
} |
|||
} |
|||
|
|||
// 获取所有请求头
|
|||
public getHeaders(): Record<string, string> { |
|||
return { ...this.config.headers }; |
|||
} |
|||
|
|||
// 检查是否包含特定请求头
|
|||
public hasHeader(key: string): boolean { |
|||
return Boolean(this.config.headers?.[key]); |
|||
} |
|||
|
|||
// 获取请求头值
|
|||
public getHeader(key: string): string | undefined { |
|||
return this.config.headers?.[key]; |
|||
} |
|||
} |
|||
|
|||
// 导出默认实例
|
|||
export const httpClientConfig = HttpClientConfigManager.getInstance(); |
@ -0,0 +1,71 @@ |
|||
// API配置类型
|
|||
export interface ApiConfig { |
|||
baseURL: string; |
|||
timeout: number; |
|||
version: string; |
|||
maxRetries: number; |
|||
retryDelay: number; |
|||
} |
|||
|
|||
// 认证配置类型
|
|||
export interface AuthConfig { |
|||
tokenKey: string; |
|||
refreshTokenKey: string; |
|||
tokenExpiresKey: string; |
|||
setToken(token: string): void; |
|||
getToken(): string | null; |
|||
setRefreshToken(token: string): void; |
|||
getRefreshToken(): string | null; |
|||
setTokenExpires(expires: number): void; |
|||
getTokenExpires(): number | null; |
|||
clearTokens(): void; |
|||
isTokenExpired(): boolean; |
|||
} |
|||
|
|||
// 应用配置类型
|
|||
export interface AppConfig { |
|||
title: string; |
|||
description: string; |
|||
} |
|||
|
|||
// Mock配置类型
|
|||
export interface MockConfig { |
|||
enabled: boolean; |
|||
delay: number; |
|||
} |
|||
|
|||
// 环境类型
|
|||
export type Environment = 'development' | 'staging' | 'production'; |
|||
|
|||
// HTTP客户端配置类型
|
|||
export interface HttpClientConfig { |
|||
baseURL: string; |
|||
timeout?: number; |
|||
headers?: Record<string, string>; |
|||
maxRetries?: number; |
|||
retryDelay?: number; |
|||
} |
|||
|
|||
// 环境配置类型
|
|||
export interface EnvironmentConfig { |
|||
baseURL: string; |
|||
timeout: number; |
|||
headers: Record<string, string>; |
|||
} |
|||
|
|||
// 完整配置类型
|
|||
export interface Config { |
|||
api: ApiConfig; |
|||
auth: AuthConfig; |
|||
app: AppConfig; |
|||
mock: MockConfig; |
|||
environment: Environment; |
|||
} |
|||
|
|||
// 操作结果接口
|
|||
export interface OperationResult<T = any> { |
|||
successMessage: string | null; |
|||
errorMessages: string[]; |
|||
data: T | null; |
|||
isSuccess: boolean; |
|||
} |
@ -0,0 +1,96 @@ |
|||
/// <reference types="vite/client" />
|
|||
|
|||
/** |
|||
* Vite环境变量类型声明 |
|||
* 用于TypeScript类型检查和IDE智能提示 |
|||
*/ |
|||
interface ImportMetaEnv { |
|||
// API配置
|
|||
/** API基础URL */ |
|||
readonly VITE_API_BASE_URL: string; |
|||
/** API请求超时时间(毫秒) */ |
|||
readonly VITE_API_TIMEOUT: string; |
|||
/** API版本号 */ |
|||
readonly VITE_API_VERSION: string; |
|||
/** API请求最大重试次数 */ |
|||
readonly VITE_API_MAX_RETRIES: string; |
|||
/** API请求重试延迟时间(毫秒) */ |
|||
readonly VITE_API_RETRY_DELAY: string; |
|||
|
|||
// 应用配置
|
|||
/** 应用标题 */ |
|||
readonly VITE_APP_TITLE: string; |
|||
/** 应用描述 */ |
|||
readonly VITE_APP_DESCRIPTION: string; |
|||
|
|||
// 认证配置
|
|||
/** 访问令牌存储键名 */ |
|||
readonly VITE_AUTH_TOKEN_KEY: string; |
|||
/** 刷新令牌存储键名 */ |
|||
readonly VITE_AUTH_REFRESH_TOKEN_KEY: string; |
|||
/** 令牌过期时间存储键名 */ |
|||
readonly VITE_AUTH_TOKEN_EXPIRES_KEY: string; |
|||
|
|||
// Mock配置
|
|||
/** 是否启用Mock数据 */ |
|||
readonly VITE_ENABLE_MOCK: string; |
|||
/** Mock数据延迟时间(毫秒) */ |
|||
readonly VITE_MOCK_DELAY: string; |
|||
|
|||
// 环境标识
|
|||
/** 当前运行环境 */ |
|||
readonly MODE: 'development' | 'staging' | 'production'; |
|||
} |
|||
|
|||
/** |
|||
* Vite环境变量接口 |
|||
*/ |
|||
interface ImportMeta { |
|||
readonly env: ImportMetaEnv; |
|||
} |
|||
|
|||
/** |
|||
* Node.js环境变量类型声明 |
|||
*/ |
|||
declare namespace NodeJS { |
|||
interface ProcessEnv extends ImportMetaEnv {} |
|||
} |
|||
|
|||
/** |
|||
* 环境配置类型 |
|||
*/ |
|||
interface EnvConfig { |
|||
/** API基础URL */ |
|||
readonly API_BASE_URL: string; |
|||
/** API请求超时时间(毫秒) */ |
|||
readonly API_TIMEOUT: number; |
|||
/** API版本号 */ |
|||
readonly API_VERSION: string; |
|||
/** API请求最大重试次数 */ |
|||
readonly API_MAX_RETRIES: number; |
|||
/** API请求重试延迟时间(毫秒) */ |
|||
readonly API_RETRY_DELAY: number; |
|||
/** 应用标题 */ |
|||
readonly APP_TITLE: string; |
|||
/** 应用描述 */ |
|||
readonly APP_DESCRIPTION: string; |
|||
/** 访问令牌存储键名 */ |
|||
readonly AUTH_TOKEN_KEY: string; |
|||
/** 刷新令牌存储键名 */ |
|||
readonly AUTH_REFRESH_TOKEN_KEY: string; |
|||
/** 令牌过期时间存储键名 */ |
|||
readonly AUTH_TOKEN_EXPIRES_KEY: string; |
|||
/** 是否启用Mock数据 */ |
|||
readonly ENABLE_MOCK: boolean; |
|||
/** Mock数据延迟时间(毫秒) */ |
|||
readonly MOCK_DELAY: number; |
|||
/** 当前运行环境 */ |
|||
readonly MODE: 'development' | 'staging' | 'production'; |
|||
} |
|||
|
|||
// 在代码中使用环境变量
|
|||
import { envConfig } from '../core/env.config'; |
|||
|
|||
console.log(envConfig.API_BASE_URL); // 从环境变量获取
|
|||
console.log(envConfig.API_TIMEOUT); // 自动转换为数字
|
|||
console.log(envConfig.API_VERSION); // 从环境变量获取
|
@ -0,0 +1,178 @@ |
|||
import { createContext, useContext, useReducer, ReactNode, useEffect, useMemo } from 'react'; |
|||
import { AuthState, AuthContextType, LoginRequest, User } from '@/types/auth'; |
|||
import { authService } from '@/services/authService'; |
|||
import { useSetRecoilState } from 'recoil'; |
|||
import { userState } from '@/states/appState'; |
|||
|
|||
const initialState: AuthState = { |
|||
user: null, |
|||
isAuthenticated: false, |
|||
isLoading: false, |
|||
error: null, |
|||
userPermissions: [], |
|||
}; |
|||
|
|||
type AuthAction = |
|||
| { type: 'LOGIN_START' } |
|||
| { type: 'LOGIN_SUCCESS'; payload: { user: User; accessToken: string; refreshToken: string } } |
|||
| { type: 'LOGIN_FAILURE'; payload: string } |
|||
| { type: 'LOGOUT' } |
|||
| { type: 'CLEAR_ERROR' } |
|||
| { type: 'SET_USER'; payload: User }; |
|||
|
|||
const authReducer = (state: AuthState, action: AuthAction): AuthState => { |
|||
switch (action.type) { |
|||
case 'LOGIN_START': |
|||
return { ...state, isLoading: true, error: null }; |
|||
case 'LOGIN_SUCCESS': |
|||
return { |
|||
...state, |
|||
isLoading: false, |
|||
isAuthenticated: true, |
|||
user: action.payload.user, |
|||
userPermissions: [ |
|||
...(Array.isArray(action.payload.user.permissions) ? action.payload.user.permissions : []), |
|||
'dashboard.view', |
|||
'users.view', |
|||
'settings.view' |
|||
], |
|||
error: null, |
|||
}; |
|||
case 'LOGIN_FAILURE': |
|||
return { |
|||
...state, |
|||
isLoading: false, |
|||
isAuthenticated: false, |
|||
user: null, |
|||
userPermissions: [], |
|||
error: action.payload, |
|||
}; |
|||
case 'LOGOUT': |
|||
return initialState; |
|||
case 'CLEAR_ERROR': |
|||
return { ...state, error: null }; |
|||
case 'SET_USER': |
|||
return { |
|||
...state, |
|||
user: action.payload, |
|||
userPermissions: [ |
|||
...(Array.isArray(action.payload.permissions) ? action.payload.permissions : []), |
|||
'dashboard.view', |
|||
'users.view', |
|||
'settings.view' |
|||
], |
|||
isAuthenticated: true, |
|||
}; |
|||
default: |
|||
return state; |
|||
} |
|||
}; |
|||
|
|||
const AuthContext = createContext<AuthContextType | undefined>(undefined); |
|||
|
|||
export function AuthProvider({ children }: { children: ReactNode }) { |
|||
const [state, dispatch] = useReducer(authReducer, initialState); |
|||
const setGlobalUser = useSetRecoilState(userState); |
|||
|
|||
// 同步用户状态到 Recoil
|
|||
useEffect(() => { |
|||
setGlobalUser(state.user); |
|||
}, [state.user, setGlobalUser]); |
|||
|
|||
// 初始化时检查用户状态
|
|||
useEffect(() => { |
|||
const initAuth = async () => { |
|||
const token = localStorage.getItem('accessToken'); |
|||
if (token) { |
|||
try { |
|||
const result = await authService.getCurrentUser(); |
|||
if (result.isSuccess && result.data?.user) { |
|||
dispatch({ type: 'SET_USER', payload: result.data.user }); |
|||
} else { |
|||
dispatch({ type: 'LOGOUT' }); |
|||
} |
|||
} catch { |
|||
dispatch({ type: 'LOGOUT' }); |
|||
} |
|||
} |
|||
}; |
|||
initAuth(); |
|||
}, []); |
|||
|
|||
const login = async (request: LoginRequest) => { |
|||
dispatch({ type: 'LOGIN_START' }); |
|||
try { |
|||
const result = await authService.login(request); |
|||
if (result.isSuccess && result.data) { |
|||
const { accessToken, refreshToken, user } = result.data; |
|||
localStorage.setItem('accessToken', accessToken); |
|||
localStorage.setItem('refreshToken', refreshToken); |
|||
dispatch({ type: 'LOGIN_SUCCESS', payload: { user, accessToken, refreshToken } }); |
|||
} else { |
|||
dispatch({ |
|||
type: 'LOGIN_FAILURE', |
|||
payload: result.errorMessages?.[0] || '登录失败', |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
dispatch({ |
|||
type: 'LOGIN_FAILURE', |
|||
payload: '登录请求失败,请稍后重试', |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
const logout = async () => { |
|||
try { |
|||
await authService.logout(); |
|||
} finally { |
|||
localStorage.removeItem('accessToken'); |
|||
localStorage.removeItem('refreshToken'); |
|||
dispatch({ type: 'LOGOUT' }); |
|||
} |
|||
}; |
|||
|
|||
const refreshToken = async () => { |
|||
const refreshToken = localStorage.getItem('refreshToken'); |
|||
if (!refreshToken) { |
|||
dispatch({ type: 'LOGOUT' }); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
const result = await authService.refreshToken(refreshToken); |
|||
if (result.isSuccess && result.data) { |
|||
const { accessToken, refreshToken: newRefreshToken, user } = result.data; |
|||
localStorage.setItem('accessToken', accessToken); |
|||
localStorage.setItem('refreshToken', newRefreshToken); |
|||
dispatch({ type: 'SET_USER', payload: user }); |
|||
} else { |
|||
dispatch({ type: 'LOGOUT' }); |
|||
} |
|||
} catch { |
|||
dispatch({ type: 'LOGOUT' }); |
|||
} |
|||
}; |
|||
|
|||
// 使用 useMemo 优化 Context 值
|
|||
const contextValue = useMemo(() => ({ |
|||
...state, |
|||
login, |
|||
logout, |
|||
refreshToken, |
|||
}), [state]); |
|||
|
|||
return ( |
|||
<AuthContext.Provider value={contextValue}> |
|||
{children} |
|||
</AuthContext.Provider> |
|||
); |
|||
} |
|||
|
|||
export function useAuth() { |
|||
const context = useContext(AuthContext); |
|||
if (context === undefined) { |
|||
throw new Error('useAuth must be used within an AuthProvider'); |
|||
} |
|||
return context; |
|||
} |
@ -1,83 +0,0 @@ |
|||
import { useState, useCallback } from 'react'; |
|||
import { authService } from '@/services/authService'; |
|||
import { LoginRequest, LoginResponse } from '@/types/auth'; |
|||
import { useNavigate } from 'react-router-dom'; |
|||
|
|||
export function useAuth() { |
|||
const [isLoading, setIsLoading] = useState(false); |
|||
const [error, setError] = useState<string | null>(null); |
|||
const navigate = useNavigate(); |
|||
|
|||
const login = useCallback(async (request: LoginRequest) => { |
|||
setIsLoading(true); |
|||
setError(null); |
|||
|
|||
try { |
|||
const result = await authService.login(request); |
|||
|
|||
if (result.isSuccess && result.data) { |
|||
localStorage.setItem('accessToken', result.data.accessToken); |
|||
localStorage.setItem('refreshToken', result.data.refreshToken); |
|||
navigate('/'); |
|||
return result.data; |
|||
} else { |
|||
setError(result.errorMessages?.[0] || '登录失败,请检查用户名和密码'); |
|||
return null; |
|||
} |
|||
} catch (err) { |
|||
setError('登录请求失败,请稍后重试'); |
|||
return null; |
|||
} finally { |
|||
setIsLoading(false); |
|||
} |
|||
}, [navigate]); |
|||
|
|||
const logout = useCallback(async () => { |
|||
try { |
|||
await authService.logout(); |
|||
navigate('/login'); |
|||
} catch (err) { |
|||
setError('登出失败,请稍后重试'); |
|||
} |
|||
}, [navigate]); |
|||
|
|||
const refreshToken = useCallback(async () => { |
|||
const refreshToken = localStorage.getItem('refreshToken'); |
|||
if (!refreshToken) { |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
const result = await authService.refreshToken(refreshToken); |
|||
if (result.isSuccess && result.data) { |
|||
localStorage.setItem('accessToken', result.data.accessToken); |
|||
localStorage.setItem('refreshToken', result.data.refreshToken); |
|||
return result.data; |
|||
} |
|||
return null; |
|||
} catch (err) { |
|||
return null; |
|||
} |
|||
}, []); |
|||
|
|||
const getCurrentUser = useCallback(async () => { |
|||
try { |
|||
const result = await authService.getCurrentUser(); |
|||
if (result.isSuccess && result.data) { |
|||
return result.data.user; |
|||
} |
|||
return null; |
|||
} catch (err) { |
|||
return null; |
|||
} |
|||
}, []); |
|||
|
|||
return { |
|||
isLoading, |
|||
error, |
|||
login, |
|||
logout, |
|||
refreshToken, |
|||
getCurrentUser, |
|||
}; |
|||
} |
@ -0,0 +1,19 @@ |
|||
import { ReactNode, useEffect } from 'react'; |
|||
import { useRecoilValue } from 'recoil'; |
|||
import { appSettingsState } from '@/states/appState'; |
|||
|
|||
interface ThemeProviderProps { |
|||
children: ReactNode; |
|||
} |
|||
|
|||
export function ThemeProvider({ children }: ThemeProviderProps) { |
|||
const settings = useRecoilValue(appSettingsState); |
|||
|
|||
useEffect(() => { |
|||
// 应用主题
|
|||
document.documentElement.classList.remove('light', 'dark'); |
|||
document.documentElement.classList.add(settings.theme); |
|||
}, [settings.theme]); |
|||
|
|||
return <>{children}</>; |
|||
} |
@ -0,0 +1,42 @@ |
|||
import { atom, selector } from 'recoil'; |
|||
import { User } from '@/types/auth'; |
|||
|
|||
// 应用设置状态
|
|||
export const appSettingsState = atom({ |
|||
key: 'appSettingsState', |
|||
default: { |
|||
theme: 'light', |
|||
language: 'zh-CN', |
|||
sidebarCollapsed: false, |
|||
} |
|||
}); |
|||
|
|||
// 用户信息状态(与 AuthProvider 同步)
|
|||
export const userState = atom<User | null>({ |
|||
key: 'userState', |
|||
default: null |
|||
}); |
|||
|
|||
// 应用偏好设置
|
|||
export const preferencesState = atom({ |
|||
key: 'preferencesState', |
|||
default: { |
|||
notifications: true, |
|||
soundEnabled: true, |
|||
autoRefresh: true, |
|||
} |
|||
}); |
|||
|
|||
// 派生状态:用户设置
|
|||
export const userSettingsSelector = selector({ |
|||
key: 'userSettingsSelector', |
|||
get: ({get}) => { |
|||
const user = get(userState); |
|||
const settings = get(appSettingsState); |
|||
return { |
|||
...settings, |
|||
userId: user?.id, |
|||
userName: user?.name, |
|||
}; |
|||
} |
|||
}); |
@ -1,26 +1,40 @@ |
|||
import { Permission } from '@/constants/menuConfig'; |
|||
|
|||
export interface User { |
|||
id: string; |
|||
userName: string; |
|||
name: string; |
|||
email: string; |
|||
phoneNumber: string; |
|||
roles: string[]; |
|||
permissions: Permission[]; |
|||
} |
|||
|
|||
export interface LoginRequest { |
|||
username: string; |
|||
password: string; |
|||
} |
|||
|
|||
export interface LoginResponse { |
|||
accessToken: string; |
|||
refreshToken: string; |
|||
expiresAt: string; |
|||
user: User; |
|||
} |
|||
|
|||
export interface AuthState { |
|||
user: User | null; |
|||
isAuthenticated: boolean; |
|||
isLoading: boolean; |
|||
error: string | null; |
|||
userPermissions: Permission[]; |
|||
} |
|||
|
|||
export interface AuthContextType extends AuthState { |
|||
login: (request: LoginRequest) => Promise<void>; |
|||
logout: () => Promise<void>; |
|||
refreshToken: () => Promise<void>; |
|||
} |
|||
|
|||
export interface OperationResult<T> { |
|||
successMessage: string | null; |
|||
errorMessages: string[] | null; |
|||
data: T | null; |
|||
isSuccess: boolean; |
|||
} |
|||
|
|||
export interface LoginRequest { |
|||
username: string; |
|||
password: string; |
|||
} |
@ -1,32 +0,0 @@ |
|||
/// <reference types="vite/client" />
|
|||
|
|||
interface ImportMetaEnv { |
|||
// API配置
|
|||
readonly VITE_API_BASE_URL: string; |
|||
readonly VITE_API_TIMEOUT: string; |
|||
readonly VITE_API_VERSION: string; |
|||
|
|||
// 应用配置
|
|||
readonly VITE_APP_TITLE: string; |
|||
readonly VITE_APP_DESCRIPTION: string; |
|||
|
|||
// 认证配置
|
|||
readonly VITE_AUTH_TOKEN_KEY: string; |
|||
readonly VITE_AUTH_REFRESH_TOKEN_KEY: string; |
|||
readonly VITE_AUTH_TOKEN_EXPIRES_KEY: string; |
|||
|
|||
// 其他配置
|
|||
readonly VITE_ENABLE_MOCK: string; |
|||
readonly VITE_MOCK_DELAY: string; |
|||
} |
|||
|
|||
interface ImportMeta { |
|||
readonly env: ImportMetaEnv; |
|||
} |
|||
|
|||
// 在代码中使用环境变量
|
|||
import { envConfig } from '@/config/env'; |
|||
|
|||
console.log(envConfig.API_BASE_URL); // 从环境变量获取
|
|||
console.log(envConfig.API_TIMEOUT); // 自动转换为数字
|
|||
console.log(envConfig.API_VERSION); // 从环境变量获取
|
@ -0,0 +1,59 @@ |
|||
-- 用户表 |
|||
CREATE TABLE Users ( |
|||
Id INT IDENTITY(1,1) PRIMARY KEY, |
|||
Username NVARCHAR(50) NOT NULL UNIQUE, |
|||
Email NVARCHAR(100) NOT NULL UNIQUE, |
|||
PasswordHash NVARCHAR(MAX) NOT NULL, |
|||
FirstName NVARCHAR(50), |
|||
LastName NVARCHAR(50), |
|||
IsActive BIT DEFAULT 1, |
|||
CreatedAt DATETIME DEFAULT GETDATE(), |
|||
UpdatedAt DATETIME DEFAULT GETDATE() |
|||
); |
|||
|
|||
-- 角色表 |
|||
CREATE TABLE Roles ( |
|||
Id INT IDENTITY(1,1) PRIMARY KEY, |
|||
Name NVARCHAR(50) NOT NULL UNIQUE, |
|||
Description NVARCHAR(200), |
|||
CreatedAt DATETIME DEFAULT GETDATE(), |
|||
UpdatedAt DATETIME DEFAULT GETDATE() |
|||
); |
|||
|
|||
-- 权限表 |
|||
CREATE TABLE Permissions ( |
|||
Id INT IDENTITY(1,1) PRIMARY KEY, |
|||
Name NVARCHAR(50) NOT NULL UNIQUE, |
|||
Description NVARCHAR(200), |
|||
CreatedAt DATETIME DEFAULT GETDATE() |
|||
); |
|||
|
|||
-- 用户角色关联表 |
|||
CREATE TABLE UserRoles ( |
|||
UserId INT NOT NULL, |
|||
RoleId INT NOT NULL, |
|||
CreatedAt DATETIME DEFAULT GETDATE(), |
|||
PRIMARY KEY (UserId, RoleId), |
|||
FOREIGN KEY (UserId) REFERENCES Users(Id), |
|||
FOREIGN KEY (RoleId) REFERENCES Roles(Id) |
|||
); |
|||
|
|||
-- 角色权限关联表 |
|||
CREATE TABLE RolePermissions ( |
|||
RoleId INT NOT NULL, |
|||
PermissionId INT NOT NULL, |
|||
CreatedAt DATETIME DEFAULT GETDATE(), |
|||
PRIMARY KEY (RoleId, PermissionId), |
|||
FOREIGN KEY (RoleId) REFERENCES Roles(Id), |
|||
FOREIGN KEY (PermissionId) REFERENCES Permissions(Id) |
|||
); |
|||
|
|||
-- 刷新令牌表 |
|||
CREATE TABLE RefreshTokens ( |
|||
Id INT IDENTITY(1,1) PRIMARY KEY, |
|||
UserId INT NOT NULL, |
|||
Token NVARCHAR(MAX) NOT NULL, |
|||
ExpiresAt DATETIME NOT NULL, |
|||
CreatedAt DATETIME DEFAULT GETDATE(), |
|||
FOREIGN KEY (UserId) REFERENCES Users(Id) |
|||
); |
Loading…
Reference in new issue