first commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Attendance;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Attendance;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Attendance;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/attendance")]
|
||||
[Authorize]
|
||||
public class AttendanceController : ControllerBase
|
||||
{
|
||||
private readonly IAttendanceService _service;
|
||||
|
||||
public AttendanceController(IAttendanceService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
// POST /api/attendance/punch 打卡
|
||||
[HttpPost("punch")]
|
||||
public async Task<IActionResult> Punch()
|
||||
{
|
||||
var result = await _service.PunchAsync(GetUserId());
|
||||
return Ok(ApiResult<PunchResultDto>.Ok(result));
|
||||
}
|
||||
|
||||
// GET /api/attendance/my 我的考勤记录
|
||||
[HttpGet("my")]
|
||||
public async Task<IActionResult> GetMy([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
var result = await _service.GetMyRecordsAsync(GetUserId(), pageIndex, pageSize);
|
||||
return Ok(ApiResult<PageResult<AttendanceDto>>.Ok(result));
|
||||
}
|
||||
|
||||
// GET /api/attendance/my/summary 我的考勤统计
|
||||
[HttpGet("my/summary")]
|
||||
public async Task<IActionResult> GetMySummary([FromQuery] string month)
|
||||
{
|
||||
var result = await _service.GetMySummaryAsync(GetUserId(), month);
|
||||
return Ok(ApiResult<AttendanceSummaryDto>.Ok(result));
|
||||
}
|
||||
|
||||
// GET /api/attendance 全部考勤(管理员)
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll([FromQuery] AttendanceQueryDto query)
|
||||
{
|
||||
var result = await _service.GetAllRecordsAsync(query);
|
||||
return Ok(ApiResult<PageResult<AttendanceDto>>.Ok(result));
|
||||
}
|
||||
|
||||
// POST /api/attendance/leave 申请请假
|
||||
[HttpPost("leave")]
|
||||
public async Task<IActionResult> ApplyLeave([FromBody] CreateLeaveDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _service.ApplyLeaveAsync(GetUserId(), dto);
|
||||
return Ok(ApiResult<LeaveRequestDto>.Ok(result, "申请已提交"));
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code));
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/attendance/leave/my 我的请假
|
||||
[HttpGet("leave/my")]
|
||||
public async Task<IActionResult> GetMyLeaves()
|
||||
{
|
||||
var result = await _service.GetMyLeavesAsync(GetUserId());
|
||||
return Ok(ApiResult<List<LeaveRequestDto>>.Ok(result));
|
||||
}
|
||||
|
||||
// GET /api/attendance/leave 全部请假(管理员)
|
||||
[HttpGet("leave")]
|
||||
public async Task<IActionResult> GetAllLeaves([FromQuery] string status)
|
||||
{
|
||||
var result = await _service.GetAllLeavesAsync(status);
|
||||
return Ok(ApiResult<List<LeaveRequestDto>>.Ok(result));
|
||||
}
|
||||
|
||||
// PUT /api/attendance/leave/{id}/approve 审批请假
|
||||
[HttpPut("leave/{id}/approve")]
|
||||
public async Task<IActionResult> ApproveLeave(long id, [FromBody] ApproveLeaveDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var approverName = User.FindFirst("realName")?.Value ?? "管理员";
|
||||
var result = await _service.ApproveLeaveAsync(id, GetUserId(), approverName, dto);
|
||||
return Ok(ApiResult<LeaveRequestDto>.Ok(result, "审批完成"));
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code));
|
||||
}
|
||||
}
|
||||
|
||||
private long GetUserId() => long.Parse(User.FindFirst("id")!.Value);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Auth;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Auth;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Auth;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public AuthController(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _authService.LoginAsync(dto);
|
||||
return Ok(ApiResult<LoginResultDto>.Ok(result, "登录成功"));
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
public async Task<IActionResult> Refresh([FromBody] RefreshTokenDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _authService.RefreshTokenAsync(dto.RefreshToken);
|
||||
return Ok(ApiResult<LoginResultDto>.Ok(result, "刷新成功"));
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code));
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout([FromBody] RefreshTokenDto dto)
|
||||
{
|
||||
await _authService.RevokeRefreshTokenAsync(dto.RefreshToken);
|
||||
return Ok(ApiResult<object>.Ok(null, "已退出登录"));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("me")]
|
||||
public async Task<IActionResult> GetCurrentUser()
|
||||
{
|
||||
var id = GetUserId();
|
||||
var user = await _authService.GetCurrentUserAsync(id);
|
||||
return Ok(ApiResult<EmployeeInfoDto>.Ok(user));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("menus")]
|
||||
public async Task<IActionResult> GetMenus()
|
||||
{
|
||||
var id = GetUserId();
|
||||
var menus = await _authService.GetUserMenusAsync(id);
|
||||
return Ok(ApiResult<List<MenuDto>>.Ok(menus));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("permissions")]
|
||||
public async Task<IActionResult> GetPermissions()
|
||||
{
|
||||
var id = GetUserId();
|
||||
var perms = await _authService.GetUserPermissionsAsync(id);
|
||||
return Ok(ApiResult<List<string>>.Ok(perms));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("password")]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _authService.ChangePasswordAsync(GetUserId(), dto);
|
||||
return Ok(ApiResult<object>.Ok(null, "密码修改成功"));
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code));
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("profile")]
|
||||
public async Task<IActionResult> UpdateProfile([FromBody] UpdateProfileDto dto)
|
||||
{
|
||||
await _authService.UpdateProfileAsync(GetUserId(), dto);
|
||||
return Ok(ApiResult<object>.Ok(null, "资料更新成功"));
|
||||
}
|
||||
|
||||
private long GetUserId() => long.Parse(User.FindFirst("id")!.Value);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Base;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Base;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Base;
|
||||
|
||||
// ==================== 工艺庄口 ====================
|
||||
[ApiController]
|
||||
[Route("api/craftzhuangkou")]
|
||||
[Authorize]
|
||||
public class CraftZhuangKouController : ControllerBase
|
||||
{
|
||||
private readonly ICraftZhuangKouService _svc;
|
||||
public CraftZhuangKouController(ICraftZhuangKouService svc) => _svc = svc;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20, [FromQuery] string keyword = "", [FromQuery] int? year = null)
|
||||
=> Ok(ApiResult<PageResult<CraftZhuangKouDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, keyword, year)));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CraftZhuangKouCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<CraftZhuangKouDto>.Ok(await _svc.CreateAsync(dto), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] CraftZhuangKouCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<CraftZhuangKouDto>.Ok(await _svc.UpdateAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生丝规格 ====================
|
||||
[ApiController]
|
||||
[Route("api/shengsiguige")]
|
||||
[Authorize]
|
||||
public class ShengsiGuigeController : ControllerBase
|
||||
{
|
||||
private readonly IShengsiGuigeService _svc;
|
||||
public ShengsiGuigeController(IShengsiGuigeService svc) => _svc = svc;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20, [FromQuery] string keyword = "")
|
||||
=> Ok(ApiResult<PageResult<ShengsiGuigeDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, keyword)));
|
||||
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAllGuige()
|
||||
=> Ok(ApiResult<List<string>>.Ok(await _svc.GetAllGuigeAsync()));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] ShengsiGuigeDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<ShengsiGuigeDto>.Ok(await _svc.CreateAsync(dto), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] ShengsiGuigeDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<ShengsiGuigeDto>.Ok(await _svc.UpdateAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生丝定级指标 ====================
|
||||
[ApiController]
|
||||
[Route("api/shengsidingjizhibiao")]
|
||||
[Authorize]
|
||||
public class ShengsiDingjizhibiaoController : ControllerBase
|
||||
{
|
||||
private readonly IShengsiDingjizhibiaoService _svc;
|
||||
public ShengsiDingjizhibiaoController(IShengsiDingjizhibiaoService svc) => _svc = svc;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20, [FromQuery] string keyword = "", [FromQuery] int? standard = null, [FromQuery] string guige = "")
|
||||
=> Ok(ApiResult<PageResult<ShengsiDingjizhibiaoDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, keyword, standard, guige)));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] ShengsiDingjizhibiaoDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<ShengsiDingjizhibiaoDto>.Ok(await _svc.CreateAsync(dto), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] ShengsiDingjizhibiaoDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<ShengsiDingjizhibiaoDto>.Ok(await _svc.UpdateAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 机组车台 ====================
|
||||
[ApiController]
|
||||
[Route("api/jizuchetai")]
|
||||
[Authorize]
|
||||
public class JizuChetaiController : ControllerBase
|
||||
{
|
||||
private readonly IJizuChetaiService _svc;
|
||||
public JizuChetaiController(IJizuChetaiService svc) => _svc = svc;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20, [FromQuery] string keyword = "")
|
||||
=> Ok(ApiResult<PageResult<JizuChetaiDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, keyword)));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] JizuChetaiDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<JizuChetaiDto>.Ok(await _svc.CreateAsync(dto), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] JizuChetaiDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<JizuChetaiDto>.Ok(await _svc.UpdateAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Notice;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Auth;
|
||||
using F9WebAiTest.Application.Services.Notice;
|
||||
using F9WebAiTest.Domain.Entities;
|
||||
using F9WebAiTest.Domain.Entities.Notice;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Notice;
|
||||
|
||||
// ==================== 消息类型管理(管理员) ====================
|
||||
[ApiController]
|
||||
[Route("api/notification/type")]
|
||||
[Authorize]
|
||||
public class NotificationTypeController : ControllerBase
|
||||
{
|
||||
private readonly INotificationService _svc;
|
||||
public NotificationTypeController(INotificationService svc) => _svc = svc;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetList()
|
||||
=> Ok(ApiResult<List<NotificationTypeDto>>.Ok(await _svc.GetTypeListAsync()));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] NotificationTypeCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<NotificationTypeDto>.Ok(await _svc.CreateTypeAsync(dto, GetUserId()), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] NotificationTypeCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<NotificationTypeDto>.Ok(await _svc.UpdateTypeAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteTypeAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
private long GetUserId() => long.Parse(User.FindFirst("id")!.Value);
|
||||
}
|
||||
|
||||
// ==================== 用户端:订阅 + 消息 ====================
|
||||
[ApiController]
|
||||
[Route("api/notification")]
|
||||
[Authorize]
|
||||
public class NotificationController : ControllerBase
|
||||
{
|
||||
private readonly INotificationService _svc;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IAuthService _authService;
|
||||
public NotificationController(INotificationService svc, IMediator mediator, IAuthService authService)
|
||||
{
|
||||
_svc = svc;
|
||||
_mediator = mediator;
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
// 可订阅的消息类型列表(有权限的才可见/可订阅)
|
||||
[HttpGet("subscribe")]
|
||||
public async Task<IActionResult> GetSubscribeList()
|
||||
=> Ok(ApiResult<List<NotificationTypeDto>>.Ok(await _svc.GetSubscribeListAsync(GetUserId())));
|
||||
|
||||
// 订阅 / 取消订阅
|
||||
[HttpPost("subscribe")]
|
||||
public async Task<IActionResult> Subscribe([FromBody] SubscribeDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _svc.SubscribeAsync(GetUserId(), dto.NotificationTypeId, dto.Subscribe);
|
||||
return Ok(ApiResult<object>.Ok(null, dto.Subscribe ? "订阅成功" : "已取消订阅"));
|
||||
}
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 我的消息列表(readStatus: 0未读 1已读;category: 0通知 1待办 2公告)
|
||||
[HttpGet("messages")]
|
||||
public async Task<IActionResult> GetMyMessages([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20, [FromQuery] int? readStatus = null, [FromQuery] int? category = null)
|
||||
=> Ok(ApiResult<PageResult<NotificationMessageDto>>.Ok(await _svc.GetMyMessagesAsync(GetUserId(), pageIndex, pageSize, readStatus, category)));
|
||||
|
||||
// 我的待办列表
|
||||
[HttpGet("todos")]
|
||||
public async Task<IActionResult> GetMyTodos([FromQuery] int? status = null)
|
||||
=> Ok(ApiResult<List<TodoItemDto>>.Ok(await _svc.GetMyTodosAsync(GetUserId(), status)));
|
||||
|
||||
// 完成待办
|
||||
[HttpPost("todos/{id}/complete")]
|
||||
public async Task<IActionResult> CompleteTodo(long id, [FromBody] CompleteTodoDto dto)
|
||||
{
|
||||
try { await _svc.CompleteTodoAsync(GetUserId(), id, dto.HandleResult); return Ok(ApiResult<object>.Ok(null, "已完成")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 标记已读
|
||||
[HttpPut("messages/{id}/read")]
|
||||
public async Task<IActionResult> MarkRead(long id)
|
||||
{
|
||||
try { await _svc.MarkReadAsync(GetUserId(), id); return Ok(ApiResult<object>.Ok(null)); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 全部已读
|
||||
[HttpPut("messages/read-all")]
|
||||
public async Task<IActionResult> MarkAllRead()
|
||||
{
|
||||
await _svc.MarkAllReadAsync(GetUserId());
|
||||
return Ok(ApiResult<object>.Ok(null));
|
||||
}
|
||||
|
||||
// 未读数量
|
||||
[HttpGet("unread-count")]
|
||||
public async Task<IActionResult> GetUnreadCount()
|
||||
=> Ok(ApiResult<UnreadCountDto>.Ok(new UnreadCountDto { Count = await _svc.GetUnreadCountAsync(GetUserId()) }));
|
||||
|
||||
// 触发一条消息(模拟业务系统触发,用于演示/测试)
|
||||
[HttpPost("trigger")]
|
||||
public async Task<IActionResult> Trigger([FromBody] TriggerDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msgId = await _mediator.Send(new CreateNotificationCommand
|
||||
{
|
||||
NotificationTypeCode = dto.Code,
|
||||
BusinessType = dto.BusinessType ?? "Manual",
|
||||
BusinessId = dto.BusinessId ?? Guid.NewGuid().ToString("N"),
|
||||
Title = dto.Title ?? "测试消息",
|
||||
Content = dto.Content ?? "这是一条测试消息",
|
||||
Priority = dto.Priority ?? NotificationPriority.Normal,
|
||||
RoutePath = dto.RoutePath ?? "",
|
||||
ForceReceivers = dto.AssigneeIds ?? new List<long>(),
|
||||
AssigneeIds = dto.AssigneeIds ?? new List<long>(),
|
||||
CreatorId = GetUserId(),
|
||||
CreatorName = User.FindFirst("realName")?.Value ?? "系统"
|
||||
});
|
||||
return Ok(ApiResult<object>.Ok(new { id = msgId }, "消息已创建并即时推送给在线接收人"));
|
||||
}
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 发送广播通知(拥有通知广播权限的用户可发送,空接收人 = 订阅广播)
|
||||
[HttpPost("broadcast")]
|
||||
public async Task<IActionResult> Broadcast([FromBody] BroadcastNotificationDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var perms = await _authService.GetUserPermissionsAsync(userId);
|
||||
if (!perms.Contains("notification:broadcast"))
|
||||
return StatusCode(403, ApiResult<object>.Fail("您没有发送广播通知的权限", 403));
|
||||
|
||||
var msgId = await _mediator.Send(new CreateNotificationCommand
|
||||
{
|
||||
NotificationTypeCode = "Broadcast",
|
||||
BusinessType = "Broadcast",
|
||||
BusinessId = Guid.NewGuid().ToString("N"),
|
||||
Title = dto.Title,
|
||||
Content = dto.Content,
|
||||
Priority = dto.Priority ?? NotificationPriority.Normal,
|
||||
RoutePath = dto.RoutePath ?? "",
|
||||
ForceReceivers = new List<long>(),
|
||||
CreatorId = userId,
|
||||
CreatorName = User.FindFirst("realName")?.Value ?? "系统"
|
||||
});
|
||||
return Ok(ApiResult<object>.Ok(new { id = msgId }, "广播消息已创建"));
|
||||
}
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
private long GetUserId() => long.Parse(User.FindFirst("id")!.Value);
|
||||
}
|
||||
|
||||
public class TriggerDto
|
||||
{
|
||||
public string Code { get; set; } = "TeyeFound";
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
public string BusinessType { get; set; }
|
||||
public string BusinessId { get; set; }
|
||||
public NotificationPriority? Priority { get; set; }
|
||||
public string RoutePath { get; set; }
|
||||
public List<long> AssigneeIds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Prod;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Prod;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/hbjy")]
|
||||
[Authorize]
|
||||
public class HbjyController : ControllerBase
|
||||
{
|
||||
private readonly IHbjyService _svc;
|
||||
public HbjyController(IHbjyService svc) => _svc = svc;
|
||||
|
||||
// 黑板检验列表
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged(
|
||||
[FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] DateTime? date = null, [FromQuery] long? deptId = null)
|
||||
=> Ok(ApiResult<PageResult<HbjyListItemDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, date, deptId)));
|
||||
|
||||
// 获取检验编辑数据
|
||||
[HttpGet("{lsjlId}")]
|
||||
public async Task<IActionResult> GetEdit(long lsjlId)
|
||||
{
|
||||
try { return Ok(ApiResult<HbjyEditDto>.Ok(await _svc.GetEditAsync(lsjlId))); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 保存黑板检验
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Save([FromBody] HbjySaveDto dto)
|
||||
{
|
||||
try { await _svc.SaveAsync(dto); return Ok(ApiResult<object>.Ok(null, "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Base;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Base;
|
||||
using F9WebAiTest.Application.Services.Prod;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Prod;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/lsjl")]
|
||||
[Authorize]
|
||||
public class LsjlController : ControllerBase
|
||||
{
|
||||
private readonly ILsjlService _svc;
|
||||
private readonly IJizuChetaiService _jzSvc;
|
||||
|
||||
public LsjlController(ILsjlService svc, IJizuChetaiService jzSvc)
|
||||
{
|
||||
_svc = svc;
|
||||
_jzSvc = jzSvc;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged(
|
||||
[FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] DateTime? date = null, [FromQuery] long? deptId = null, [FromQuery] string keyword = "")
|
||||
=> Ok(ApiResult<PageResult<LsjlDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, date, deptId, keyword)));
|
||||
|
||||
// 根据车间+机组查询车台号范围
|
||||
[HttpGet("chetai")]
|
||||
public async Task<IActionResult> GetChetai([FromQuery] long deptId, [FromQuery] int jizu)
|
||||
=> Ok(ApiResult<JizuChetaiDto>.Ok(await _jzSvc.GetByDepIdAndJizuAsync(deptId, jizu)));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] LsjlCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<LsjlDto>.Ok(await _svc.CreateAsync(dto), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] LsjlCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<LsjlDto>.Ok(await _svc.UpdateAsync(id, dto), "更新成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Prod;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.Prod;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/xdjy")]
|
||||
[Authorize]
|
||||
public class XdjyController : ControllerBase
|
||||
{
|
||||
private readonly IXdjyService _svc;
|
||||
public XdjyController(IXdjyService svc) => _svc = svc;
|
||||
|
||||
// 纤度检验列表(落丝记录 + 检验汇总)
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged(
|
||||
[FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] DateTime? date = null, [FromQuery] long? deptId = null)
|
||||
=> Ok(ApiResult<PageResult<XdjyListItemDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, date, deptId)));
|
||||
|
||||
// 获取检验编辑数据
|
||||
[HttpGet("{lsjlId}")]
|
||||
public async Task<IActionResult> GetEdit(long lsjlId)
|
||||
{
|
||||
try { return Ok(ApiResult<XdjyEditDto>.Ok(await _svc.GetEditAsync(lsjlId))); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// 保存纤度检验
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Save([FromBody] XdjySaveDto dto)
|
||||
{
|
||||
try { await _svc.SaveAsync(dto); return Ok(ApiResult<object>.Ok(null, "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.CQRS.SYS.Departments.Commands;
|
||||
using F9WebAiTest.Application.CQRS.SYS.Departments.Queries;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>系统部门管理(DDD + CQRS)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/department")]
|
||||
[Authorize]
|
||||
public class SysDepartmentController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public SysDepartmentController(IMediator mediator) => _mediator = mediator;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetDepartmentAllQuery())));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Department_CreateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _mediator.Send(command) }, "创建成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] Department_UpdateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Department_DelCommand(id)), "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}/active")]
|
||||
public async Task<IActionResult> ActiveOrDeactivate(long id, [FromBody] Department_ActiveOrDeactivateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "操作成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.CQRS.SYS.RBAC.Menus.Commands;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.RBAC.Menus;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>系统菜单管理(DDD + CQRS)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/menu")]
|
||||
[Authorize]
|
||||
public class SysMenuController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IMenuReadRepository _menuReadRepository;
|
||||
public SysMenuController(IMediator mediator, IMenuReadRepository menuReadRepository)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_menuReadRepository = menuReadRepository;
|
||||
}
|
||||
|
||||
[HttpGet("tree")]
|
||||
public async Task<IActionResult> GetTree()
|
||||
=> Ok(ApiResult<object>.Ok(await _menuReadRepository.GetTreeMenuAsync()));
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll([FromQuery] string like = "", [FromQuery] long? parentId = null)
|
||||
=> Ok(ApiResult<object>.Ok(await _menuReadRepository.GetAllMenuAsync(like, parentId)));
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _menuReadRepository.GetMenuByIdForEditAsync(id)));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Menu_CreateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _mediator.Send(command) }, "创建成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] Menu_UpdateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Menu_DelCommand(id)), "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}/active")]
|
||||
public async Task<IActionResult> ActiveOrDeactivate(long id, [FromBody] Menu_ActiveOrDeactivateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "操作成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.CQRS.SYS.RBAC.Roles.Commands;
|
||||
using F9WebAiTest.Application.CQRS.SYS.RBAC.Roles.Queries;
|
||||
using F9WebAiTest.Domain.Enums.SYS.RBAC;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>系统角色管理(DDD + CQRS)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/role")]
|
||||
[Authorize]
|
||||
public class SysRoleController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public SysRoleController(IMediator mediator) => _mediator = mediator;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetRoleAllQuery())));
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetRoleByIdForEditQuery(id))));
|
||||
|
||||
[HttpGet("{id}/permissions")]
|
||||
public async Task<IActionResult> GetPermissions(long id, [FromQuery] Resource_Type_Enum resourceType)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetRolePermissionsByResourceTypeQuery(id, resourceType))));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Role_CreateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _mediator.Send(command) }, "创建成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] Role_UpdateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Role_DelCommand(id)), "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}/active")]
|
||||
public async Task<IActionResult> ActiveOrDeactivate(long id, [FromBody] Role_ActiveOrDeactivateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "操作成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPost("{id}/permissions")]
|
||||
public async Task<IActionResult> AssignPermission(long id, [FromBody] RolePermission_AssignCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { RoleId = id }), "绑定成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/permissions/{rpId}")]
|
||||
public async Task<IActionResult> RemovePermission(long id, long rpId, [FromQuery] Resource_Type_Enum resourceType)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new RolePermission_RemoveCommand(rpId, resourceType)), "解绑成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.CQRS.SYS.RBAC.Users.Commands;
|
||||
using F9WebAiTest.Application.CQRS.SYS.RBAC.Users.Queries;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>系统用户管理(DDD + CQRS)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/user")]
|
||||
[Authorize]
|
||||
public class SysUserController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public SysUserController(IMediator mediator) => _mediator = mediator;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] long departmentId = 0, [FromQuery] string like = "", [FromQuery] int page = 1, [FromQuery] int pageSize = 10)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetUserPagedQuery(departmentId, like, page, pageSize))));
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetUserByIdQuery(id))));
|
||||
|
||||
[HttpGet("{id}/roles")]
|
||||
public async Task<IActionResult> GetUserRoles(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetUserRolesByUserIdQuery(id))));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] User_CreateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _mediator.Send(command) }, "创建成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] User_UpdateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new User_DelCommand(id)), "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}/active")]
|
||||
public async Task<IActionResult> ActiveOrDeactivate(long id, [FromBody] User_ActiveOrDeactivateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "操作成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}/reset-password")]
|
||||
public async Task<IActionResult> ResetPassword(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new User_ResetPasswordCommand(id)), "密码已重置")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPost("{id}/roles")]
|
||||
public async Task<IActionResult> AssignRole(long id, [FromBody] UserRole_AssignCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { UserId = id }), "绑定成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/roles/{roleId}")]
|
||||
public async Task<IActionResult> RemoveRole(long id, long roleId)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new UserRole_RemoveCommand(id, roleId)), "解绑成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<RootNamespace>F9WebAiTest.Api</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\F9WebAiTest.Domain\F9WebAiTest.Domain.csproj" />
|
||||
<ProjectReference Include="..\F9WebAiTest.Application\F9WebAiTest.Application.csproj" />
|
||||
<ProjectReference Include="..\F9WebAiTest.Infrastructure\F9WebAiTest.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="14.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace F9WebAiTest.Api.Hubs;
|
||||
|
||||
/// <summary>在线用户登记表(用于判断用户是否在线,离线则走"拉取兜底")</summary>
|
||||
public static class OnlineUserRegistry
|
||||
{
|
||||
private static readonly ConcurrentDictionary<long, byte> _users = new();
|
||||
|
||||
public static void Add(long userId) => _users[userId] = 0;
|
||||
|
||||
public static void Remove(long userId) => _users.TryRemove(userId, out _);
|
||||
|
||||
public static bool IsOnline(long userId) => _users.ContainsKey(userId);
|
||||
}
|
||||
|
||||
/// <summary>通知推送 Hub(客户端连接后按 UserId 标识,服务端用 Clients.User(userId) 定向推送)</summary>
|
||||
public class NotificationHub : Hub
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
if (userId > 0) OnlineUserRegistry.Add(userId);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
if (userId > 0) OnlineUserRegistry.Remove(userId);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
private long GetUserId()
|
||||
{
|
||||
var raw = Context.User?.FindFirst("id")?.Value;
|
||||
return long.TryParse(raw, out var id) ? id : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>自定义用户标识:使用 JWT 中的 "id" 声明作为 SignalR 的 UserIdentifier</summary>
|
||||
public class NotificationUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string GetUserId(HubConnectionContext connection)
|
||||
=> connection.User?.FindFirst("id")?.Value ?? connection.ConnectionId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace F9WebAiTest.Api.JsonConverters;
|
||||
|
||||
/// <summary>
|
||||
/// 将 long 序列化为字符串,避免雪花 ID(超过 JS Number 安全整数 2^53)在前后端传递时丢失精度。
|
||||
/// 反序列化时兼容字符串与数字两种形式。
|
||||
/// </summary>
|
||||
public class LongToStringJsonConverter : JsonConverter<long>
|
||||
{
|
||||
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> reader.TokenType == JsonTokenType.String
|
||||
? long.Parse(reader.GetString()!)
|
||||
: reader.GetInt64();
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Text;
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Api.Hubs;
|
||||
using F9WebAiTest.Api.Services;
|
||||
using F9WebAiTest.Application;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using F9WebAiTest.Application.Services.Notice;
|
||||
using F9WebAiTest.Infrastructure;
|
||||
using F9WebAiTest.Infrastructure.Data;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 分层注册
|
||||
builder.Services.AddInfrastructure(builder.Configuration);
|
||||
builder.Services.AddApplication();
|
||||
|
||||
// JWT 认证
|
||||
var jwtKey = builder.Configuration["Jwt:Key"];
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
|
||||
};
|
||||
// SignalR 通过 query string 传递 token,需从 access_token 读取
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
if (!string.IsNullOrEmpty(accessToken) && context.HttpContext.Request.Path.StartsWithSegments("/hubs"))
|
||||
{
|
||||
context.Token = accessToken;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
// long 序列化为字符串,避免雪花 ID 超出 JS 安全整数范围导致精度丢失
|
||||
options.JsonSerializerOptions.Converters.Add(new F9WebAiTest.Api.JsonConverters.LongToStringJsonConverter());
|
||||
});
|
||||
|
||||
// SignalR 实时推送 + 自定义用户标识 + 即时推送器(创建消息时直接推送,无后台投递服务)
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddSingleton<IUserIdProvider, NotificationUserIdProvider>();
|
||||
builder.Services.AddScoped<INotificationPusher, NotificationPusher>();
|
||||
|
||||
// 业务状态变更事件 → SignalR 推送(多端状态同步)
|
||||
builder.Services.AddScoped<INotificationHandler<BusinessChangedEvent>, BusinessChangedEventHandler>();
|
||||
|
||||
// CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
|
||||
});
|
||||
|
||||
// NSwag(OpenAPI 文档 + Swagger UI + 可生成 TS/C# 客户端)
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddOpenApiDocument(settings =>
|
||||
{
|
||||
settings.Title = "企业 RBAC 管理系统 API";
|
||||
settings.Version = "v1";
|
||||
settings.Description = "基于 NSwag 生成的 OpenAPI 文档,可用于生成 TypeScript/C# 客户端";
|
||||
settings.AddSecurity("Bearer", new NSwag.OpenApiSecurityScheme
|
||||
{
|
||||
Type = NSwag.OpenApiSecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "请输入 access token(格式:Bearer {token})"
|
||||
});
|
||||
settings.OperationProcessors.Add(new NSwag.Generation.Processors.Security.AspNetCoreOperationSecurityScopeProcessor("Bearer"));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 初始化数据库(FreeSql CodeFirst 建表 + 种子)
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var fsql = scope.ServiceProvider.GetRequiredService<IFreeSql>();
|
||||
var snowflake = scope.ServiceProvider.GetRequiredService<F9WebAiTest.Application.Interfaces.SnowflakeID.ISnowflakeIdGenerator>();
|
||||
DbSeeder.Seed(fsql, snowflake);
|
||||
}
|
||||
|
||||
// NSwag 中间件(swagger 文档 + UI)
|
||||
app.UseOpenApi();
|
||||
app.UseSwaggerUi();
|
||||
|
||||
app.UseCors("AllowAll");
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapHub<NotificationHub>("/hubs/notification");
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"F9WebAiTest.Api": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:3000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using F9WebAiTest.Api.Hubs;
|
||||
using F9WebAiTest.Application.Services;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace F9WebAiTest.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 业务状态变更事件的 SignalR 推送处理器。
|
||||
/// 业务服务发布 BusinessChangedEvent 后,推送给所有在线客户端,
|
||||
/// 各端监听 "BusinessChanged" 后刷新已打开的相关页面,实现多端实时同步。
|
||||
/// </summary>
|
||||
public class BusinessChangedEventHandler : INotificationHandler<BusinessChangedEvent>
|
||||
{
|
||||
private readonly IHubContext<NotificationHub> _hubContext;
|
||||
|
||||
public BusinessChangedEventHandler(IHubContext<NotificationHub> hubContext)
|
||||
=> _hubContext = hubContext;
|
||||
|
||||
public Task Handle(BusinessChangedEvent e, CancellationToken ct)
|
||||
=> _hubContext.Clients.All.SendAsync("BusinessChanged", e, ct);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// 已移除:消息投递不再使用后台服务(BackgroundService)与轮询。
|
||||
//
|
||||
// 现在的投递方式(即时推送):
|
||||
// CreateNotificationCommandHandler(F9WebAiTest.Application/Services/Notice/NotificationMediatR.cs)
|
||||
// 在消息创建后立即:
|
||||
// 1. 确定接收人(ForceReceivers 精确送达,否则按消息类型订阅关系广播);
|
||||
// 2. 写入 NotificationReceiver(MessageId + UserId 幂等);
|
||||
// 3. 通过 INotificationPusher(实现在 F9WebAiTest.Api/Services/NotificationPusher.cs)经 SignalR 直接推送。
|
||||
//
|
||||
// 本文件已无任何代码,可直接删除。
|
||||
@@ -0,0 +1,45 @@
|
||||
using F9WebAiTest.Api.Hubs;
|
||||
using F9WebAiTest.Application.Dtos.Notice;
|
||||
using F9WebAiTest.Application.Services.Notice;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace F9WebAiTest.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 消息实时推送器实现:基于 SignalR 的 IHubContext 直接推送。
|
||||
/// 无后台队列、无轮询——消息创建时同步完成投递与推送。
|
||||
///
|
||||
/// 在线判定:OnlineUserRegistry(Hub 连接/断开时登记的内存表)。
|
||||
/// 之所以需要显式判定而不能依赖推送异常:SignalR 对"无连接的 Clients.User 调用"是静默成功的,
|
||||
/// 不判定的话离线用户会被误记为推送成功。
|
||||
///
|
||||
/// 用户标识:由 NotificationUserIdProvider 统一取 JWT 的 "id" 声明,
|
||||
/// 因此 Clients.User(userId) 可精准命中该用户的所有在线连接(支持多端同时在线)。
|
||||
/// </summary>
|
||||
public class NotificationPusher : INotificationPusher
|
||||
{
|
||||
private readonly IHubContext<NotificationHub> _hubContext;
|
||||
|
||||
public NotificationPusher(IHubContext<NotificationHub> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsOnline(long userId) => OnlineUserRegistry.IsOnline(userId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> PushNewMessageAsync(long userId, NotificationMessageDto message, CancellationToken ct = default)
|
||||
{
|
||||
// 离线用户:不推送(消息已持久化,上线后拉取兜底),返回 false 由调用方标记 IsOffline
|
||||
if (!OnlineUserRegistry.IsOnline(userId)) return false;
|
||||
|
||||
await _hubContext.Clients.User(userId.ToString()).SendAsync("ReceiveNotification", message, ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task PushUnreadCountAsync(long userId, int unreadCount, int todoCount, CancellationToken ct = default)
|
||||
=> _hubContext.Clients.User(userId.ToString())
|
||||
.SendAsync("UnreadCountChanged", new { unreadCount, todoCount }, ct);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:3000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "f9-web-ai-test-rbac-secret-key-2026-please-change-in-production",
|
||||
"Issuer": "F9WebAiTest",
|
||||
"Audience": "F9WebAiTestClient",
|
||||
"AccessTokenExpireMinutes": 120,
|
||||
"RefreshTokenExpireDays": 7
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Default": "Data Source=f9_web_ai_test_rbac.db"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user