refactor: 拆分微信API到独立项目并迁移相关代码
1. 新增WeChatApi独立项目,将原WebApi中的微信相关控制器迁移至新项目 2. 新增后台权限管理相关实体、服务接口和基础控制器 3. 完善管理员用户服务,增加角色关联查询和赋值逻辑 4. 修复WebApi Swagger文档过滤微信API版本的问题 5. 更新解决方案文件,添加新的微信API项目引用 6. 新增微信API基础配置文件和项目属性配置
This commit is contained in:
@ -1,129 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- CommunityMessage 社区预置消息表 - 20条模拟测试数据
|
|
||||||
-- 生成时间: 2026-06-12
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
INSERT INTO [dbo].[CommunityMessage]
|
|
||||||
([Id], [Status], [IsDeleted], [CreatedBy], [CreatedAt], [UpdatedBy], [UpdatedAt],
|
|
||||||
[JournalId], [UserId], [UserJournalId], [JournalTaskId], [JournalTaskAnswerId],
|
|
||||||
[Content], [ImageUrl], [SortOrder], [IsActive], [Type], [LikeCount], [IsFeatured])
|
|
||||||
VALUES
|
|
||||||
-- 1. 文章类型 - 期刊导读
|
|
||||||
(1916234567890001, 1, 0, N'系统管理员', '2026-05-01 09:00:00', NULL, NULL,
|
|
||||||
1001, 5001, 8001, 3001, 6001,
|
|
||||||
N'本期期刊聚焦人工智能在教育领域的创新应用,欢迎各位读者踊跃讨论。',
|
|
||||||
NULL, 1, 1, 1, 42, 1),
|
|
||||||
|
|
||||||
-- 2. 引用类型 - 名人名言
|
|
||||||
(1916234567890002, 1, 0, N'编辑小王', '2026-05-02 10:30:00', NULL, NULL,
|
|
||||||
1001, 5002, 8002, 3001, 6002,
|
|
||||||
N'"教育不是灌输,而是点燃火焰。" —— 苏格拉底',
|
|
||||||
NULL, 2, 1, 2, 88, 1),
|
|
||||||
|
|
||||||
-- 3. 公告类型 - 活动通知
|
|
||||||
(1916234567890003, 1, 0, N'系统管理员', '2026-05-03 14:00:00', N'系统管理员', '2026-05-03 15:00:00',
|
|
||||||
1001, 5003, 8003, 3002, 6003,
|
|
||||||
N'【活动通知】第十二期线上读书分享会将于本周六晚8点举行,主题为"科技与人文的对话",欢迎大家报名参加!',
|
|
||||||
N'/uploads/images/activity_notice_202605.png', 1, 1, 3, 156, 1),
|
|
||||||
|
|
||||||
-- 4. 文章类型 - 读者投稿
|
|
||||||
(1916234567890004, 1, 0, N'读者张三', '2026-05-04 08:15:00', NULL, NULL,
|
|
||||||
1002, 5004, 8004, 3003, 6004,
|
|
||||||
N'阅读了本期的深度报道《数字时代的阅读习惯变迁》,深有感触。作为一个从纸质书时代走过来的人,既感慨科技带来的便利,也怀念翻页时的触感。',
|
|
||||||
NULL, 3, 1, 1, 35, 0),
|
|
||||||
|
|
||||||
-- 5. 引用类型 - 期刊摘录
|
|
||||||
(1916234567890005, 1, 0, N'编辑小李', '2026-05-05 11:20:00', NULL, NULL,
|
|
||||||
1002, 5005, 8005, 3003, 6005,
|
|
||||||
N'摘自本期特稿:"在信息爆炸的时代,深度阅读能力正在成为一种稀缺的核心竞争力。"',
|
|
||||||
N'/uploads/images/quote_bg_01.png', 4, 1, 2, 67, 1),
|
|
||||||
|
|
||||||
-- 6. 文章类型 - 互动讨论
|
|
||||||
(1916234567890006, 1, 0, N'读者李四', '2026-05-06 16:45:00', NULL, NULL,
|
|
||||||
1001, 5006, 8006, 3001, 6006,
|
|
||||||
N'关于本期讨论话题"AI是否会取代教师",我认为技术只是工具,教育的本质是人与人之间灵魂的碰撞,这是机器无法替代的。',
|
|
||||||
NULL, 5, 1, 1, 92, 1),
|
|
||||||
|
|
||||||
-- 7. 公告类型 - 系统维护
|
|
||||||
(1916234567890007, 1, 0, N'系统管理员', '2026-05-07 09:00:00', N'系统管理员', '2026-05-07 09:30:00',
|
|
||||||
1001, 5001, 8001, 3001, 6001,
|
|
||||||
N'【系统公告】平台将于5月10日凌晨2:00-5:00进行系统升级维护,届时服务将短暂中断,请各位读者提前保存阅读进度。',
|
|
||||||
NULL, 1, 1, 3, 23, 0),
|
|
||||||
|
|
||||||
-- 8. 文章类型 - 编辑寄语
|
|
||||||
(1916234567890008, 1, 0, N'主编老陈', '2026-05-08 07:30:00', NULL, NULL,
|
|
||||||
1003, 5007, 8007, 3004, 6007,
|
|
||||||
N'新一期杂志如期而至。本期我们特别策划了"青年创作者专栏",收录了十位90后作家的原创作品,期待听到你们的声音。',
|
|
||||||
N'/uploads/images/editor_letter_05.jpg', 1, 1, 1, 210, 1),
|
|
||||||
|
|
||||||
-- 9. 引用类型 - 读者金句
|
|
||||||
(1916234567890009, 1, 0, N'读者王五', '2026-05-09 20:10:00', NULL, NULL,
|
|
||||||
1002, 5008, 8008, 3003, 6008,
|
|
||||||
N'"每一本杂志都是一扇窗,让我们看到不同的人生风景。" 感谢社区里每一位分享者。',
|
|
||||||
NULL, 6, 1, 2, 54, 0),
|
|
||||||
|
|
||||||
-- 10. 文章类型 - 话题互动
|
|
||||||
(1916234567890010, 1, 0, N'读者赵六', '2026-05-10 13:55:00', NULL, NULL,
|
|
||||||
1003, 5009, 8009, 3004, 6009,
|
|
||||||
N'看完"城市记忆"系列第三篇,想起了小时候胡同里的叫卖声和邻居家的饭菜香。城市化进程中,我们该如何留住这些温暖的记忆?',
|
|
||||||
N'/uploads/images/city_memory_03.jpg', 7, 1, 1, 78, 1),
|
|
||||||
|
|
||||||
-- 11. 公告类型 - 征稿启事
|
|
||||||
(1916234567890011, 1, 0, N'编辑小王', '2026-05-11 10:00:00', N'编辑小王', '2026-05-12 08:00:00',
|
|
||||||
1003, 5010, 8010, 3005, 6010,
|
|
||||||
N'【征稿启事】第十三期"我的阅读故事"主题征稿正式启动!征稿时间:5月11日-6月11日,优秀作品将刊登在杂志特辑中,期待您的来稿。',
|
|
||||||
N'/uploads/images/call_for_papers_2026.png', 2, 1, 3, 189, 1),
|
|
||||||
|
|
||||||
-- 12. 文章类型 - 读书笔记
|
|
||||||
(1916234567890012, 1, 0, N'读者孙七', '2026-05-12 19:30:00', NULL, NULL,
|
|
||||||
1001, 5011, 8011, 3001, 6011,
|
|
||||||
N'本期推荐的三本书我都读完了,最喜欢《思考,快与慢》。作者对直觉思维和理性思维的分析让人醍醐灌顶,强烈推荐给所有社区朋友。',
|
|
||||||
NULL, 8, 1, 1, 45, 0),
|
|
||||||
|
|
||||||
-- 13. 引用类型 - 经典回顾
|
|
||||||
(1916234567890013, 1, 0, N'编辑小李', '2026-05-13 15:00:00', NULL, NULL,
|
|
||||||
1002, 5002, 8002, 3003, 6012,
|
|
||||||
N'回顾创刊号寄语:"我们相信,好的内容值得被慢慢品味。在这个快节奏的时代,让我们一起做慢阅读的实践者。"',
|
|
||||||
N'/uploads/images/anniversary_retro.png', 3, 1, 2, 134, 1),
|
|
||||||
|
|
||||||
-- 14. 文章类型 - 作者互动
|
|
||||||
(1916234567890014, 1, 0, N'作者林清', '2026-05-14 21:45:00', NULL, NULL,
|
|
||||||
1003, 5012, 8012, 3004, 6013,
|
|
||||||
N'感谢大家对《城市记忆》系列的支持和反馈!每一条评论我都认真阅读了。你们的共鸣是我继续创作的最大动力,下一篇将聚焦"消失的声音"。',
|
|
||||||
NULL, 9, 1, 1, 167, 1),
|
|
||||||
|
|
||||||
-- 15. 公告类型 - 获奖公告
|
|
||||||
(1916234567890015, 1, 0, N'系统管理员', '2026-05-15 12:00:00', N'系统管理员', '2026-05-15 14:00:00',
|
|
||||||
1001, 5001, 8001, 3002, 6001,
|
|
||||||
N'【获奖公告】第十一期"最佳读者评论"评选结果揭晓!恭喜读者@书香满园、@夜读人、@思考者三位获得本期精选评论奖,奖品将于一周内寄送。',
|
|
||||||
N'/uploads/images/award_11.jpg', 4, 1, 3, 302, 1),
|
|
||||||
|
|
||||||
-- 16. 文章类型 - 生活随笔
|
|
||||||
(1916234567890016, 1, 0, N'读者周八', '2026-05-16 07:00:00', NULL, NULL,
|
|
||||||
1002, 5013, 8013, 3003, 6014,
|
|
||||||
N'清晨六点,一杯咖啡,一本杂志。这已经成了我三年来雷打不动的习惯。今天的这篇关于极简生活的文章,正好呼应了我最近的生活理念——少即是多。',
|
|
||||||
N'/uploads/images/morning_read.jpg', 10, 1, 1, 29, 0),
|
|
||||||
|
|
||||||
-- 17. 引用类型 - 编辑精选
|
|
||||||
(1916234567890017, 1, 0, N'主编老陈', '2026-05-17 16:30:00', NULL, NULL,
|
|
||||||
1003, 5007, 8007, 3004, 6015,
|
|
||||||
N'本期编辑精选语录:"真正的知识不在于拥有多少信息,而在于能用多少方式去理解世界。" —— 选自专栏文章《认知的边界》',
|
|
||||||
NULL, 5, 1, 2, 95, 1),
|
|
||||||
|
|
||||||
-- 18. 文章类型 - 问题探讨
|
|
||||||
(1916234567890018, 1, 0, N'读者吴九', '2026-05-18 22:15:00', NULL, NULL,
|
|
||||||
1001, 5014, 8014, 3001, 6016,
|
|
||||||
N'想请教大家,本期提到的"元宇宙图书馆"概念,你们觉得在技术层面还有多远?VR阅读体验是否真的能超越传统阅读?期待和大家探讨。',
|
|
||||||
NULL, 11, 1, 1, 61, 0),
|
|
||||||
|
|
||||||
-- 19. 公告类型 - 订阅优惠
|
|
||||||
(1916234567890019, 1, 0, N'系统管理员', '2026-05-19 09:30:00', NULL, NULL,
|
|
||||||
1002, 5003, 8003, 3002, 6003,
|
|
||||||
N'【限时优惠】杂志年度订阅特惠活动开启!5月20日-5月31日期间订阅全年杂志享7折优惠,老用户续费额外赠送限量版精装合集一本。',
|
|
||||||
N'/uploads/images/subscription_promo_2026.png', 6, 1, 3, 278, 0),
|
|
||||||
|
|
||||||
-- 20. 文章类型 - 社区感言
|
|
||||||
(1916234567890020, 1, 0, N'读者郑十', '2026-05-20 18:00:00', NULL, NULL,
|
|
||||||
1003, 5015, 8015, 3004, 6017,
|
|
||||||
N'加入这个阅读社区整整一年了,从最初的潜水到现在的积极互动,在这里认识了很多志同道合的朋友。感谢平台的用心运营,让我们一起在文字中找到归属。',
|
|
||||||
N'/uploads/images/community_anniversary.jpg', 12, 1, 1, 445, 1);
|
|
||||||
80
QYZH.InteractiveMagazine.IService/IAdminPermissionService.cs
Normal file
80
QYZH.InteractiveMagazine.IService/IAdminPermissionService.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.IService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台权限管理服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IAdminPermissionService : IBaseService<AdminRole>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建菜单
|
||||||
|
/// </summary>
|
||||||
|
Task<AdminMenuOutput> CreateMenuAsync(AdminMenuInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新菜单
|
||||||
|
/// </summary>
|
||||||
|
Task<AdminMenuOutput> UpdateMenuAsync(long id, AdminMenuInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除菜单
|
||||||
|
/// </summary>
|
||||||
|
Task DeleteMenuAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取菜单树
|
||||||
|
/// </summary>
|
||||||
|
Task<List<AdminMenuOutput>> GetMenuTreeAsync(AdminMenuQueryInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建角色
|
||||||
|
/// </summary>
|
||||||
|
Task<AdminRoleOutput> CreateRoleAsync(AdminRoleInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新角色
|
||||||
|
/// </summary>
|
||||||
|
Task<AdminRoleOutput> UpdateRoleAsync(long id, AdminRoleInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除角色
|
||||||
|
/// </summary>
|
||||||
|
Task DeleteRoleAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色详情
|
||||||
|
/// </summary>
|
||||||
|
Task<AdminRoleOutput> GetRoleByIdAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色分页列表
|
||||||
|
/// </summary>
|
||||||
|
Task<PageListModel<AdminRoleOutput>> GetRoleListAsync(AdminRoleQueryInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配角色菜单
|
||||||
|
/// </summary>
|
||||||
|
Task AssignRoleMenusAsync(long roleId, AssignRoleMenusInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配管理员角色
|
||||||
|
/// </summary>
|
||||||
|
Task AssignAdminUserRolesAsync(long adminUserId, AssignAdminUserRolesInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员菜单树
|
||||||
|
/// </summary>
|
||||||
|
Task<List<AdminMenuOutput>> GetAdminUserMenuTreeAsync(long adminUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员角色
|
||||||
|
/// </summary>
|
||||||
|
Task<List<AdminRoleSimpleOutput>> GetAdminUserRolesAsync(long adminUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员权限编码
|
||||||
|
/// </summary>
|
||||||
|
Task<List<string>> GetAdminUserPermissionCodesAsync(long adminUserId);
|
||||||
|
}
|
||||||
270
QYZH.InteractiveMagazine.Models/Dto/Admin/AdminPermissionDto.cs
Normal file
270
QYZH.InteractiveMagazine.Models/Dto/Admin/AdminPermissionDto.cs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台菜单输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminMenuInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 父级菜单ID,根节点为0
|
||||||
|
/// </summary>
|
||||||
|
public long ParentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前端路由路径
|
||||||
|
/// </summary>
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前端组件路径
|
||||||
|
/// </summary>
|
||||||
|
public string? Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单图标
|
||||||
|
/// </summary>
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序值
|
||||||
|
/// </summary>
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否显示在菜单
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; } = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台菜单输出
|
||||||
|
/// </summary>
|
||||||
|
public class AdminMenuOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单ID
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 父级菜单ID
|
||||||
|
/// </summary>
|
||||||
|
public long ParentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前端路由路径
|
||||||
|
/// </summary>
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前端组件路径
|
||||||
|
/// </summary>
|
||||||
|
public string? Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单图标
|
||||||
|
/// </summary>
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序值
|
||||||
|
/// </summary>
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否显示在菜单
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 子菜单
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminMenuOutput> Children { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台菜单查询输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminMenuQueryInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单名称
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码
|
||||||
|
/// </summary>
|
||||||
|
public string? Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int? Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminRoleInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色编码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> MenuIds { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色输出
|
||||||
|
/// </summary>
|
||||||
|
public class AdminRoleOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色编码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> MenuIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色简要输出
|
||||||
|
/// </summary>
|
||||||
|
public class AdminRoleSimpleOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色编码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色查询输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminRoleQueryInput : PageQueryModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名称
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色编码
|
||||||
|
/// </summary>
|
||||||
|
public string? Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int? Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配角色菜单输入
|
||||||
|
/// </summary>
|
||||||
|
public class AssignRoleMenusInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> MenuIds { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配用户角色输入
|
||||||
|
/// </summary>
|
||||||
|
public class AssignAdminUserRolesInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RoleIds { get; set; } = [];
|
||||||
|
}
|
||||||
@ -27,6 +27,11 @@ public class AdminUserInput
|
|||||||
/// 状态: Active, Inactive
|
/// 状态: Active, Inactive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Status { get; set; } = 1;
|
public int Status { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID集合,更新时为null表示不调整角色
|
||||||
|
/// </summary>
|
||||||
|
public List<long>? RoleIds { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -73,6 +78,16 @@ public class AdminUserOutput
|
|||||||
/// 更新时间
|
/// 更新时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? UpdatedAt { get; set; }
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RoleIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色集合
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -26,6 +26,26 @@ public class AdminLoginOutput
|
|||||||
public string UserName { get; set; } = string.Empty;
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string Type { get; set; } = string.Empty;
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RoleIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色集合
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单树
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminMenuOutput> Menus { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码集合
|
||||||
|
/// </summary>
|
||||||
|
public List<string> PermissionCodes { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AdminUserInfoOutput
|
public class AdminUserInfoOutput
|
||||||
@ -37,4 +57,24 @@ public class AdminUserInfoOutput
|
|||||||
public string Type { get; set; } = string.Empty;
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int Status { get; set; }
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色ID集合
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RoleIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色集合
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 菜单树
|
||||||
|
/// </summary>
|
||||||
|
public List<AdminMenuOutput> Menus { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码集合
|
||||||
|
/// </summary>
|
||||||
|
public List<string> PermissionCodes { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
66
QYZH.InteractiveMagazine.Models/Entity/AdminMenu.cs
Normal file
66
QYZH.InteractiveMagazine.Models/Entity/AdminMenu.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台菜单表
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("AdminMenu")]
|
||||||
|
public partial class AdminMenu : SqlSugarBaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:父级菜单ID,根节点为0
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long ParentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:菜单名称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:权限编码
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:前端路由路径
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:前端组件路径
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string? Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:菜单图标
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:排序值
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:是否显示在菜单
|
||||||
|
/// Default:b'1'
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
}
|
||||||
31
QYZH.InteractiveMagazine.Models/Entity/AdminRole.cs
Normal file
31
QYZH.InteractiveMagazine.Models/Entity/AdminRole.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色表
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("AdminRole")]
|
||||||
|
public partial class AdminRole : SqlSugarBaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:角色名称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:角色编码
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:备注
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
24
QYZH.InteractiveMagazine.Models/Entity/AdminRoleMenu.cs
Normal file
24
QYZH.InteractiveMagazine.Models/Entity/AdminRoleMenu.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台角色菜单关系表
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("AdminRoleMenu")]
|
||||||
|
public partial class AdminRoleMenu : SqlSugarBaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:角色ID
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long RoleId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:菜单ID
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long MenuId { get; set; }
|
||||||
|
}
|
||||||
24
QYZH.InteractiveMagazine.Models/Entity/AdminUserRole.cs
Normal file
24
QYZH.InteractiveMagazine.Models/Entity/AdminUserRole.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台管理员角色关系表
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("AdminUserRole")]
|
||||||
|
public partial class AdminUserRole : SqlSugarBaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:管理员ID
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long AdminUserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:角色ID
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long RoleId { get; set; }
|
||||||
|
}
|
||||||
@ -12,7 +12,11 @@ using QYZH.InteractiveMagazine.Repository;
|
|||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Service;
|
namespace QYZH.InteractiveMagazine.Service;
|
||||||
|
|
||||||
public class AdminAuthService(BaseRepository<AdminUser> adminUserRepository, IConfiguration configuration, ILogger<AdminAuthService> logger) : BaseRepository<AdminUser>, IAdminAuthService
|
public class AdminAuthService(
|
||||||
|
BaseRepository<AdminUser> adminUserRepository,
|
||||||
|
IAdminPermissionService adminPermissionService,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILogger<AdminAuthService> logger) : BaseRepository<AdminUser>, IAdminAuthService
|
||||||
{
|
{
|
||||||
|
|
||||||
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||||
@ -60,12 +64,20 @@ public class AdminAuthService(BaseRepository<AdminUser> adminUserRepository, ICo
|
|||||||
|
|
||||||
logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id);
|
logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id);
|
||||||
|
|
||||||
|
var roles = await adminPermissionService.GetAdminUserRolesAsync(adminUser.Id);
|
||||||
|
var menus = await adminPermissionService.GetAdminUserMenuTreeAsync(adminUser.Id);
|
||||||
|
var permissionCodes = await adminPermissionService.GetAdminUserPermissionCodesAsync(adminUser.Id);
|
||||||
|
|
||||||
return new AdminLoginOutput
|
return new AdminLoginOutput
|
||||||
{
|
{
|
||||||
Token = token,
|
Token = token,
|
||||||
UserId = (long)adminUser.Id,
|
UserId = (long)adminUser.Id,
|
||||||
UserName = adminUser.UserName,
|
UserName = adminUser.UserName,
|
||||||
Type = adminUser.Type.ToString(),
|
Type = adminUser.Type.ToString(),
|
||||||
|
RoleIds = roles.Select(x => x.Id).ToList(),
|
||||||
|
Roles = roles,
|
||||||
|
Menus = menus,
|
||||||
|
PermissionCodes = permissionCodes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,12 +101,20 @@ public class AdminAuthService(BaseRepository<AdminUser> adminUserRepository, ICo
|
|||||||
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var roles = await adminPermissionService.GetAdminUserRolesAsync(adminUser.Id);
|
||||||
|
var menus = await adminPermissionService.GetAdminUserMenuTreeAsync(adminUser.Id);
|
||||||
|
var permissionCodes = await adminPermissionService.GetAdminUserPermissionCodesAsync(adminUser.Id);
|
||||||
|
|
||||||
return new AdminUserInfoOutput
|
return new AdminUserInfoOutput
|
||||||
{
|
{
|
||||||
UserId = adminUser.Id,
|
UserId = adminUser.Id,
|
||||||
UserName = adminUser.UserName,
|
UserName = adminUser.UserName,
|
||||||
Type = adminUser.Type.ToString(),
|
Type = adminUser.Type.ToString(),
|
||||||
Status = adminUser.Status
|
Status = adminUser.Status,
|
||||||
|
RoleIds = roles.Select(x => x.Id).ToList(),
|
||||||
|
Roles = roles,
|
||||||
|
Menus = menus,
|
||||||
|
PermissionCodes = permissionCodes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
495
QYZH.InteractiveMagazine.Service/AdminPermissionService.cs
Normal file
495
QYZH.InteractiveMagazine.Service/AdminPermissionService.cs
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QYZH.InteractiveMagazine.IService;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
using QYZH.InteractiveMagazine.Repository;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台权限管理服务实现
|
||||||
|
/// </summary>
|
||||||
|
public class AdminPermissionService(
|
||||||
|
BaseRepository<AdminRole> adminRoleRepository,
|
||||||
|
BaseRepository<AdminMenu> adminMenuRepository,
|
||||||
|
BaseRepository<AdminRoleMenu> adminRoleMenuRepository,
|
||||||
|
BaseRepository<AdminUserRole> adminUserRoleRepository,
|
||||||
|
BaseRepository<AdminUser> adminUserRepository,
|
||||||
|
ILogger<AdminPermissionService> logger) : BaseRepository<AdminRole>, IAdminPermissionService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建菜单
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminMenuOutput> CreateMenuAsync(AdminMenuInput input)
|
||||||
|
{
|
||||||
|
await ValidateMenuInputAsync(input);
|
||||||
|
|
||||||
|
var menu = new AdminMenu
|
||||||
|
{
|
||||||
|
ParentId = input.ParentId,
|
||||||
|
Name = input.Name.Trim(),
|
||||||
|
Code = input.Code.Trim(),
|
||||||
|
Path = input.Path?.Trim(),
|
||||||
|
Component = input.Component?.Trim(),
|
||||||
|
Icon = input.Icon?.Trim(),
|
||||||
|
Sort = input.Sort,
|
||||||
|
IsVisible = input.IsVisible,
|
||||||
|
Status = input.Status,
|
||||||
|
CreatedBy = "System",
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
UpdatedBy = "System",
|
||||||
|
UpdatedAt = DateTime.Now,
|
||||||
|
IsDeleted = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await adminMenuRepository.InsertAsync(menu);
|
||||||
|
BusinessException.ThrowIf(!result, "创建菜单失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
|
||||||
|
logger.LogInformation("创建后台菜单成功:{Code},ID:{Id}", menu.Code, menu.Id);
|
||||||
|
return ToMenuOutput(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新菜单
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminMenuOutput> UpdateMenuAsync(long id, AdminMenuInput input)
|
||||||
|
{
|
||||||
|
var menu = await adminMenuRepository.GetByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(menu == null, "菜单不存在", ResultCode.NOT_FOUND);
|
||||||
|
BusinessException.ThrowIf(input.ParentId == id, "父级菜单不能选择自身", ResultCode.BAD_REQUEST);
|
||||||
|
|
||||||
|
await ValidateMenuInputAsync(input, id);
|
||||||
|
|
||||||
|
menu!.ParentId = input.ParentId;
|
||||||
|
menu.Name = input.Name.Trim();
|
||||||
|
menu.Code = input.Code.Trim();
|
||||||
|
menu.Path = input.Path?.Trim();
|
||||||
|
menu.Component = input.Component?.Trim();
|
||||||
|
menu.Icon = input.Icon?.Trim();
|
||||||
|
menu.Sort = input.Sort;
|
||||||
|
menu.IsVisible = input.IsVisible;
|
||||||
|
menu.Status = input.Status;
|
||||||
|
menu.UpdatedBy = "System";
|
||||||
|
menu.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
var result = await adminMenuRepository.UpdateAsync(menu);
|
||||||
|
BusinessException.ThrowIf(!result, "更新菜单失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
|
||||||
|
logger.LogInformation("更新后台菜单成功:{Code},ID:{Id}", menu.Code, menu.Id);
|
||||||
|
return ToMenuOutput(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除菜单
|
||||||
|
/// </summary>
|
||||||
|
public async Task DeleteMenuAsync(long id)
|
||||||
|
{
|
||||||
|
var menu = await adminMenuRepository.GetByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(menu == null, "菜单不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
var hasChildren = await adminMenuRepository.Queryable().AnyAsync(x => x.ParentId == id && !x.IsDeleted);
|
||||||
|
BusinessException.ThrowIf(hasChildren, "请先删除子菜单", ResultCode.CONFLICT);
|
||||||
|
|
||||||
|
var usedByRole = await adminRoleMenuRepository.Queryable().AnyAsync(x => x.MenuId == id && !x.IsDeleted);
|
||||||
|
BusinessException.ThrowIf(usedByRole, "菜单已被角色使用,不能删除", ResultCode.CONFLICT);
|
||||||
|
|
||||||
|
var result = await adminMenuRepository.DeleteByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(!result, "删除菜单失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取菜单树
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<AdminMenuOutput>> GetMenuTreeAsync(AdminMenuQueryInput input)
|
||||||
|
{
|
||||||
|
var menus = await adminMenuRepository.Queryable()
|
||||||
|
.Where(x => !x.IsDeleted)
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!))
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code!))
|
||||||
|
.WhereIF(input.Status.HasValue, x => x.Status == input.Status!.Value)
|
||||||
|
.OrderBy(x => x.Sort)
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return BuildMenuTree(menus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建角色
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminRoleOutput> CreateRoleAsync(AdminRoleInput input)
|
||||||
|
{
|
||||||
|
await ValidateRoleInputAsync(input);
|
||||||
|
|
||||||
|
var role = new AdminRole
|
||||||
|
{
|
||||||
|
Name = input.Name.Trim(),
|
||||||
|
Code = input.Code.Trim(),
|
||||||
|
Remark = input.Remark?.Trim(),
|
||||||
|
Status = input.Status,
|
||||||
|
CreatedBy = "System",
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
UpdatedBy = "System",
|
||||||
|
UpdatedAt = DateTime.Now,
|
||||||
|
IsDeleted = false
|
||||||
|
};
|
||||||
|
|
||||||
|
await UseTranAsync(async () =>
|
||||||
|
{
|
||||||
|
var inserted = await adminRoleRepository.InsertAsync(role);
|
||||||
|
BusinessException.ThrowIf(!inserted, "创建角色失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
await ReplaceRoleMenusAsync(role.Id, input.MenuIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.LogInformation("创建后台角色成功:{Code},ID:{Id}", role.Code, role.Id);
|
||||||
|
return await GetRoleByIdAsync(role.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新角色
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminRoleOutput> UpdateRoleAsync(long id, AdminRoleInput input)
|
||||||
|
{
|
||||||
|
var role = await adminRoleRepository.GetByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
await ValidateRoleInputAsync(input, id);
|
||||||
|
|
||||||
|
role!.Name = input.Name.Trim();
|
||||||
|
role.Code = input.Code.Trim();
|
||||||
|
role.Remark = input.Remark?.Trim();
|
||||||
|
role.Status = input.Status;
|
||||||
|
role.UpdatedBy = "System";
|
||||||
|
role.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
await UseTranAsync(async () =>
|
||||||
|
{
|
||||||
|
var updated = await adminRoleRepository.UpdateAsync(role);
|
||||||
|
BusinessException.ThrowIf(!updated, "更新角色失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
await ReplaceRoleMenusAsync(role.Id, input.MenuIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.LogInformation("更新后台角色成功:{Code},ID:{Id}", role.Code, role.Id);
|
||||||
|
return await GetRoleByIdAsync(role.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除角色
|
||||||
|
/// </summary>
|
||||||
|
public async Task DeleteRoleAsync(long id)
|
||||||
|
{
|
||||||
|
var role = await adminRoleRepository.GetByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
var usedByUser = await adminUserRoleRepository.Queryable().AnyAsync(x => x.RoleId == id && !x.IsDeleted);
|
||||||
|
BusinessException.ThrowIf(usedByUser, "角色已分配给管理员,不能删除", ResultCode.CONFLICT);
|
||||||
|
|
||||||
|
await UseTranAsync(async () =>
|
||||||
|
{
|
||||||
|
await adminRoleMenuRepository.Deleteable().Where(x => x.RoleId == id).ExecuteCommandAsync();
|
||||||
|
var deleted = await adminRoleRepository.DeleteByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(!deleted, "删除角色失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色详情
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminRoleOutput> GetRoleByIdAsync(long id)
|
||||||
|
{
|
||||||
|
var role = await adminRoleRepository.GetByIdAsync(id);
|
||||||
|
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
var menuIds = await adminRoleMenuRepository.Queryable()
|
||||||
|
.Where(x => x.RoleId == id && !x.IsDeleted)
|
||||||
|
.Select(x => x.MenuId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return ToRoleOutput(role!, menuIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色分页列表
|
||||||
|
/// </summary>
|
||||||
|
public async Task<PageListModel<AdminRoleOutput>> GetRoleListAsync(AdminRoleQueryInput input)
|
||||||
|
{
|
||||||
|
RefAsync<int> totalNumber = 0;
|
||||||
|
var roles = await adminRoleRepository.Queryable()
|
||||||
|
.Where(x => !x.IsDeleted)
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!))
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code!))
|
||||||
|
.WhereIF(input.Status.HasValue, x => x.Status == input.Status!.Value)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
||||||
|
|
||||||
|
var roleIds = roles.Select(x => x.Id).ToList();
|
||||||
|
var roleMenus = roleIds.Count == 0
|
||||||
|
? []
|
||||||
|
: await adminRoleMenuRepository.Queryable()
|
||||||
|
.Where(x => roleIds.Contains(x.RoleId) && !x.IsDeleted)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var outputs = roles
|
||||||
|
.Select(x => ToRoleOutput(x, roleMenus.Where(rm => rm.RoleId == x.Id).Select(rm => rm.MenuId).ToList()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new PageListModel<AdminRoleOutput>(outputs, input.PageIndex, input.PageSize, totalNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配角色菜单
|
||||||
|
/// </summary>
|
||||||
|
public async Task AssignRoleMenusAsync(long roleId, AssignRoleMenusInput input)
|
||||||
|
{
|
||||||
|
var role = await adminRoleRepository.GetByIdAsync(roleId);
|
||||||
|
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
await ValidateMenuIdsAsync(input.MenuIds);
|
||||||
|
await UseTranAsync(async () => await ReplaceRoleMenusAsync(roleId, input.MenuIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配管理员角色
|
||||||
|
/// </summary>
|
||||||
|
public async Task AssignAdminUserRolesAsync(long adminUserId, AssignAdminUserRolesInput input)
|
||||||
|
{
|
||||||
|
await ReplaceAdminUserRolesAsync(adminUserId, input.RoleIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员菜单树
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<AdminMenuOutput>> GetAdminUserMenuTreeAsync(long adminUserId)
|
||||||
|
{
|
||||||
|
var menus = await GetAdminUserMenusAsync(adminUserId, true);
|
||||||
|
return BuildMenuTree(menus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员角色
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<AdminRoleSimpleOutput>> GetAdminUserRolesAsync(long adminUserId)
|
||||||
|
{
|
||||||
|
var adminUser = await adminUserRepository.GetByIdAsync(adminUserId);
|
||||||
|
BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
return await adminRoleRepository.Queryable()
|
||||||
|
.InnerJoin<AdminUserRole>((role, userRole) => role.Id == userRole.RoleId)
|
||||||
|
.Where((role, userRole) => userRole.AdminUserId == adminUserId && !role.IsDeleted && !userRole.IsDeleted)
|
||||||
|
.Select((role, userRole) => new AdminRoleSimpleOutput
|
||||||
|
{
|
||||||
|
Id = role.Id,
|
||||||
|
Name = role.Name,
|
||||||
|
Code = role.Code
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取管理员权限编码
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<string>> GetAdminUserPermissionCodesAsync(long adminUserId)
|
||||||
|
{
|
||||||
|
var menus = await GetAdminUserMenusAsync(adminUserId, false);
|
||||||
|
return menus.Select(x => x.Code).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateMenuInputAsync(AdminMenuInput input, long? id = null)
|
||||||
|
{
|
||||||
|
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Name), "菜单名称不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Code), "权限编码不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
|
||||||
|
if (input.ParentId > 0)
|
||||||
|
{
|
||||||
|
var parentExists = await adminMenuRepository.Queryable().AnyAsync(x => x.Id == input.ParentId && !x.IsDeleted);
|
||||||
|
BusinessException.ThrowIf(!parentExists, "父级菜单不存在", ResultCode.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = input.Code.Trim();
|
||||||
|
var codeExists = await adminMenuRepository.Queryable()
|
||||||
|
.AnyAsync(x => x.Code == code && !x.IsDeleted && (!id.HasValue || x.Id != id.Value));
|
||||||
|
BusinessException.ThrowIf(codeExists, "权限编码已存在", ResultCode.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateRoleInputAsync(AdminRoleInput input, long? id = null)
|
||||||
|
{
|
||||||
|
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Name), "角色名称不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Code), "角色编码不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
|
||||||
|
var code = input.Code.Trim();
|
||||||
|
var codeExists = await adminRoleRepository.Queryable()
|
||||||
|
.AnyAsync(x => x.Code == code && !x.IsDeleted && (!id.HasValue || x.Id != id.Value));
|
||||||
|
BusinessException.ThrowIf(codeExists, "角色编码已存在", ResultCode.CONFLICT);
|
||||||
|
|
||||||
|
await ValidateMenuIdsAsync(input.MenuIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateMenuIdsAsync(List<long> menuIds)
|
||||||
|
{
|
||||||
|
var ids = menuIds.Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existsCount = await adminMenuRepository.Queryable()
|
||||||
|
.Where(x => ids.Contains(x.Id) && !x.IsDeleted)
|
||||||
|
.CountAsync();
|
||||||
|
BusinessException.ThrowIf(existsCount != ids.Count, "包含不存在的菜单", ResultCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateRoleIdsAsync(List<long> roleIds)
|
||||||
|
{
|
||||||
|
var ids = roleIds.Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existsCount = await adminRoleRepository.Queryable()
|
||||||
|
.Where(x => ids.Contains(x.Id) && !x.IsDeleted)
|
||||||
|
.CountAsync();
|
||||||
|
BusinessException.ThrowIf(existsCount != ids.Count, "包含不存在的角色", ResultCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReplaceRoleMenusAsync(long roleId, List<long> menuIds)
|
||||||
|
{
|
||||||
|
await adminRoleMenuRepository.Deleteable().Where(x => x.RoleId == roleId).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var items = menuIds.Distinct().Select(menuId => new AdminRoleMenu
|
||||||
|
{
|
||||||
|
RoleId = roleId,
|
||||||
|
MenuId = menuId,
|
||||||
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
|
CreatedBy = "System",
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedBy = "System",
|
||||||
|
UpdatedAt = now,
|
||||||
|
IsDeleted = false
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (items.Count > 0)
|
||||||
|
{
|
||||||
|
await adminRoleMenuRepository.Context.Insertable(items).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReplaceAdminUserRolesAsync(long adminUserId, List<long> roleIds)
|
||||||
|
{
|
||||||
|
var adminUser = await adminUserRepository.GetByIdAsync(adminUserId);
|
||||||
|
BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
await ValidateRoleIdsAsync(roleIds);
|
||||||
|
|
||||||
|
await UseTranAsync(async () =>
|
||||||
|
{
|
||||||
|
await adminUserRoleRepository.Deleteable().Where(x => x.AdminUserId == adminUserId).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var items = roleIds.Distinct().Select(roleId => new AdminUserRole
|
||||||
|
{
|
||||||
|
AdminUserId = adminUserId,
|
||||||
|
RoleId = roleId,
|
||||||
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
|
CreatedBy = "System",
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedBy = "System",
|
||||||
|
UpdatedAt = now,
|
||||||
|
IsDeleted = false
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (items.Count > 0)
|
||||||
|
{
|
||||||
|
await adminUserRoleRepository.Context.Insertable(items).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<AdminMenu>> GetAdminUserMenusAsync(long adminUserId, bool visibleOnly)
|
||||||
|
{
|
||||||
|
var adminUser = await adminUserRepository.GetByIdAsync(adminUserId);
|
||||||
|
BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND);
|
||||||
|
|
||||||
|
if (adminUser!.Type == AdminUserTypeEnum.SuperAdmin)
|
||||||
|
{
|
||||||
|
return await adminMenuRepository.Queryable()
|
||||||
|
.Where(x => !x.IsDeleted && x.Status == (int)DefaultStatusEnum.Active)
|
||||||
|
.WhereIF(visibleOnly, x => x.IsVisible)
|
||||||
|
.OrderBy(x => x.Sort)
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var menus = await adminMenuRepository.Queryable()
|
||||||
|
.InnerJoin<AdminRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
|
||||||
|
.InnerJoin<AdminRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
|
||||||
|
.InnerJoin<AdminUserRole>((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId)
|
||||||
|
.Where((menu, roleMenu, role, userRole) =>
|
||||||
|
userRole.AdminUserId == adminUserId
|
||||||
|
&& !menu.IsDeleted
|
||||||
|
&& !roleMenu.IsDeleted
|
||||||
|
&& !role.IsDeleted
|
||||||
|
&& !userRole.IsDeleted
|
||||||
|
&& menu.Status == (int)DefaultStatusEnum.Active
|
||||||
|
&& role.Status == (int)DefaultStatusEnum.Active)
|
||||||
|
.WhereIF(visibleOnly, (menu, roleMenu, role, userRole) => menu.IsVisible)
|
||||||
|
.OrderBy((menu, roleMenu, role, userRole) => menu.Sort)
|
||||||
|
.OrderBy((menu, roleMenu, role, userRole) => menu.Id)
|
||||||
|
.Select((menu, roleMenu, role, userRole) => menu)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return menus.DistinctBy(x => x.Id).OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AdminMenuOutput> BuildMenuTree(List<AdminMenu> menus)
|
||||||
|
{
|
||||||
|
var outputs = menus.Select(ToMenuOutput).ToList();
|
||||||
|
var lookup = outputs.ToLookup(x => x.ParentId);
|
||||||
|
|
||||||
|
foreach (var item in outputs)
|
||||||
|
{
|
||||||
|
item.Children = lookup[item.Id].OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
.Where(x => x.ParentId == 0 || outputs.All(item => item.Id != x.ParentId))
|
||||||
|
.OrderBy(x => x.Sort)
|
||||||
|
.ThenBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdminMenuOutput ToMenuOutput(AdminMenu menu)
|
||||||
|
{
|
||||||
|
return new AdminMenuOutput
|
||||||
|
{
|
||||||
|
Id = menu.Id,
|
||||||
|
ParentId = menu.ParentId,
|
||||||
|
Name = menu.Name,
|
||||||
|
Code = menu.Code,
|
||||||
|
Path = menu.Path,
|
||||||
|
Component = menu.Component,
|
||||||
|
Icon = menu.Icon,
|
||||||
|
Sort = menu.Sort,
|
||||||
|
IsVisible = menu.IsVisible,
|
||||||
|
Status = menu.Status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdminRoleOutput ToRoleOutput(AdminRole role, List<long> menuIds)
|
||||||
|
{
|
||||||
|
return new AdminRoleOutput
|
||||||
|
{
|
||||||
|
Id = role.Id,
|
||||||
|
Name = role.Name,
|
||||||
|
Code = role.Code,
|
||||||
|
Remark = role.Remark,
|
||||||
|
Status = role.Status,
|
||||||
|
MenuIds = menuIds,
|
||||||
|
CreatedAt = role.CreatedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,7 +15,10 @@ namespace QYZH.InteractiveMagazine.Service;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 管理员用户服务实现
|
/// 管理员用户服务实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILogger<AdminUserService> logger) : BaseRepository<AdminUser>, IAdminUserService
|
public class AdminUserService(
|
||||||
|
BaseRepository<AdminUser> adminUserRepository,
|
||||||
|
IAdminPermissionService adminPermissionService,
|
||||||
|
ILogger<AdminUserService> logger) : BaseRepository<AdminUser>, IAdminUserService
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -63,9 +66,14 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
throw new BusinessException("创建管理员失败", ResultCode.GLOBAL_ERROR);
|
throw new BusinessException("创建管理员失败", ResultCode.GLOBAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.RoleIds != null)
|
||||||
|
{
|
||||||
|
await adminPermissionService.AssignAdminUserRolesAsync(adminUser.Id, new AssignAdminUserRolesInput { RoleIds = input.RoleIds });
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id);
|
logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id);
|
||||||
|
|
||||||
return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type.ToString(), Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt };
|
return await ToAdminUserOutputAsync(adminUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -114,9 +122,14 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
throw new BusinessException("更新管理员失败", ResultCode.GLOBAL_ERROR);
|
throw new BusinessException("更新管理员失败", ResultCode.GLOBAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.RoleIds != null)
|
||||||
|
{
|
||||||
|
await adminPermissionService.AssignAdminUserRolesAsync(adminUser.Id, new AssignAdminUserRolesInput { RoleIds = input.RoleIds });
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("管理员更新成功,ID: {Id}", id);
|
logger.LogInformation("管理员更新成功,ID: {Id}", id);
|
||||||
|
|
||||||
return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type.ToString(), Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt };
|
return await ToAdminUserOutputAsync(adminUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -156,17 +169,7 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
logger.LogWarning("未找到管理员,ID: {Id}", id);
|
logger.LogWarning("未找到管理员,ID: {Id}", id);
|
||||||
throw new BusinessException("管理员不存在", ResultCode.NOT_FOUND);
|
throw new BusinessException("管理员不存在", ResultCode.NOT_FOUND);
|
||||||
}
|
}
|
||||||
return new AdminUserOutput
|
return await ToAdminUserOutputAsync(adminUser);
|
||||||
{
|
|
||||||
Id = adminUser.Id,
|
|
||||||
UserName = adminUser.UserName,
|
|
||||||
Type = adminUser.Type.ToString(),
|
|
||||||
Status = adminUser.Status,
|
|
||||||
CreatedBy = adminUser.CreatedBy,
|
|
||||||
CreatedAt = adminUser.CreatedAt,
|
|
||||||
UpdatedBy = adminUser.UpdatedBy,
|
|
||||||
UpdatedAt = adminUser.UpdatedAt
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -201,6 +204,12 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
UpdatedAt = a.UpdatedAt
|
UpdatedAt = a.UpdatedAt
|
||||||
}, true)
|
}, true)
|
||||||
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
||||||
|
foreach (var item in pageResult)
|
||||||
|
{
|
||||||
|
item.Roles = await adminPermissionService.GetAdminUserRolesAsync(item.Id);
|
||||||
|
item.RoleIds = item.Roles.Select(x => x.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
return new PageListModel<AdminUserOutput>(pageResult, input.PageIndex, input.PageSize, totalNumber);
|
return new PageListModel<AdminUserOutput>(pageResult, input.PageIndex, input.PageSize, totalNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,4 +240,22 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
|
|
||||||
logger.LogInformation("管理员状态更新成功,ID: {Id}", id);
|
logger.LogInformation("管理员状态更新成功,ID: {Id}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<AdminUserOutput> ToAdminUserOutputAsync(AdminUser adminUser)
|
||||||
|
{
|
||||||
|
var roles = await adminPermissionService.GetAdminUserRolesAsync(adminUser.Id);
|
||||||
|
return new AdminUserOutput
|
||||||
|
{
|
||||||
|
Id = adminUser.Id,
|
||||||
|
UserName = adminUser.UserName,
|
||||||
|
Type = adminUser.Type.ToString(),
|
||||||
|
Status = adminUser.Status,
|
||||||
|
CreatedBy = adminUser.CreatedBy,
|
||||||
|
CreatedAt = adminUser.CreatedAt,
|
||||||
|
UpdatedBy = adminUser.UpdatedBy,
|
||||||
|
UpdatedAt = adminUser.UpdatedAt,
|
||||||
|
RoleIds = roles.Select(x => x.Id).ToList(),
|
||||||
|
Roles = roles
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.IService;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.Bag;
|
using QYZH.InteractiveMagazine.Models.Dto.Bag;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序背包控制器
|
/// 小程序背包控制器
|
||||||
@ -4,7 +4,7 @@ using QYZH.InteractiveMagazine.Models.Common;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 签到控制器
|
/// 签到控制器
|
||||||
@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.IService;
|
|||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序社区控制器
|
/// 小程序社区控制器
|
||||||
@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.IService;
|
|||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序期刊管理控制器
|
/// 小程序期刊管理控制器
|
||||||
@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.IService;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.Mall;
|
using QYZH.InteractiveMagazine.Models.Dto.Mall;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序商城控制器
|
/// 小程序商城控制器
|
||||||
@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.IService;
|
|||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序勋章控制器
|
/// 小程序勋章控制器
|
||||||
@ -4,7 +4,7 @@ using QYZH.InteractiveMagazine.Models.Common;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.Pet;
|
using QYZH.InteractiveMagazine.Models.Dto.Pet;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序宠物管理控制器
|
/// 小程序宠物管理控制器
|
||||||
@ -5,7 +5,7 @@ using QYZH.InteractiveMagazine.Models.Dto;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序首页控制器
|
/// 小程序首页控制器
|
||||||
@ -6,7 +6,7 @@ using QYZH.InteractiveMagazine.Models.Common;
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
using QYZH.InteractiveMagazine.Models.WeChat;
|
using QYZH.InteractiveMagazine.Models.WeChat;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序认证控制器
|
/// 微信小程序认证控制器
|
||||||
@ -5,7 +5,7 @@ using QYZH.InteractiveMagazine.Models.Dto;
|
|||||||
using QYZH.InteractiveMagazine.Models.Enum;
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序基础控制器
|
/// 小程序基础控制器
|
||||||
189
QYZH.InteractiveMagazine.WeChatApi/Program.cs
Normal file
189
QYZH.InteractiveMagazine.WeChatApi/Program.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||||
|
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Autofacs;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Context;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.SDK;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Settings;
|
||||||
|
using QYZH.InteractiveMagazine.Repository;
|
||||||
|
using QYZH.InteractiveMagazine.Repository.Core;
|
||||||
|
using Serilog;
|
||||||
|
using SqlSugar.IOC;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Yitter.IdGenerator;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Configuration.AddJsonFile("medal-rule-config.json", optional: false, reloadOnChange: true);
|
||||||
|
|
||||||
|
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = 1 });
|
||||||
|
|
||||||
|
builder.UseAutofac();
|
||||||
|
|
||||||
|
builder.InitSqlSugarDb(new IocConfig
|
||||||
|
{
|
||||||
|
ConfigId = 0,
|
||||||
|
DbType = IocDbType.MySql,
|
||||||
|
ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||||
|
IsAutoCloseConnection = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddDataProtection()
|
||||||
|
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "DataProtection")));
|
||||||
|
|
||||||
|
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
|
||||||
|
builder.Services.AddRabbitMQ(builder.Configuration);
|
||||||
|
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.ReadFrom.Configuration(builder.Configuration)
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
|
builder.Services.AddControllers(options =>
|
||||||
|
{
|
||||||
|
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||||
|
options.Filters.Add<ModelValidActionFilterAttribute>();
|
||||||
|
})
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||||
|
options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter());
|
||||||
|
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
||||||
|
})
|
||||||
|
.ConfigureApiBehaviorOptions(opt => opt.SuppressModelStateInvalidFilter = true);
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.AddCorsRegister();
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
builder.Services.AddScoped(typeof(BaseRepository<>));
|
||||||
|
builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment);
|
||||||
|
builder.Services.AddSDKService(builder.Configuration);
|
||||||
|
builder.Services.Configure<MedalRuleConfigSettings>(builder.Configuration.GetSection("MedalRuleConfig"));
|
||||||
|
|
||||||
|
builder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("AllowAll", policy =>
|
||||||
|
{
|
||||||
|
policy.AllowAnyOrigin()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
|
||||||
|
{
|
||||||
|
options.Level = System.IO.Compression.CompressionLevel.Optimal;
|
||||||
|
});
|
||||||
|
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
|
||||||
|
{
|
||||||
|
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
||||||
|
});
|
||||||
|
builder.Services.AddResponseCompression(options =>
|
||||||
|
{
|
||||||
|
options.EnableForHttps = true;
|
||||||
|
options.Providers.Add<BrotliCompressionProvider>();
|
||||||
|
options.Providers.Add<GzipCompressionProvider>();
|
||||||
|
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["image/svg+xml", "application/json", "text/plain"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSwaggerGen(option =>
|
||||||
|
{
|
||||||
|
var xmlFile = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
|
||||||
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||||
|
var modelXml = Path.Combine(AppContext.BaseDirectory, "QYZH.InteractiveMagazine.Models.xml");
|
||||||
|
var version = ApiVersionEnum.Wechat;
|
||||||
|
|
||||||
|
option.SwaggerDoc(version.ToString(), new OpenApiInfo
|
||||||
|
{
|
||||||
|
Title = AppDomain.CurrentDomain.FriendlyName,
|
||||||
|
Version = "互动期刊接口文档",
|
||||||
|
Description = $"{version.GetDescription()}接口,Last Modify Time:{new FileInfo(xmlPath).LastWriteTime:yyyy-MM-dd HH:mm:ss}"
|
||||||
|
});
|
||||||
|
|
||||||
|
option.OrderActionsBy(o => o.RelativePath);
|
||||||
|
|
||||||
|
if (File.Exists(xmlPath))
|
||||||
|
{
|
||||||
|
option.IncludeXmlComments(xmlPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(modelXml))
|
||||||
|
{
|
||||||
|
option.IncludeXmlComments(modelXml, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Description = "请输入 Token,格式为 Bearer Token",
|
||||||
|
Name = "Authorization",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Type = SecuritySchemeType.ApiKey,
|
||||||
|
BearerFormat = "JWT",
|
||||||
|
Scheme = "Bearer"
|
||||||
|
});
|
||||||
|
option.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
option.DocInclusionPredicate((docName, apiDesc) =>
|
||||||
|
{
|
||||||
|
if (!apiDesc.TryGetMethodInfo(out var methodInfo))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupName = methodInfo.DeclaringType?
|
||||||
|
.GetCustomAttributes(true)
|
||||||
|
.OfType<ApiExplorerSettingsAttribute>()
|
||||||
|
.FirstOrDefault()?
|
||||||
|
.GroupName;
|
||||||
|
|
||||||
|
return groupName == docName;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(c =>
|
||||||
|
{
|
||||||
|
var version = ApiVersionEnum.Wechat;
|
||||||
|
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口");
|
||||||
|
c.DocExpansion(DocExpansion.None);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseServiceContext();
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseCors("AllowAll");
|
||||||
|
app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||||
|
app.UseMiddleware<OperationLogMiddleware>();
|
||||||
|
app.UseMiddleware<JwtAutoRefreshMiddleware>();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:51131",
|
||||||
|
"sslPort": 44367
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5198",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:7253;http://localhost:5198",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac" Version="9.1.0" />
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
|
||||||
|
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.IService\QYZH.InteractiveMagazine.IService.csproj" />
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Service\QYZH.InteractiveMagazine.Service.csproj" />
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Infrastructure\QYZH.InteractiveMagazine.Infrastructure.csproj" />
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Common\QYZH.InteractiveMagazine.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
QYZH.InteractiveMagazine.WeChatApi/appsettings.json
Normal file
71
QYZH.InteractiveMagazine.WeChatApi/appsettings.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "server=192.168.20.150;port=13306;database=InteractiveMagazine;user=user;password=n68792bu!y99r905;charset=utf8mb4;"
|
||||||
|
},
|
||||||
|
"JwtSettings": {
|
||||||
|
"Issuer": "QYZH.InteractiveMagazine",
|
||||||
|
"Audience": "QYZH.InteractiveMagazine",
|
||||||
|
"SecretKey": "zG7pLqR9xVw2bN8fYtHk3mPc5sA1dF6eUjW4gXhC7vB",
|
||||||
|
"ExpiryMinutes": 120,
|
||||||
|
"JwtTokenExpiryDays": 30
|
||||||
|
},
|
||||||
|
"RedisSettings": {
|
||||||
|
"ConnectionString": "192.168.20.150:16379,defaultDatabase=5",
|
||||||
|
"Sentinels": [],
|
||||||
|
"ExpireSecondRange": [ 3600, 7200 ]
|
||||||
|
},
|
||||||
|
"RabbitMq": {
|
||||||
|
"HostName": "192.168.20.150",
|
||||||
|
"Port": 5672,
|
||||||
|
"UserName": "smartschool",
|
||||||
|
"Password": "@ss%&*otz%d*pq2S",
|
||||||
|
"VirtualHost": "InteractiveMagazine",
|
||||||
|
"ClientProvidedName": "Custom connection name"
|
||||||
|
},
|
||||||
|
"WeChatSettings": {
|
||||||
|
"AppId": "wx8c08da60bd207e64",
|
||||||
|
"AppSecret": "76fa314c34762c0347bd613e488c6872"
|
||||||
|
},
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Override": {
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"System": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WriteTo": [
|
||||||
|
{
|
||||||
|
"Name": "Console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Args": {
|
||||||
|
"path": "logs/log-.txt",
|
||||||
|
"rollingInterval": "Day"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"AiChat": {
|
||||||
|
"ApiKey": "Ollama",
|
||||||
|
"BaseUrl": "http://172.16.10.130:11434/v1/",
|
||||||
|
"Model": "qwen2.5vl:7b",
|
||||||
|
"TimeoutSeconds": 300,
|
||||||
|
"MaxTokens": 2000,
|
||||||
|
"Temperature": 0.5
|
||||||
|
},
|
||||||
|
"AliyunOSSConfigs": {
|
||||||
|
"AccessKeyID": "LTAI5tEBXGewpHSLiSxyx6Bf",
|
||||||
|
"AccessKeySecret": "w29b8wkw6XQVL8GWXgp3ZesgYeDKvf",
|
||||||
|
"VodBucketName": "outin-5277bbb52bec11f08dbd00163e169e2b.oss-cn-beijing.aliyuncs.com",
|
||||||
|
"BucketName": "qyzh2025test",
|
||||||
|
"Region": "beijing",
|
||||||
|
"RoleArn": "acs:ram::1064745380176636:role/aliyunosstokengeneratorrole",
|
||||||
|
"DurationSeconds": 3600, //过期时间(秒)
|
||||||
|
"Endpoint": "oss-cn-beijing.aliyuncs.com",
|
||||||
|
"ProjectName": "InteractiveMagazine",
|
||||||
|
"Domain": "http://oss-test.qyzhjy.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
QYZH.InteractiveMagazine.WeChatApi/medal-rule-config.json
Normal file
43
QYZH.InteractiveMagazine.WeChatApi/medal-rule-config.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"MedalRuleConfig": {
|
||||||
|
"Tables": [
|
||||||
|
{
|
||||||
|
"TableName": "CheckInRecord",
|
||||||
|
"DisplayName": "签到记录",
|
||||||
|
"Fields": [
|
||||||
|
{ "FieldName": "ContinuousDays", "DisplayName": "连续天数" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TableName": "UserPet",
|
||||||
|
"DisplayName": "宠物",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"FieldName": "GrowthPoints",
|
||||||
|
"DisplayName": "成长值"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "FeedingCount",
|
||||||
|
"DisplayName": "喂养次数"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "Comprehension",
|
||||||
|
"DisplayName": "理解力"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "Judgment",
|
||||||
|
"DisplayName": "判断力"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "Expression",
|
||||||
|
"DisplayName": "表达力"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "Persuasiveness",
|
||||||
|
"DisplayName": "说服力"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using QYZH.InteractiveMagazine.IService;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后台权限管理
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
|
||||||
|
public class PermissionController(IAdminPermissionService adminPermissionService) : BaseController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建菜单
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("menus")]
|
||||||
|
public async Task<BaseResponse<AdminMenuOutput>> CreateMenuAsync([FromBody] AdminMenuInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.CreateMenuAsync(input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新菜单
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("menus/{id}")]
|
||||||
|
public async Task<BaseResponse<AdminMenuOutput>> UpdateMenuAsync(long id, [FromBody] AdminMenuInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.UpdateMenuAsync(id, input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除菜单
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("menus/{id}")]
|
||||||
|
public async Task<BaseResponse<object>> DeleteMenuAsync(long id)
|
||||||
|
{
|
||||||
|
await adminPermissionService.DeleteMenuAsync(id);
|
||||||
|
return Success(new object());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取菜单树
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("menus/tree")]
|
||||||
|
public async Task<BaseResponse<List<AdminMenuOutput>>> GetMenuTreeAsync([FromBody] AdminMenuQueryInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.GetMenuTreeAsync(input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前管理员菜单树
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("menus/current")]
|
||||||
|
public async Task<BaseResponse<List<AdminMenuOutput>>> GetCurrentMenuTreeAsync()
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
BusinessException.ThrowIf(userId == null, "未获取到用户信息", ResultCode.DENY);
|
||||||
|
|
||||||
|
var result = await adminPermissionService.GetAdminUserMenuTreeAsync(userId!.Value);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建角色
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("roles")]
|
||||||
|
public async Task<BaseResponse<AdminRoleOutput>> CreateRoleAsync([FromBody] AdminRoleInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.CreateRoleAsync(input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新角色
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("roles/{id}")]
|
||||||
|
public async Task<BaseResponse<AdminRoleOutput>> UpdateRoleAsync(long id, [FromBody] AdminRoleInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.UpdateRoleAsync(id, input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除角色
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("roles/{id}")]
|
||||||
|
public async Task<BaseResponse<object>> DeleteRoleAsync(long id)
|
||||||
|
{
|
||||||
|
await adminPermissionService.DeleteRoleAsync(id);
|
||||||
|
return Success(new object());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色详情
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("roles/{id}")]
|
||||||
|
public async Task<BaseResponse<AdminRoleOutput>> GetRoleByIdAsync(long id)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.GetRoleByIdAsync(id);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色分页列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("roles/list")]
|
||||||
|
public async Task<BaseResponse<PageListModel<AdminRoleOutput>>> GetRoleListAsync([FromBody] AdminRoleQueryInput input)
|
||||||
|
{
|
||||||
|
var result = await adminPermissionService.GetRoleListAsync(input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配角色菜单
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("roles/{roleId}/menus")]
|
||||||
|
public async Task<BaseResponse<object>> AssignRoleMenusAsync(long roleId, [FromBody] AssignRoleMenusInput input)
|
||||||
|
{
|
||||||
|
await adminPermissionService.AssignRoleMenusAsync(roleId, input);
|
||||||
|
return Success(new object());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分配管理员角色
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("users/{adminUserId}/roles")]
|
||||||
|
public async Task<BaseResponse<object>> AssignAdminUserRolesAsync(long adminUserId, [FromBody] AssignAdminUserRolesInput input)
|
||||||
|
{
|
||||||
|
await adminPermissionService.AssignAdminUserRolesAsync(adminUserId, input);
|
||||||
|
return Success(new object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -90,7 +90,7 @@ builder.Services.AddSwaggerGen(option =>
|
|||||||
var modelXml = Path.Combine(AppContext.BaseDirectory, $"QYZH.InteractiveMagazine.Models.xml");
|
var modelXml = Path.Combine(AppContext.BaseDirectory, $"QYZH.InteractiveMagazine.Models.xml");
|
||||||
|
|
||||||
|
|
||||||
Enum.GetValues<ApiVersionEnum>().ToList().ForEach(version =>
|
Enum.GetValues<ApiVersionEnum>().Where(version => version != ApiVersionEnum.Wechat).ToList().ForEach(version =>
|
||||||
{
|
{
|
||||||
// 配置文档信息
|
// 配置文档信息
|
||||||
option.SwaggerDoc(version.ToString(), new OpenApiInfo
|
option.SwaggerDoc(version.ToString(), new OpenApiInfo
|
||||||
@ -201,7 +201,7 @@ var app = builder.Build();
|
|||||||
app.UseSwaggerUI(c =>
|
app.UseSwaggerUI(c =>
|
||||||
{
|
{
|
||||||
// 根据版本名称倒序 遍历展示
|
// 根据版本名称倒序 遍历展示
|
||||||
Enum.GetValues<ApiVersionEnum>().OrderBy(e => e).ToList().ForEach(version =>
|
Enum.GetValues<ApiVersionEnum>().Where(version => version != ApiVersionEnum.Wechat).OrderBy(e => e).ToList().ForEach(version =>
|
||||||
{
|
{
|
||||||
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口");
|
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口");
|
||||||
});
|
});
|
||||||
@ -220,4 +220,3 @@ app.UseAuthorization();
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,7 @@ public class JournalTaskReceiveConsumer(
|
|||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true
|
||||||
}) ?? throw new InvalidOperationException("期刊任务消息内容为空");
|
}) ?? throw new InvalidOperationException("期刊任务消息内容为空");
|
||||||
|
data.Normalize();
|
||||||
|
|
||||||
if (data.Questions == null || data.Questions.Length == 0)
|
if (data.Questions == null || data.Questions.Length == 0)
|
||||||
{
|
{
|
||||||
@ -1093,11 +1094,26 @@ public class QuestionData
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long UserId { get; set; }
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 学生ID
|
||||||
|
/// </summary>
|
||||||
|
public long StudentId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 期刊ID
|
/// 期刊ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 作业ID
|
||||||
|
/// </summary>
|
||||||
|
public long HomeworkId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 书籍ID
|
||||||
|
/// </summary>
|
||||||
|
public long BookId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 页ID
|
/// 页ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1117,6 +1133,22 @@ public class QuestionData
|
|||||||
/// 创建时间
|
/// 创建时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime CreatedTime { get; set; }
|
public DateTime CreatedTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 兼容新版作答消息字段
|
||||||
|
/// </summary>
|
||||||
|
public void Normalize()
|
||||||
|
{
|
||||||
|
if (UserId <= 0)
|
||||||
|
{
|
||||||
|
UserId = StudentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JournalId <= 0)
|
||||||
|
{
|
||||||
|
JournalId = BookId > 0 ? BookId : HomeworkId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -9,5 +9,6 @@
|
|||||||
<Project Path="QYZH.InteractiveMagazine.Repository/QYZH.InteractiveMagazine.Repository.csproj" />
|
<Project Path="QYZH.InteractiveMagazine.Repository/QYZH.InteractiveMagazine.Repository.csproj" />
|
||||||
<Project Path="QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj" />
|
<Project Path="QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj" />
|
||||||
<Project Path="QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj" />
|
<Project Path="QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj" />
|
||||||
|
<Project Path="QYZH.InteractiveMagazine.WeChatApi/QYZH.InteractiveMagazine.WeChatApi.csproj" />
|
||||||
<Project Path="QYZH.InteractiveMagazine.WorkService/QYZH.InteractiveMagazine.WorkService.csproj" />
|
<Project Path="QYZH.InteractiveMagazine.WorkService/QYZH.InteractiveMagazine.WorkService.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
Reference in New Issue
Block a user