using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Model;
using Model.Dto.System;
using Model.Entity.System;
using Model.Mapper;
using ORM;
using Service.Interface;
using SqlSugar;
namespace Service.Implement
{
///
/// 认证授权 服务实现:登录校验 → 加载角色/权限 → 签发 JWT(权限编码写入 Claims)
///
public class AuthService : IAuthService
{
private readonly IConfiguration _configuration;
private readonly ICurrentUser _currentUser;
private readonly ILogger _logger;
public AuthService(IConfiguration configuration, ICurrentUser currentUser, ILogger logger)
{
_configuration = configuration;
_currentUser = currentUser;
_logger = logger;
}
public async Task> LoginAsync(LoginDto dto, string? ip)
{
if (dto == null || string.IsNullOrWhiteSpace(dto.UserName) || string.IsNullOrWhiteSpace(dto.Password))
return Result.Error("用户名和密码不能为空");
try
{
var user = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.UserName == dto.UserName && x.IsDel == 0).FirstAsync();
if (user == null)
return Result.Error("用户名或密码错误");
if (user.IsEnabled == 0)
return Result.Error("账号已被禁用,请联系管理员");
if (!PasswordHelper.Verify(dto.Password, user.PasswordHash))
return Result.Error("用户名或密码错误");
// 加载角色与权限
var (roleIds, roleNames, permissionCodes, dataScope) = await LoadUserAuthAsync(user.Id);
// 签发 JWT
var token = GenerateJwt(user, roleIds, roleNames, permissionCodes, dataScope);
// 更新最后登录时间
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.LastLoginTime == DateTime.Now)
.Where(x => x.Id == user.Id).ExecuteCommandAsync();
// 登录审计
await WriteAuditAsync(user.Id, user.UserName, roleIds.FirstOrDefault(), roleNames.FirstOrDefault(),
"Login", "User", user.Id, 0, null, null, ip, $"用户 {user.UserName} 登录系统");
var userDto = user.ToDto();
userDto.RoleIds = roleIds.Select(r => r.ToString()).ToList();
userDto.RoleNames = string.Join(",", roleNames);
return Result.Success(new LoginResultDto
{
Token = token,
RefreshToken = token,
User = userDto,
Permissions = permissionCodes
});
}
catch (Exception ex)
{
_logger.LogError(ex, "登录异常");
return Result.Error("登录失败", ex);
}
}
public async Task LogoutAsync()
{
if (!_currentUser.IsAuthenticated)
return Result.Success();
try
{
await WriteAuditAsync(_currentUser.UserId, _currentUser.UserName,
_currentUser.RoleIds.FirstOrDefault(), _currentUser.RoleNames.FirstOrDefault(),
"Logout", "User", _currentUser.UserId, 0, null, null, null,
$"用户 {_currentUser.UserName} 退出登录");
return Result.Success();
}
catch (Exception ex)
{
_logger.LogError(ex, "退出登录记录审计失败");
return Result.Success(); // 退出本身不应失败
}
}
public async Task> GetMeAsync()
{
if (!_currentUser.IsAuthenticated)
return Result.Error("未登录或 Token 已失效");
try
{
var user = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == _currentUser.UserId && x.IsDel == 0).FirstAsync();
if (user == null)
return Result.Error("用户不存在或已被删除");
var userDto = user.ToDto();
userDto.RoleIds = _currentUser.RoleIds.Select(r => r.ToString()).ToList();
userDto.RoleNames = string.Join(",", _currentUser.RoleNames);
return Result.Success(new LoginResultDto
{
Token = "",
RefreshToken = "",
User = userDto,
Permissions = _currentUser.Permissions
});
}
catch (Exception ex)
{
return Result.Error("获取当前用户信息失败", ex);
}
}
public async Task ChangePasswordAsync(ChangePasswordDto dto)
{
if (dto == null || string.IsNullOrWhiteSpace(dto.OldPassword) || string.IsNullOrWhiteSpace(dto.NewPassword))
return Result.Error("原密码和新密码不能为空");
if (dto.NewPassword.Length < 6)
return Result.Error("新密码长度不能少于 6 位");
if (!_currentUser.IsAuthenticated)
return Result.Error("未登录或 Token 已失效");
try
{
var user = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == _currentUser.UserId && x.IsDel == 0).FirstAsync();
if (user == null)
return Result.Error("用户不存在或已被删除");
if (!PasswordHelper.Verify(dto.OldPassword, user.PasswordHash))
return Result.Error("原密码错误");
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.PasswordHash == PasswordHelper.Hash(dto.NewPassword))
.Where(x => x.Id == user.Id).ExecuteCommandAsync();
await WriteAuditAsync(user.Id, user.UserName, _currentUser.RoleIds.FirstOrDefault(), _currentUser.RoleNames.FirstOrDefault(),
"Update", "User", user.Id, 0, null, null, null, $"用户 {user.UserName} 修改了自己的密码");
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("修改密码失败", ex);
}
}
///
/// 加载用户的角色Id/角色名/权限编码/数据范围(取所有角色的并集,数据范围取最宽)
///
public static async Task<(List roleIds, List roleNames, List permissions, byte dataScope)>
LoadUserAuthAsync(long userId)
{
var db = SqlSugarContext.DbContext;
var roleIds = await db.Queryable()
.Where(x => x.UserId == userId)
.Select(x => x.RoleId)
.ToListAsync();
var roleList = new List();
if (roleIds.Count > 0)
roleList = await db.Queryable()
.Where(x => roleIds.Contains(x.Id) && x.IsDel == 0).ToListAsync();
var permissionCodes = new List();
if (roleIds.Count > 0)
{
var permIds = await db.Queryable()
.Where(x => roleIds.Contains(x.RoleId))
.Select(x => x.PermissionId)
.ToListAsync();
if (permIds.Count > 0)
{
var perms = await db.Queryable()
.Where(x => permIds.Contains(x.Id) && x.IsDel == 0).ToListAsync();
permissionCodes = perms.Select(p => p.Code).Distinct().ToList();
}
}
// 数据范围取最宽:任一角色是"全部"即为全部
var dataScope = roleList.Count > 0 ? roleList.Min(r => r.DataScope) : (byte)2;
return (roleIds, roleList.Select(r => r.Name).ToList(), permissionCodes, dataScope);
}
/// 生成 JWT:把用户身份 + 角色 + 权限编码全部写入 Claims(授权过滤器免查库)
private string GenerateJwt(UserEntity user, List roleIds, List roleNames, List permissions, byte dataScope)
{
var jwt = _configuration.GetSection("Jwt");
var secret = jwt["SecretKey"]!;
var issuer = jwt["Issuer"] ?? "IOT-API";
var audience = jwt["Audience"] ?? "IOT-Web";
var expireMinutes = double.TryParse(jwt["ExpireMinutes"], out var m) ? m : 480;
var claims = new List
{
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Name, user.UserName),
new("roleIds", string.Join(",", roleIds)),
new("roleNames", string.Join(",", roleNames)),
new("permissions", string.Join(",", permissions)),
new("dataScope", dataScope.ToString()),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N"))
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer, audience, claims,
expires: DateTime.Now.AddMinutes(expireMinutes), signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// 写一条审计日志
private static async Task WriteAuditAsync(long userId, string? userName, long? roleId, string? roleName,
string operationType, string operationTarget, long targetId, long deviceId,
string? oldValue, string? newValue, string? ip, string? description)
{
var now = DateTime.Now;
await SqlSugarContext.DbContext.Insertable(new AuditLogEntity
{
UserId = userId,
UserName = userName ?? "",
RoleId = roleId ?? 0,
RoleName = roleName ?? "",
OperationType = operationType,
OperationTarget = operationTarget,
TargetId = targetId,
DeviceId = deviceId,
OldValue = oldValue,
NewValue = newValue,
Ip = ip,
Description = description,
OperateTime = now,
CreateTime = now
}).ExecuteCommandAsync();
}
}
}