22using Juggle . Infrastructure . Persistence ;
33using Juggle . Application . Models . Request ;
44using Juggle . Application . Models . Response ;
5+ using Juggle . Application . Services ;
56using Microsoft . AspNetCore . Authorization ;
67using Microsoft . AspNetCore . Mvc ;
78using Microsoft . EntityFrameworkCore ;
9+ using System . Text . Json ;
810
911namespace Juggle . Api . Controllers . Api ;
1012
@@ -13,47 +15,72 @@ namespace Juggle.Api.Controllers.Api;
1315public class RoleController : ControllerBase
1416{
1517 private readonly JuggleDbContext _db ;
18+ private readonly ITenantAccessor _tenant ;
1619
17- public RoleController ( JuggleDbContext db ) => _db = db ;
20+ public RoleController ( JuggleDbContext db , ITenantAccessor tenant )
21+ {
22+ _db = db ;
23+ _tenant = tenant ;
24+ }
1825
1926 /// <summary>角色分页列表</summary>
2027 [ HttpPost ( "page" ) , Authorize ]
2128 public async Task < ApiResult > Page ( [ FromBody ] PageRequest req )
2229 {
30+ // 超级管理员看所有角色,其他人员只看本租户角色和全局角色(TenantId=null)
2331 var query = _db . Roles . Where ( r => r . Deleted == 0 ) ;
32+ if ( ! _tenant . IsSuperAdmin )
33+ {
34+ // 非超管:显示全局角色(null TenantId)+ 本租户角色
35+ query = query . Where ( r => r . TenantId == null || r . TenantId == _tenant . TenantId ) ;
36+ }
37+
2438 if ( ! string . IsNullOrEmpty ( req . Keyword ) )
2539 query = query . Where ( r => r . RoleName ! . Contains ( req . Keyword ) || ( r . RoleCode != null && r . RoleCode . Contains ( req . Keyword ) ) ) ;
2640
2741 var total = await query . CountAsync ( ) ;
2842 var records = await query . OrderByDescending ( r => r . Id )
2943 . Skip ( ( req . PageNum - 1 ) * req . PageSize )
3044 . Take ( req . PageSize )
45+ . Select ( r => new
46+ {
47+ r . Id , r . RoleName , r . RoleCode , r . Remark , r . CreatedAt , r . UpdatedAt ,
48+ r . TenantId ,
49+ TenantName = _db . Tenants . Where ( t => t . Id == r . TenantId && t . Deleted == 0 ) . Select ( t => t . TenantName ) . FirstOrDefault ( ) ! ,
50+ MenuCount = _db . RoleMenus . Count ( rm => rm . RoleId == r . Id && rm . Deleted == 0 )
51+ } )
3152 . ToListAsync ( ) ;
3253
33- // 查每个角色的菜单权限数
34- var roleIds = records . Select ( r => r . Id ) . ToList ( ) ;
35- var menuCounts = await _db . RoleMenus
36- . Where ( rm => roleIds . Contains ( rm . RoleId ) && rm . Deleted == 0 )
37- . GroupBy ( rm => rm . RoleId )
38- . Select ( g => new { RoleId = g . Key , Count = g . Count ( ) } )
39- . ToDictionaryAsync ( x => x . RoleId , x => x . Count ) ;
40-
41- var result = records . Select ( r => new
42- {
43- r . Id , r . RoleName , r . RoleCode , r . Remark , r . CreatedAt , r . UpdatedAt ,
44- MenuCount = menuCounts . GetValueOrDefault ( r . Id , 0 )
45- } ) ;
46- return ApiResult . Success ( new { total , records = result } ) ;
54+ return ApiResult . Success ( new { total , records } ) ;
4755 }
4856
49- /// <summary>所有角色(下拉选择用 )</summary>
57+ /// <summary>角色下拉列表(根据权限过滤 )</summary>
5058 [ HttpGet ( "all" ) , Authorize ]
5159 public async Task < ApiResult > All ( )
5260 {
61+ var query = _db . Roles . Where ( r => r . Deleted == 0 ) ;
62+ if ( ! _tenant . IsSuperAdmin )
63+ query = query . Where ( r => r . TenantId == null || r . TenantId == _tenant . TenantId ) ;
64+
65+ var list = await query . OrderBy ( r => r . Id )
66+ . Select ( r => new { r . Id , r . RoleName , r . RoleCode , r . TenantId } )
67+ . ToListAsync ( ) ;
68+ return ApiResult . Success ( list ) ;
69+ }
70+
71+ /// <summary>根据租户ID获取角色列表(用户编辑时使用)</summary>
72+ [ HttpGet ( "byTenant/{tenantId}" ) , Authorize ]
73+ public async Task < ApiResult > ByTenant ( long tenantId )
74+ {
75+ // 超管可获取指定租户下所有角色
76+ // 非超管只能获取本租户的角色
77+ if ( ! _tenant . IsSuperAdmin && tenantId != _tenant . TenantId )
78+ return ApiResult . Fail ( "无权访问该租户角色" , 403 ) ;
79+
5380 var list = await _db . Roles
54- . Where ( r => r . Deleted == 0 )
81+ . Where ( r => r . Deleted == 0 && ( r . TenantId == tenantId || r . TenantId == null ) )
5582 . OrderBy ( r => r . Id )
56- . Select ( r => new { r . Id , r . RoleName , r . RoleCode } )
83+ . Select ( r => new { r . Id , r . RoleName , r . RoleCode , r . TenantId } )
5784 . ToListAsync ( ) ;
5885 return ApiResult . Success ( list ) ;
5986 }
@@ -70,10 +97,24 @@ public async Task<ApiResult> Detail(long id)
7097 . Select ( rm => rm . MenuKey )
7198 . ToListAsync ( ) ;
7299
100+ // 获取角色所属租户的最大菜单权限
101+ List < string > tenantMenuKeys = new ( ) ;
102+ if ( role . TenantId . HasValue )
103+ {
104+ var tenant = await _db . Tenants . FindAsync ( role . TenantId . Value ) ;
105+ if ( tenant != null )
106+ {
107+ try { tenantMenuKeys = JsonSerializer . Deserialize < List < string > > ( tenant . MenuKeys ?? "[]" ) ?? new ( ) ; }
108+ catch { tenantMenuKeys = new ( ) ; }
109+ }
110+ }
111+
73112 return ApiResult . Success ( new
74113 {
75114 role . Id , role . RoleName , role . RoleCode , role . Remark , role . CreatedAt ,
76- MenuKeys = menuKeys
115+ role . TenantId ,
116+ MenuKeys = menuKeys ,
117+ TenantMenuKeys = tenantMenuKeys // 角色可分配的最大权限边界
77118 } ) ;
78119 }
79120
@@ -84,17 +125,46 @@ public async Task<ApiResult> Add([FromBody] RoleAddRequest req)
84125 if ( string . IsNullOrWhiteSpace ( req . RoleName ) )
85126 return ApiResult . Fail ( "角色名称不能为空" ) ;
86127
128+ // 非超管不能设置租户
129+ if ( ! _tenant . IsSuperAdmin && req . TenantId != null )
130+ return ApiResult . Fail ( "无权指定租户" , 403 ) ;
131+
132+ // 非超管只能创建本租户角色或全局角色
133+ if ( ! _tenant . IsSuperAdmin && req . TenantId != null && req . TenantId != _tenant . TenantId )
134+ return ApiResult . Fail ( "只能创建本租户的角色" , 403 ) ;
135+
136+ // 租户编码唯一性
87137 if ( ! string . IsNullOrWhiteSpace ( req . RoleCode ) )
88138 {
89139 var exists = await _db . Roles . AnyAsync ( r => r . RoleCode == req . RoleCode && r . Deleted == 0 ) ;
90140 if ( exists ) return ApiResult . Fail ( "角色编码已存在" ) ;
91141 }
92142
143+ // 如果角色绑定租户,校验菜单权限不超出租户权限
144+ if ( req . TenantId . HasValue )
145+ {
146+ var tenant = await _db . Tenants . FindAsync ( req . TenantId . Value ) ;
147+ if ( tenant != null )
148+ {
149+ List < string > tenantMenuKeys ;
150+ try { tenantMenuKeys = JsonSerializer . Deserialize < List < string > > ( tenant . MenuKeys ?? "[]" ) ?? new ( ) ; }
151+ catch { tenantMenuKeys = new ( ) ; }
152+
153+ if ( tenantMenuKeys . Count > 0 )
154+ {
155+ var overKeys = req . MenuKeys . Where ( k => ! tenantMenuKeys . Contains ( k ) ) . ToList ( ) ;
156+ if ( overKeys . Count > 0 )
157+ return ApiResult . Fail ( $ "角色权限超出租户权限范围:{ string . Join ( ", " , overKeys ) } ") ;
158+ }
159+ }
160+ }
161+
93162 var role = new RoleEntity
94163 {
95164 RoleName = req . RoleName ,
96165 RoleCode = req . RoleCode ,
97166 Remark = req . Remark ,
167+ TenantId = req . TenantId ,
98168 Deleted = 0 ,
99169 CreatedAt = DateTime . Now . ToString ( "o" )
100170 } ;
@@ -106,10 +176,7 @@ public async Task<ApiResult> Add([FromBody] RoleAddRequest req)
106176 {
107177 _db . RoleMenus . AddRange ( req . MenuKeys . Select ( key => new RoleMenuEntity
108178 {
109- RoleId = role . Id ,
110- MenuKey = key ,
111- Deleted = 0 ,
112- CreatedAt = DateTime . Now . ToString ( "o" )
179+ RoleId = role . Id , MenuKey = key , Deleted = 0 , CreatedAt = DateTime . Now . ToString ( "o" )
113180 } ) ) ;
114181 await _db . SaveChangesAsync ( ) ;
115182 }
@@ -125,15 +192,41 @@ public async Task<ApiResult> Update([FromBody] RoleUpdateRequest req)
125192 if ( role == null || role . Deleted == 1 ) return ApiResult . Fail ( "角色不存在" ) ;
126193 if ( role . Id == 1 ) return ApiResult . Fail ( "不能修改超级管理员角色" ) ;
127194
195+ // 非超管不能修改租户
196+ if ( ! _tenant . IsSuperAdmin && req . TenantId != role . TenantId )
197+ return ApiResult . Fail ( "无权修改角色所属租户" , 403 ) ;
198+
199+ // 非超管不能把角色移到其他租户
200+ if ( ! _tenant . IsSuperAdmin && req . TenantId != null && req . TenantId != _tenant . TenantId )
201+ return ApiResult . Fail ( "无权将角色移至其他租户" , 403 ) ;
202+
203+ // 如果修改了租户,重新校验权限
204+ if ( req . TenantId . HasValue )
205+ {
206+ var tenant = await _db . Tenants . FindAsync ( req . TenantId . Value ) ;
207+ if ( tenant != null )
208+ {
209+ List < string > tenantMenuKeys ;
210+ try { tenantMenuKeys = JsonSerializer . Deserialize < List < string > > ( tenant . MenuKeys ?? "[]" ) ?? new ( ) ; }
211+ catch { tenantMenuKeys = new ( ) ; }
212+
213+ if ( tenantMenuKeys . Count > 0 )
214+ {
215+ var overKeys = req . MenuKeys . Where ( k => ! tenantMenuKeys . Contains ( k ) ) . ToList ( ) ;
216+ if ( overKeys . Count > 0 )
217+ return ApiResult . Fail ( $ "角色权限超出租户权限范围:{ string . Join ( ", " , overKeys ) } ") ;
218+ }
219+ }
220+ }
221+
128222 role . RoleName = req . RoleName ;
129223 role . RoleCode = req . RoleCode ;
130224 role . Remark = req . Remark ;
225+ role . TenantId = req . TenantId ;
131226 role . UpdatedAt = DateTime . Now . ToString ( "o" ) ;
132227
133228 // 先删除旧权限
134- var oldMenus = await _db . RoleMenus
135- . Where ( rm => rm . RoleId == req . Id && rm . Deleted == 0 )
136- . ToListAsync ( ) ;
229+ var oldMenus = await _db . RoleMenus . Where ( rm => rm . RoleId == req . Id && rm . Deleted == 0 ) . ToListAsync ( ) ;
137230 foreach ( var m in oldMenus ) m . Deleted = 1 ;
138231 _db . RoleMenus . UpdateRange ( oldMenus ) ;
139232
@@ -142,10 +235,7 @@ public async Task<ApiResult> Update([FromBody] RoleUpdateRequest req)
142235 {
143236 _db . RoleMenus . AddRange ( req . MenuKeys . Select ( key => new RoleMenuEntity
144237 {
145- RoleId = req . Id ,
146- MenuKey = key ,
147- Deleted = 0 ,
148- CreatedAt = DateTime . Now . ToString ( "o" )
238+ RoleId = req . Id , MenuKey = key , Deleted = 0 , CreatedAt = DateTime . Now . ToString ( "o" )
149239 } ) ) ;
150240 }
151241
@@ -158,17 +248,20 @@ public async Task<ApiResult> Update([FromBody] RoleUpdateRequest req)
158248 public async Task < ApiResult > Delete ( long id )
159249 {
160250 if ( id == 1 ) return ApiResult . Fail ( "不能删除超级管理员角色" ) ;
251+
161252 var role = await _db . Roles . FindAsync ( id ) ;
162253 if ( role == null || role . Deleted == 1 ) return ApiResult . Fail ( "角色不存在" ) ;
163254
164- // 检查是否有用户使用该角色
255+ // 非超管只能删除本租户角色
256+ if ( ! _tenant . IsSuperAdmin && role . TenantId != _tenant . TenantId && role . TenantId != null )
257+ return ApiResult . Fail ( "无权删除该角色" , 403 ) ;
258+
165259 var hasUsers = await _db . Users . AnyAsync ( u => u . RoleId == id && u . Deleted == 0 ) ;
166260 if ( hasUsers ) return ApiResult . Fail ( "该角色下还有用户,不能删除" ) ;
167261
168- role . Deleted = 1 ;
262+ role . Deleted = 1 ;
169263 role . UpdatedAt = DateTime . Now . ToString ( "o" ) ;
170264
171- // 同时删除角色菜单
172265 var menus = await _db . RoleMenus . Where ( rm => rm . RoleId == id && rm . Deleted == 0 ) . ToListAsync ( ) ;
173266 foreach ( var m in menus ) m . Deleted = 1 ;
174267 _db . RoleMenus . UpdateRange ( menus ) ;
0 commit comments